完成帖子列表分页获取展示接口

This commit is contained in:
柏码の讲师 2023-10-11 18:46:28 +08:00
parent e487de0071
commit f80c3ef0c7
9 changed files with 124 additions and 5 deletions

View File

@ -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));
}
} }

View File

@ -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;
}

View File

@ -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);
} }

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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:";
} }

View File

@ -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秒内不能再次发起请求

View File

@ -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">