完成帖子列表分页获取展示接口
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.vo.request.TopicCreateVO;
|
||||
import com.example.entity.vo.response.TopicPreviewVO;
|
||||
import com.example.entity.vo.response.TopicTypeVO;
|
||||
import com.example.entity.vo.response.WeatherVO;
|
||||
import com.example.service.TopicService;
|
||||
@ -10,6 +11,7 @@ import com.example.utils.Const;
|
||||
import com.example.utils.ControllerUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
@ -49,4 +51,10 @@ public class ForumController {
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int id) {
|
||||
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.TopicType;
|
||||
import com.example.entity.vo.request.TopicCreateVO;
|
||||
import com.example.entity.vo.response.TopicPreviewVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface TopicService extends IService<Topic> {
|
||||
List<TopicType> listType();
|
||||
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
|
||||
public String uploadImage(int id, MultipartFile file) throws IOException {
|
||||
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("-", "");
|
||||
Date date = new Date();
|
||||
imageName = "/cache/" + format.format(date) + "/" + imageName;
|
||||
|
@ -1,21 +1,25 @@
|
||||
package com.example.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
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.example.entity.dto.Topic;
|
||||
import com.example.entity.dto.TopicType;
|
||||
import com.example.entity.vo.request.TopicCreateVO;
|
||||
import com.example.entity.vo.response.TopicPreviewVO;
|
||||
import com.example.mapper.TopicMapper;
|
||||
import com.example.mapper.TopicTypeMapper;
|
||||
import com.example.service.TopicService;
|
||||
import com.example.utils.CacheUtils;
|
||||
import com.example.utils.Const;
|
||||
import com.example.utils.FlowUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements TopicService {
|
||||
@ -26,6 +30,9 @@ public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements
|
||||
@Resource
|
||||
FlowUtils flowUtils;
|
||||
|
||||
@Resource
|
||||
CacheUtils cacheUtils;
|
||||
|
||||
@Override
|
||||
public List<TopicType> listType() {
|
||||
return typeMapper.selectList(null);
|
||||
@ -36,7 +43,7 @@ public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements
|
||||
if(!this.textLimitCheck(vo.getContent()))
|
||||
return "文章长度过大,发文失败!";
|
||||
String key = Const.FORUM_TOPIC_CREATE_COUNTER + uid;
|
||||
if(flowUtils.limitPeriodCounterCheck(key, 3, 3600))
|
||||
if(!flowUtils.limitPeriodCounterCheck(key, 3, 3600))
|
||||
return "发文频繁,请稍后再试!";
|
||||
Topic topic = new Topic();
|
||||
BeanUtils.copyProperties(vo, topic);
|
||||
@ -44,12 +51,50 @@ public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements
|
||||
topic.setUid(uid);
|
||||
topic.setTime(new Date());
|
||||
if(this.save(topic)) {
|
||||
cacheUtils.deleteCache(Const.FORUM_TOPIC_PREVIEW_CACHE + "*");
|
||||
return null;
|
||||
} else {
|
||||
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) {
|
||||
if(object == null) return false;
|
||||
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_WEATHER_CACHE = "weather:cache:";
|
||||
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
|
||||
StringRedisTemplate template;
|
||||
|
||||
private static final LimitAction defaultAction = overclock -> overclock;
|
||||
private static final LimitAction defaultAction = overclock -> !overclock;
|
||||
|
||||
/**
|
||||
* 针对于单次频率限制,请求成功后,在冷却时间内不得再次进行请求,如3秒内不能再次发起请求
|
||||
|
@ -14,11 +14,13 @@ const weather = reactive({
|
||||
success: false
|
||||
})
|
||||
const editor = ref(false)
|
||||
const list = ref(null)
|
||||
|
||||
const today = computed(() => {
|
||||
const date = new Date()
|
||||
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 => {
|
||||
const latitude = position.coords.latitude
|
||||
@ -48,6 +50,10 @@ navigator.geolocation.getCurrentPosition(position => {
|
||||
<div @click="editor = true"><el-icon><EditPen/></el-icon> 点击发表主题...</div>
|
||||
</div>
|
||||
</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 style="width: 280px">
|
||||
<div style="position: sticky;top: 20px">
|
||||
|
Loading…
x
Reference in New Issue
Block a user