diff --git a/my-project-backend/src/main/java/com/example/controller/ForumController.java b/my-project-backend/src/main/java/com/example/controller/ForumController.java index 5ac85a8..c1b7585 100644 --- a/my-project-backend/src/main/java/com/example/controller/ForumController.java +++ b/my-project-backend/src/main/java/com/example/controller/ForumController.java @@ -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> listTopic(@RequestParam @Min(0) int page, + @RequestParam @Min(0) int type) { + return RestBean.success(topicService.listTopicByPage(page, type)); + } } diff --git a/my-project-backend/src/main/java/com/example/entity/vo/response/TopicPreviewVO.java b/my-project-backend/src/main/java/com/example/entity/vo/response/TopicPreviewVO.java new file mode 100644 index 0000000..68fe4e2 --- /dev/null +++ b/my-project-backend/src/main/java/com/example/entity/vo/response/TopicPreviewVO.java @@ -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 images; + Date time; +} diff --git a/my-project-backend/src/main/java/com/example/service/TopicService.java b/my-project-backend/src/main/java/com/example/service/TopicService.java index 64935f6..a65e0f9 100644 --- a/my-project-backend/src/main/java/com/example/service/TopicService.java +++ b/my-project-backend/src/main/java/com/example/service/TopicService.java @@ -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 { List listType(); String createTopic(int uid, TopicCreateVO vo); + List listTopicByPage(int pageNumber, int type); } diff --git a/my-project-backend/src/main/java/com/example/service/impl/ImageServiceImpl.java b/my-project-backend/src/main/java/com/example/service/impl/ImageServiceImpl.java index 16e88c7..93d3032 100644 --- a/my-project-backend/src/main/java/com/example/service/impl/ImageServiceImpl.java +++ b/my-project-backend/src/main/java/com/example/service/impl/ImageServiceImpl.java @@ -50,7 +50,7 @@ public class ImageServiceImpl extends ServiceImpl @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; diff --git a/my-project-backend/src/main/java/com/example/service/impl/TopicServiceImpl.java b/my-project-backend/src/main/java/com/example/service/impl/TopicServiceImpl.java index d3bfda2..c35f1ca 100644 --- a/my-project-backend/src/main/java/com/example/service/impl/TopicServiceImpl.java +++ b/my-project-backend/src/main/java/com/example/service/impl/TopicServiceImpl.java @@ -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 implements TopicService { @@ -26,6 +30,9 @@ public class TopicServiceImpl extends ServiceImpl implements @Resource FlowUtils flowUtils; + @Resource + CacheUtils cacheUtils; + @Override public List listType() { return typeMapper.selectList(null); @@ -36,7 +43,7 @@ public class TopicServiceImpl extends ServiceImpl 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 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 listTopicByPage(int pageNumber, int type){ + String key = Const.FORUM_TOPIC_PREVIEW_CACHE + pageNumber; + List list = cacheUtils.takeListFromCache(key, TopicPreviewVO.class); + if(list != null) return list; + //无缓存从数据库读取 + Page page = Page.of(pageNumber, 10); + if(type == 0) + this.baseMapper.selectPage(page, null); + else + this.baseMapper.selectPage(page, Wrappers.query().eq("type", type)); + List 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 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; diff --git a/my-project-backend/src/main/java/com/example/utils/CacheUtils.java b/my-project-backend/src/main/java/com/example/utils/CacheUtils.java new file mode 100644 index 0000000..7e3f2e7 --- /dev/null +++ b/my-project-backend/src/main/java/com/example/utils/CacheUtils.java @@ -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 takeFromCache(String key, Class dataType) { + String s = template.opsForValue().get(key); + if(s == null) return null; + return JSONObject.from(s).to(dataType); + } + + public List takeListFromCache(String key, Class itemType) { + String s = template.opsForValue().get(key); + if(s == null) return null; + return JSONArray.from(s).toList(itemType); + } + + public void saveToCache(String key, T data, long expire) { + template.opsForValue().set(key, JSONObject.from(data).toJSONString(), expire, TimeUnit.SECONDS); + } + + public 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); + } +} diff --git a/my-project-backend/src/main/java/com/example/utils/Const.java b/my-project-backend/src/main/java/com/example/utils/Const.java index a691f01..76133a8 100644 --- a/my-project-backend/src/main/java/com/example/utils/Const.java +++ b/my-project-backend/src/main/java/com/example/utils/Const.java @@ -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:"; } diff --git a/my-project-backend/src/main/java/com/example/utils/FlowUtils.java b/my-project-backend/src/main/java/com/example/utils/FlowUtils.java index 4bd79d7..4836cf4 100644 --- a/my-project-backend/src/main/java/com/example/utils/FlowUtils.java +++ b/my-project-backend/src/main/java/com/example/utils/FlowUtils.java @@ -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秒内不能再次发起请求 diff --git a/my-project-frontend/src/views/forum/TopicList.vue b/my-project-frontend/src/views/forum/TopicList.vue index 47f0fcf..0a825d5 100644 --- a/my-project-frontend/src/views/forum/TopicList.vue +++ b/my-project-frontend/src/views/forum/TopicList.vue @@ -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 => {
点击发表主题...
+ +
{{item.title}}
+
{{item.text}}
+