完成帖子列表分页获取展示接口
This commit is contained in:
parent
e487de0071
commit
f80c3ef0c7
@ -2,6 +2,7 @@ package com.example.controller;
|
|||||||
|
|
||||||
import com.example.entity.RestBean;
|
import com.example.entity.RestBean;
|
||||||
import com.example.entity.vo.request.TopicCreateVO;
|
import com.example.entity.vo.request.TopicCreateVO;
|
||||||
|
import com.example.entity.vo.response.TopicPreviewVO;
|
||||||
import com.example.entity.vo.response.TopicTypeVO;
|
import com.example.entity.vo.response.TopicTypeVO;
|
||||||
import com.example.entity.vo.response.WeatherVO;
|
import com.example.entity.vo.response.WeatherVO;
|
||||||
import com.example.service.TopicService;
|
import com.example.service.TopicService;
|
||||||
@ -10,6 +11,7 @@ import com.example.utils.Const;
|
|||||||
import com.example.utils.ControllerUtils;
|
import com.example.utils.ControllerUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -49,4 +51,10 @@ public class ForumController {
|
|||||||
@RequestAttribute(Const.ATTR_USER_ID) int id) {
|
@RequestAttribute(Const.ATTR_USER_ID) int id) {
|
||||||
return utils.messageHandle(() -> topicService.createTopic(id, vo));
|
return utils.messageHandle(() -> topicService.createTopic(id, vo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/list-topic")
|
||||||
|
public RestBean<List<TopicPreviewVO>> listTopic(@RequestParam @Min(0) int page,
|
||||||
|
@RequestParam @Min(0) int type) {
|
||||||
|
return RestBean.success(topicService.listTopicByPage(page, type));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.example.entity.vo.response;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TopicPreviewVO {
|
||||||
|
int id;
|
||||||
|
int type;
|
||||||
|
String title;
|
||||||
|
String text;
|
||||||
|
List<String> images;
|
||||||
|
Date time;
|
||||||
|
}
|
@ -4,10 +4,12 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
|||||||
import com.example.entity.dto.Topic;
|
import com.example.entity.dto.Topic;
|
||||||
import com.example.entity.dto.TopicType;
|
import com.example.entity.dto.TopicType;
|
||||||
import com.example.entity.vo.request.TopicCreateVO;
|
import com.example.entity.vo.request.TopicCreateVO;
|
||||||
|
import com.example.entity.vo.response.TopicPreviewVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface TopicService extends IService<Topic> {
|
public interface TopicService extends IService<Topic> {
|
||||||
List<TopicType> listType();
|
List<TopicType> listType();
|
||||||
String createTopic(int uid, TopicCreateVO vo);
|
String createTopic(int uid, TopicCreateVO vo);
|
||||||
|
List<TopicPreviewVO> listTopicByPage(int pageNumber, int type);
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ public class ImageServiceImpl extends ServiceImpl<ImageStoreMapper, StoreImage>
|
|||||||
@Override
|
@Override
|
||||||
public String uploadImage(int id, MultipartFile file) throws IOException {
|
public String uploadImage(int id, MultipartFile file) throws IOException {
|
||||||
String key = Const.FORUM_IMAGE_COUNTER + id;
|
String key = Const.FORUM_IMAGE_COUNTER + id;
|
||||||
if(flowUtils.limitPeriodCounterCheck(key, 20, 3600)) return null;
|
if(!flowUtils.limitPeriodCounterCheck(key, 20, 3600)) return null;
|
||||||
String imageName = UUID.randomUUID().toString().replace("-", "");
|
String imageName = UUID.randomUUID().toString().replace("-", "");
|
||||||
Date date = new Date();
|
Date date = new Date();
|
||||||
imageName = "/cache/" + format.format(date) + "/" + imageName;
|
imageName = "/cache/" + format.format(date) + "/" + imageName;
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
package com.example.service.impl;
|
package com.example.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.example.entity.dto.Topic;
|
import com.example.entity.dto.Topic;
|
||||||
import com.example.entity.dto.TopicType;
|
import com.example.entity.dto.TopicType;
|
||||||
import com.example.entity.vo.request.TopicCreateVO;
|
import com.example.entity.vo.request.TopicCreateVO;
|
||||||
|
import com.example.entity.vo.response.TopicPreviewVO;
|
||||||
import com.example.mapper.TopicMapper;
|
import com.example.mapper.TopicMapper;
|
||||||
import com.example.mapper.TopicTypeMapper;
|
import com.example.mapper.TopicTypeMapper;
|
||||||
import com.example.service.TopicService;
|
import com.example.service.TopicService;
|
||||||
|
import com.example.utils.CacheUtils;
|
||||||
import com.example.utils.Const;
|
import com.example.utils.Const;
|
||||||
import com.example.utils.FlowUtils;
|
import com.example.utils.FlowUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements TopicService {
|
public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements TopicService {
|
||||||
@ -26,6 +30,9 @@ public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements
|
|||||||
@Resource
|
@Resource
|
||||||
FlowUtils flowUtils;
|
FlowUtils flowUtils;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
CacheUtils cacheUtils;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TopicType> listType() {
|
public List<TopicType> listType() {
|
||||||
return typeMapper.selectList(null);
|
return typeMapper.selectList(null);
|
||||||
@ -36,7 +43,7 @@ public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements
|
|||||||
if(!this.textLimitCheck(vo.getContent()))
|
if(!this.textLimitCheck(vo.getContent()))
|
||||||
return "文章长度过大,发文失败!";
|
return "文章长度过大,发文失败!";
|
||||||
String key = Const.FORUM_TOPIC_CREATE_COUNTER + uid;
|
String key = Const.FORUM_TOPIC_CREATE_COUNTER + uid;
|
||||||
if(flowUtils.limitPeriodCounterCheck(key, 3, 3600))
|
if(!flowUtils.limitPeriodCounterCheck(key, 3, 3600))
|
||||||
return "发文频繁,请稍后再试!";
|
return "发文频繁,请稍后再试!";
|
||||||
Topic topic = new Topic();
|
Topic topic = new Topic();
|
||||||
BeanUtils.copyProperties(vo, topic);
|
BeanUtils.copyProperties(vo, topic);
|
||||||
@ -44,12 +51,50 @@ public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements
|
|||||||
topic.setUid(uid);
|
topic.setUid(uid);
|
||||||
topic.setTime(new Date());
|
topic.setTime(new Date());
|
||||||
if(this.save(topic)) {
|
if(this.save(topic)) {
|
||||||
|
cacheUtils.deleteCache(Const.FORUM_TOPIC_PREVIEW_CACHE + "*");
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return "内部错误,请联系管理员";
|
return "内部错误,请联系管理员";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<TopicPreviewVO> listTopicByPage(int pageNumber, int type){
|
||||||
|
String key = Const.FORUM_TOPIC_PREVIEW_CACHE + pageNumber;
|
||||||
|
List<TopicPreviewVO> list = cacheUtils.takeListFromCache(key, TopicPreviewVO.class);
|
||||||
|
if(list != null) return list;
|
||||||
|
//无缓存从数据库读取
|
||||||
|
Page<Topic> page = Page.of(pageNumber, 10);
|
||||||
|
if(type == 0)
|
||||||
|
this.baseMapper.selectPage(page, null);
|
||||||
|
else
|
||||||
|
this.baseMapper.selectPage(page, Wrappers.<Topic>query().eq("type", type));
|
||||||
|
List<Topic> topics = page.getRecords();
|
||||||
|
if(topics.isEmpty()) return null;
|
||||||
|
list = topics.stream().map(this::resolveToPreview).toList();
|
||||||
|
cacheUtils.saveListToCache(key, list, 20); //进缓存
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TopicPreviewVO resolveToPreview(Topic topic) {
|
||||||
|
TopicPreviewVO vo = new TopicPreviewVO();
|
||||||
|
BeanUtils.copyProperties(topic, vo);
|
||||||
|
List<String> images = new ArrayList<>();
|
||||||
|
StringBuilder previewText = new StringBuilder();
|
||||||
|
JSONArray ops = JSONObject.parseObject(topic.getContent()).getJSONArray("ops");
|
||||||
|
for (Object op : ops) {
|
||||||
|
Object insert = JSONObject.from(op).get("insert");
|
||||||
|
if(insert instanceof String text) {
|
||||||
|
if(previewText.length() >= 300) continue;
|
||||||
|
previewText.append(text);
|
||||||
|
} else if(insert instanceof Map<?, ?> map) {
|
||||||
|
Optional.ofNullable(map.get("image")).ifPresent(obj -> images.add(obj.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vo.setText(previewText.toString());
|
||||||
|
vo.setImages(images);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean textLimitCheck(JSONObject object) {
|
private boolean textLimitCheck(JSONObject object) {
|
||||||
if(object == null) return false;
|
if(object == null) return false;
|
||||||
long length = 0;
|
long length = 0;
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.example.utils;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class CacheUtils {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
StringRedisTemplate template;
|
||||||
|
|
||||||
|
public <T> T takeFromCache(String key, Class<T> dataType) {
|
||||||
|
String s = template.opsForValue().get(key);
|
||||||
|
if(s == null) return null;
|
||||||
|
return JSONObject.from(s).to(dataType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> List<T> takeListFromCache(String key, Class<T> itemType) {
|
||||||
|
String s = template.opsForValue().get(key);
|
||||||
|
if(s == null) return null;
|
||||||
|
return JSONArray.from(s).toList(itemType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void saveToCache(String key, T data, long expire) {
|
||||||
|
template.opsForValue().set(key, JSONObject.from(data).toJSONString(), expire, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void saveListToCache(String key, T data, long expire) {
|
||||||
|
template.opsForValue().set(key, JSONArray.from(data).toJSONString(), expire, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteCache(String key){
|
||||||
|
template.delete(key);
|
||||||
|
}
|
||||||
|
}
|
@ -26,4 +26,5 @@ public final class Const {
|
|||||||
public final static String FORUM_IMAGE_COUNTER = "image:upload:";
|
public final static String FORUM_IMAGE_COUNTER = "image:upload:";
|
||||||
public final static String FORUM_WEATHER_CACHE = "weather:cache:";
|
public final static String FORUM_WEATHER_CACHE = "weather:cache:";
|
||||||
public final static String FORUM_TOPIC_CREATE_COUNTER = "topic:create:";
|
public final static String FORUM_TOPIC_CREATE_COUNTER = "topic:create:";
|
||||||
|
public final static String FORUM_TOPIC_PREVIEW_CACHE = "topic:preview:";
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ public class FlowUtils {
|
|||||||
@Resource
|
@Resource
|
||||||
StringRedisTemplate template;
|
StringRedisTemplate template;
|
||||||
|
|
||||||
private static final LimitAction defaultAction = overclock -> overclock;
|
private static final LimitAction defaultAction = overclock -> !overclock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 针对于单次频率限制,请求成功后,在冷却时间内不得再次进行请求,如3秒内不能再次发起请求
|
* 针对于单次频率限制,请求成功后,在冷却时间内不得再次进行请求,如3秒内不能再次发起请求
|
||||||
|
@ -14,11 +14,13 @@ const weather = reactive({
|
|||||||
success: false
|
success: false
|
||||||
})
|
})
|
||||||
const editor = ref(false)
|
const editor = ref(false)
|
||||||
|
const list = ref(null)
|
||||||
|
|
||||||
const today = computed(() => {
|
const today = computed(() => {
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
return `${date.getFullYear()} 年 ${date.getMonth() + 1} 月 ${date.getDate()} 日`
|
return `${date.getFullYear()} 年 ${date.getMonth() + 1} 月 ${date.getDate()} 日`
|
||||||
})
|
})
|
||||||
|
get('/api/forum/list-topic?page=1&type=0', data => list.value = data)
|
||||||
|
|
||||||
navigator.geolocation.getCurrentPosition(position => {
|
navigator.geolocation.getCurrentPosition(position => {
|
||||||
const latitude = position.coords.latitude
|
const latitude = position.coords.latitude
|
||||||
@ -48,6 +50,10 @@ navigator.geolocation.getCurrentPosition(position => {
|
|||||||
<div @click="editor = true"><el-icon><EditPen/></el-icon> 点击发表主题...</div>
|
<div @click="editor = true"><el-icon><EditPen/></el-icon> 点击发表主题...</div>
|
||||||
</div>
|
</div>
|
||||||
</light-card>
|
</light-card>
|
||||||
|
<light-card v-for="item in list" style="margin-top: 10px">
|
||||||
|
<div>{{item.title}}</div>
|
||||||
|
<div>{{item.text}}</div>
|
||||||
|
</light-card>
|
||||||
</div>
|
</div>
|
||||||
<div style="width: 280px">
|
<div style="width: 280px">
|
||||||
<div style="position: sticky;top: 20px">
|
<div style="position: sticky;top: 20px">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user