feat(comment): 实现评论热度排序及缓存优化
- 修改 CommentDO 中 heat 字段类型从 BigDecimal为 Double - 新增 selectHeatComments 方法用于查询热门评论- 优化评论分页查询逻辑,引入 Redis 缓存提升性能 - 新增评论总数与热门评论的 Redis 缓存同步机制 - 实现评论详情的 Redis 批量缓存与过期策略 - 添加 COMMENT_NOT_FOUND 业务异常码 - 更新 RedisKeyConstants 增加相关键构建方法 - 调整 XML 映射文件以支持新的查询与字段类型- 引入 RedisTemplate 和线程池异步处理缓存操作 - 在 FindCommentItemRspVO 中新增 heat 字段返回热度值
This commit is contained in:
1
.idea/dictionaries/project.xml
generated
1
.idea/dictionaries/project.xml
generated
@@ -6,6 +6,7 @@
|
|||||||
<w>hannote</w>
|
<w>hannote</w>
|
||||||
<w>hanserwei</w>
|
<w>hanserwei</w>
|
||||||
<w>jobhandler</w>
|
<w>jobhandler</w>
|
||||||
|
<w>mget</w>
|
||||||
<w>nacos</w>
|
<w>nacos</w>
|
||||||
<w>operationlog</w>
|
<w>operationlog</w>
|
||||||
<w>rustfs</w>
|
<w>rustfs</w>
|
||||||
|
|||||||
@@ -7,6 +7,26 @@ public class RedisKeyConstants {
|
|||||||
*/
|
*/
|
||||||
private static final String HAVE_FIRST_REPLY_COMMENT_KEY_PREFIX = "comment:havaFirstReplyCommentId:";
|
private static final String HAVE_FIRST_REPLY_COMMENT_KEY_PREFIX = "comment:havaFirstReplyCommentId:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash Field 键:评论总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_COMMENT_TOTAL = "commentTotal";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key 前缀:笔记评论总数
|
||||||
|
*/
|
||||||
|
private static final String COUNT_COMMENT_TOTAL_KEY_PREFIX = "count:note:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key 前缀:评论分页 ZSET
|
||||||
|
*/
|
||||||
|
private static final String COMMENT_LIST_KEY_PREFIX = "comment:list:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key 前缀:评论详情 JSON
|
||||||
|
*/
|
||||||
|
private static final String COMMENT_DETAIL_KEY_PREFIX = "comment:detail:";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建完整 KEY
|
* 构建完整 KEY
|
||||||
@@ -18,4 +38,34 @@ public class RedisKeyConstants {
|
|||||||
return HAVE_FIRST_REPLY_COMMENT_KEY_PREFIX + commentId;
|
return HAVE_FIRST_REPLY_COMMENT_KEY_PREFIX + commentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建笔记评论总数完整 KEY
|
||||||
|
*
|
||||||
|
* @param noteId 笔记 ID
|
||||||
|
* @return 笔记评论总数完整 KEY
|
||||||
|
*/
|
||||||
|
public static String buildNoteCommentTotalKey(Long noteId) {
|
||||||
|
return COUNT_COMMENT_TOTAL_KEY_PREFIX + noteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建评论分页 ZSET 完整 KEY
|
||||||
|
*
|
||||||
|
* @param noteId 笔记 ID
|
||||||
|
* @return 评论分页 ZSET 完整 KEY
|
||||||
|
*/
|
||||||
|
public static String buildCommentListKey(Long noteId) {
|
||||||
|
return COMMENT_LIST_KEY_PREFIX + noteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建评论详情完整 KEY
|
||||||
|
*
|
||||||
|
* @param commentId 评论 ID
|
||||||
|
* @return 评论详情完整 KEY
|
||||||
|
*/
|
||||||
|
public static String buildCommentDetailKey(Object commentId) {
|
||||||
|
return COMMENT_DETAIL_KEY_PREFIX + commentId;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,6 @@ import lombok.Builder;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,7 +120,7 @@ public class CommentDO {
|
|||||||
* 评论热度
|
* 评论热度
|
||||||
*/
|
*/
|
||||||
@TableField(value = "heat")
|
@TableField(value = "heat")
|
||||||
private BigDecimal heat;
|
private Double heat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 最早回复的评论ID (只有一级评论需要)
|
* 最早回复的评论ID (只有一级评论需要)
|
||||||
|
|||||||
@@ -74,4 +74,12 @@ public interface CommentDOMapper extends BaseMapper<CommentDO> {
|
|||||||
* @return 二级评论
|
* @return 二级评论
|
||||||
*/
|
*/
|
||||||
List<CommentDO> selectTwoLevelCommentByIds(@Param("commentIds") List<Long> commentIds);
|
List<CommentDO> selectTwoLevelCommentByIds(@Param("commentIds") List<Long> commentIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询热门评论
|
||||||
|
*
|
||||||
|
* @param noteId 笔记 ID
|
||||||
|
* @return 热门评论
|
||||||
|
*/
|
||||||
|
List<CommentDO> selectHeatComments(Long noteId);
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
|
|||||||
PARAM_NOT_VALID("COMMENT-10001", "参数错误"),
|
PARAM_NOT_VALID("COMMENT-10001", "参数错误"),
|
||||||
|
|
||||||
// ----------- 业务异常状态码 -----------
|
// ----------- 业务异常状态码 -----------
|
||||||
|
COMMENT_NOT_FOUND("COMMENT-20001", "此评论不存在"),
|
||||||
;
|
;
|
||||||
|
|
||||||
// 异常码
|
// 异常码
|
||||||
|
|||||||
@@ -61,4 +61,9 @@ public class FindCommentItemRspVO {
|
|||||||
*/
|
*/
|
||||||
private FindCommentItemRspVO firstReplyComment;
|
private FindCommentItemRspVO firstReplyComment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 热度值
|
||||||
|
*/
|
||||||
|
private Double heat;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,24 @@
|
|||||||
package com.hanserwei.hannote.comment.biz.service.impl;
|
package com.hanserwei.hannote.comment.biz.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder;
|
import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder;
|
||||||
import com.hanserwei.framework.common.constant.DateConstants;
|
import com.hanserwei.framework.common.constant.DateConstants;
|
||||||
|
import com.hanserwei.framework.common.exception.ApiException;
|
||||||
import com.hanserwei.framework.common.response.PageResponse;
|
import com.hanserwei.framework.common.response.PageResponse;
|
||||||
import com.hanserwei.framework.common.response.Response;
|
import com.hanserwei.framework.common.response.Response;
|
||||||
import com.hanserwei.framework.common.utils.DateUtils;
|
import com.hanserwei.framework.common.utils.DateUtils;
|
||||||
import com.hanserwei.framework.common.utils.JsonUtils;
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
import com.hanserwei.hannote.comment.biz.constants.MQConstants;
|
import com.hanserwei.hannote.comment.biz.constants.MQConstants;
|
||||||
|
import com.hanserwei.hannote.comment.biz.constants.RedisKeyConstants;
|
||||||
import com.hanserwei.hannote.comment.biz.domain.dataobject.CommentDO;
|
import com.hanserwei.hannote.comment.biz.domain.dataobject.CommentDO;
|
||||||
import com.hanserwei.hannote.comment.biz.domain.mapper.CommentDOMapper;
|
import com.hanserwei.hannote.comment.biz.domain.mapper.CommentDOMapper;
|
||||||
import com.hanserwei.hannote.comment.biz.domain.mapper.NoteCountDOMapper;
|
import com.hanserwei.hannote.comment.biz.domain.mapper.NoteCountDOMapper;
|
||||||
|
import com.hanserwei.hannote.comment.biz.enums.ResponseCodeEnum;
|
||||||
import com.hanserwei.hannote.comment.biz.model.dto.PublishCommentMqDTO;
|
import com.hanserwei.hannote.comment.biz.model.dto.PublishCommentMqDTO;
|
||||||
import com.hanserwei.hannote.comment.biz.model.vo.FindCommentItemRspVO;
|
import com.hanserwei.hannote.comment.biz.model.vo.FindCommentItemRspVO;
|
||||||
import com.hanserwei.hannote.comment.biz.model.vo.FindCommentPageListReqVO;
|
import com.hanserwei.hannote.comment.biz.model.vo.FindCommentPageListReqVO;
|
||||||
@@ -29,12 +34,13 @@ import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
|||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.data.redis.core.*;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Map;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -53,6 +59,10 @@ public class CommentServiceImpl extends ServiceImpl<CommentDOMapper, CommentDO>
|
|||||||
private KeyValueRpcService keyValueRpcService;
|
private KeyValueRpcService keyValueRpcService;
|
||||||
@Resource
|
@Resource
|
||||||
private UserRpcService userRpcService;
|
private UserRpcService userRpcService;
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource(name = "taskExecutor")
|
||||||
|
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<?> publishComment(PublishCommentReqVO publishCommentReqVO) {
|
public Response<?> publishComment(PublishCommentReqVO publishCommentReqVO) {
|
||||||
@@ -127,13 +137,33 @@ public class CommentServiceImpl extends ServiceImpl<CommentDOMapper, CommentDO>
|
|||||||
// 每页展示一级评论数量
|
// 每页展示一级评论数量
|
||||||
int pageSize = 10;
|
int pageSize = 10;
|
||||||
|
|
||||||
// TODO: 先从缓存中查询
|
// 构建评论总数 Redis Key
|
||||||
|
String noteCommentTotalKey = RedisKeyConstants.buildNoteCommentTotalKey(noteId);
|
||||||
|
// 先从 Redis 中查询该笔记的评论总数
|
||||||
|
Number commentTotal = (Number) redisTemplate.opsForHash()
|
||||||
|
.get(noteCommentTotalKey, RedisKeyConstants.FIELD_COMMENT_TOTAL);
|
||||||
|
long count = Objects.isNull(commentTotal) ? 0L : commentTotal.longValue();
|
||||||
|
|
||||||
// 查询评论总数
|
// 若缓存不存在,则查询数据库
|
||||||
Long count = noteCountDOMapper.selectCommentTotalByNoteId(noteId);
|
if (Objects.isNull(commentTotal)) {
|
||||||
|
// 查询评论总数 (从 t_note_count 笔记计数表查,提升查询性能, 避免 count(*))
|
||||||
|
Long dbCount = noteCountDOMapper.selectCommentTotalByNoteId(noteId);
|
||||||
|
|
||||||
if (Objects.isNull(count)) {
|
// 若数据库中也不存在,则抛出业务异常
|
||||||
return PageResponse.success(null, pageNo, pageSize);
|
if (Objects.isNull(dbCount)) {
|
||||||
|
throw new ApiException(ResponseCodeEnum.COMMENT_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
count = dbCount;
|
||||||
|
// 异步将评论总数同步到 Redis 中
|
||||||
|
threadPoolTaskExecutor.execute(() ->
|
||||||
|
syncNoteCommentTotal2Redis(noteCommentTotalKey, dbCount)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若评论总数为 0,则直接响应
|
||||||
|
if (count == 0) {
|
||||||
|
return PageResponse.success(null, pageNo, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分页返回参数
|
// 分页返回参数
|
||||||
@@ -144,8 +174,82 @@ public class CommentServiceImpl extends ServiceImpl<CommentDOMapper, CommentDO>
|
|||||||
commentRspVOS = Lists.newArrayList();
|
commentRspVOS = Lists.newArrayList();
|
||||||
// 计算分页查询的offset
|
// 计算分页查询的offset
|
||||||
long offset = PageResponse.getOffset(pageNo, pageSize);
|
long offset = PageResponse.getOffset(pageNo, pageSize);
|
||||||
|
// 评论分页缓存使用 ZSET + STRING 实现
|
||||||
|
// 构建评论 ZSET Key
|
||||||
|
String commentZSetKey = RedisKeyConstants.buildCommentListKey(noteId);
|
||||||
|
// 先判断 ZSET 是否存在
|
||||||
|
boolean hasKey = redisTemplate.hasKey(commentZSetKey);
|
||||||
|
|
||||||
|
// 若不存在
|
||||||
|
if (!hasKey) {
|
||||||
|
// 异步将热点评论同步到 redis 中(最多同步 500 条)
|
||||||
|
threadPoolTaskExecutor.execute(() ->
|
||||||
|
syncHeatComments2Redis(commentZSetKey, noteId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若 ZSET 缓存存在, 并且查询的是前 50 页的评论
|
||||||
|
if (hasKey && offset < 500) {
|
||||||
|
// 使用 ZRevRange 获取某篇笔记下,按热度降序排序的一级评论 ID
|
||||||
|
Set<Object> commentIds = redisTemplate.opsForZSet()
|
||||||
|
.reverseRangeByScore(commentZSetKey, -Double.MAX_VALUE, Double.MAX_VALUE, offset, pageSize);
|
||||||
|
|
||||||
|
// 若结果不为空
|
||||||
|
if (CollUtil.isNotEmpty(commentIds)) {
|
||||||
|
// Set 转 List
|
||||||
|
List<Object> commentIdList = Lists.newArrayList(commentIds);
|
||||||
|
|
||||||
|
// 构建 MGET 批量查询评论详情的 Key 集合
|
||||||
|
List<String> commentIdKeys = commentIdList.stream()
|
||||||
|
.map(RedisKeyConstants::buildCommentDetailKey)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// MGET 批量获取评论数据
|
||||||
|
List<Object> commentsJsonList = redisTemplate.opsForValue().multiGet(commentIdKeys);
|
||||||
|
|
||||||
|
// 可能存在部分评论不在缓存中,已经过期被删除,这些评论 ID 需要提取出来,等会查数据库
|
||||||
|
List<Long> expiredCommentIds = Lists.newArrayList();
|
||||||
|
for (int i = 0; i < commentsJsonList.size(); i++) {
|
||||||
|
String commentJson = (String) commentsJsonList.get(i);
|
||||||
|
if (Objects.nonNull(commentJson)) {
|
||||||
|
// 缓存中存在的评论 Json,直接转换为 VO 添加到返参集合中
|
||||||
|
FindCommentItemRspVO commentRspVO = JsonUtils.parseObject(commentJson, FindCommentItemRspVO.class);
|
||||||
|
commentRspVOS.add(commentRspVO);
|
||||||
|
} else {
|
||||||
|
// 评论失效,添加到失效评论列表
|
||||||
|
expiredCommentIds.add(Long.valueOf(commentIdList.get(i).toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于不存在的一级评论,需要批量从数据库中查询,并添加到 commentRspVOS 中
|
||||||
|
if (CollUtil.isNotEmpty(expiredCommentIds)) {
|
||||||
|
List<CommentDO> commentDOS = commentDOMapper.selectByCommentIds(expiredCommentIds);
|
||||||
|
getCommentDataAndSync2Redis(commentDOS, noteId, commentRspVOS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按热度值进行降序排列
|
||||||
|
commentRspVOS = commentRspVOS.stream()
|
||||||
|
.sorted(Comparator.comparing(FindCommentItemRspVO::getHeat).reversed())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return PageResponse.success(commentRspVOS, pageNo, count, pageSize);
|
||||||
|
}
|
||||||
|
// 缓存中没有,则查询数据库
|
||||||
//查询一级评论
|
//查询一级评论
|
||||||
List<CommentDO> oneLevelCommentIds = commentDOMapper.selectPageList(noteId, offset, pageSize);
|
List<CommentDO> oneLevelCommentIds = commentDOMapper.selectPageList(noteId, offset, pageSize);
|
||||||
|
getCommentDataAndSync2Redis(oneLevelCommentIds, noteId, commentRspVOS);
|
||||||
|
}
|
||||||
|
return PageResponse.success(commentRspVOS, pageNo, count, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一级评论数据,并同步到 Redis 中
|
||||||
|
*
|
||||||
|
* @param oneLevelCommentIds 一级评论ID列表
|
||||||
|
* @param noteId 笔记ID
|
||||||
|
* @param commentRspVOS 一级评论返回参数
|
||||||
|
*/
|
||||||
|
private void getCommentDataAndSync2Redis(List<CommentDO> oneLevelCommentIds, Long noteId, List<FindCommentItemRspVO> commentRspVOS) {
|
||||||
// 过滤出所有最早回复的二级评论ID
|
// 过滤出所有最早回复的二级评论ID
|
||||||
List<Long> twoLevelCommentIds = oneLevelCommentIds.stream()
|
List<Long> twoLevelCommentIds = oneLevelCommentIds.stream()
|
||||||
.map(CommentDO::getFirstReplyCommentId)
|
.map(CommentDO::getFirstReplyCommentId)
|
||||||
@@ -216,6 +320,7 @@ public class CommentServiceImpl extends ServiceImpl<CommentDOMapper, CommentDO>
|
|||||||
.createTime(DateUtils.formatRelativeTime(commentDO.getCreateTime()))
|
.createTime(DateUtils.formatRelativeTime(commentDO.getCreateTime()))
|
||||||
.likeTotal(commentDO.getLikeTotal())
|
.likeTotal(commentDO.getLikeTotal())
|
||||||
.childCommentTotal(commentDO.getChildCommentTotal())
|
.childCommentTotal(commentDO.getChildCommentTotal())
|
||||||
|
.heat(commentDO.getHeat())
|
||||||
.build();
|
.build();
|
||||||
// 用户信息
|
// 用户信息
|
||||||
if (userIdAndDTOMap != null) {
|
if (userIdAndDTOMap != null) {
|
||||||
@@ -236,6 +341,7 @@ public class CommentServiceImpl extends ServiceImpl<CommentDOMapper, CommentDO>
|
|||||||
.imageUrl(firstReplyCommentDO.getImageUrl())
|
.imageUrl(firstReplyCommentDO.getImageUrl())
|
||||||
.createTime(DateUtils.formatRelativeTime(firstReplyCommentDO.getCreateTime()))
|
.createTime(DateUtils.formatRelativeTime(firstReplyCommentDO.getCreateTime()))
|
||||||
.likeTotal(firstReplyCommentDO.getLikeTotal())
|
.likeTotal(firstReplyCommentDO.getLikeTotal())
|
||||||
|
.heat(firstReplyCommentDO.getHeat())
|
||||||
.build();
|
.build();
|
||||||
if (userIdAndDTOMap != null) {
|
if (userIdAndDTOMap != null) {
|
||||||
setUserInfo(userIdAndDTOMap, firstReplyCommentUserId, firstReplyCommentRspVO);
|
setUserInfo(userIdAndDTOMap, firstReplyCommentUserId, firstReplyCommentRspVO);
|
||||||
@@ -249,8 +355,86 @@ public class CommentServiceImpl extends ServiceImpl<CommentDOMapper, CommentDO>
|
|||||||
}
|
}
|
||||||
commentRspVOS.add(oneLevelCommentRspVO);
|
commentRspVOS.add(oneLevelCommentRspVO);
|
||||||
}
|
}
|
||||||
// TODO 后续逻辑
|
// 异步将笔记详情,同步到 Redis 中
|
||||||
|
threadPoolTaskExecutor.execute(() -> {
|
||||||
|
// 准备批量写入的数据
|
||||||
|
Map<String, String> data = Maps.newHashMap();
|
||||||
|
commentRspVOS.forEach(commentRspVO -> {
|
||||||
|
// 评论 ID
|
||||||
|
Long commentId = commentRspVO.getCommentId();
|
||||||
|
// 构建 Key
|
||||||
|
String key = RedisKeyConstants.buildCommentDetailKey(commentId);
|
||||||
|
data.put(key, JsonUtils.toJsonString(commentRspVO));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 使用 Redis Pipeline 提升写入性能
|
||||||
|
redisTemplate.executePipelined((RedisCallback<?>) (connection) -> {
|
||||||
|
for (Map.Entry<String, String> entry : data.entrySet()) {
|
||||||
|
// 将 Java 对象序列化为 JSON 字符串
|
||||||
|
String jsonStr = JsonUtils.toJsonString(entry.getValue());
|
||||||
|
|
||||||
|
// 随机生成过期时间 (5小时以内)
|
||||||
|
int randomExpire = RandomUtil.randomInt(5 * 60 * 60);
|
||||||
|
|
||||||
|
|
||||||
|
// 批量写入并设置过期时间
|
||||||
|
connection.setEx(
|
||||||
|
redisTemplate.getStringSerializer().serialize(entry.getKey()),
|
||||||
|
randomExpire,
|
||||||
|
redisTemplate.getStringSerializer().serialize(jsonStr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步笔记评论总数到 Redis 中
|
||||||
|
*
|
||||||
|
* @param noteCommentTotalKey 笔记评论总数 Redis key
|
||||||
|
* @param dbCount 数据库查询到的笔记评论总数
|
||||||
|
*/
|
||||||
|
private void syncNoteCommentTotal2Redis(String noteCommentTotalKey, Long dbCount) {
|
||||||
|
redisTemplate.executePipelined(new SessionCallback<>() {
|
||||||
|
@Override
|
||||||
|
public Object execute(RedisOperations operations) {
|
||||||
|
// 同步 hash 数据
|
||||||
|
operations.opsForHash().put(noteCommentTotalKey, RedisKeyConstants.FIELD_COMMENT_TOTAL, dbCount);
|
||||||
|
|
||||||
|
// 随机过期时间 (保底1小时 + 随机时间),单位:秒
|
||||||
|
long expireTime = 60 * 60 + RandomUtil.randomInt(4 * 60 * 60);
|
||||||
|
operations.expire(noteCommentTotalKey, expireTime, TimeUnit.SECONDS);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步热点评论至 Redis
|
||||||
|
*
|
||||||
|
* @param key 热门评论 Redis key
|
||||||
|
* @param noteId 笔记 ID
|
||||||
|
*/
|
||||||
|
private void syncHeatComments2Redis(String key, Long noteId) {
|
||||||
|
List<CommentDO> commentDOS = commentDOMapper.selectHeatComments(noteId);
|
||||||
|
if (CollUtil.isNotEmpty(commentDOS)) {
|
||||||
|
// 使用 Redis Pipeline 提升写入性能
|
||||||
|
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
|
||||||
|
ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
|
||||||
|
|
||||||
|
// 遍历评论数据并批量写入 ZSet
|
||||||
|
for (CommentDO commentDO : commentDOS) {
|
||||||
|
Long commentId = commentDO.getId();
|
||||||
|
Double commentHeat = commentDO.getHeat();
|
||||||
|
zSetOps.add(key, commentId, commentHeat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置随机过期时间,单位:秒
|
||||||
|
int randomExpiryTime = RandomUtil.randomInt(5 * 60 * 60); // 5小时以内
|
||||||
|
redisTemplate.expire(key, randomExpiryTime, TimeUnit.SECONDS);
|
||||||
|
return null; // 无返回值
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return PageResponse.success(commentRspVOS, pageNo, count, pageSize);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
|
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
|
||||||
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
|
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
|
||||||
<result column="child_comment_total" jdbcType="BIGINT" property="childCommentTotal"/>
|
<result column="child_comment_total" jdbcType="BIGINT" property="childCommentTotal"/>
|
||||||
<result column="heat" jdbcType="DECIMAL" property="heat"/>
|
<result column="heat" jdbcType="DOUBLE" property="heat"/>
|
||||||
<result column="first_reply_comment_id" jdbcType="BIGINT" property="firstReplyCommentId"/>
|
<result column="first_reply_comment_id" jdbcType="BIGINT" property="firstReplyCommentId"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
<sql id="Base_Column_List">
|
<sql id="Base_Column_List">
|
||||||
@@ -45,21 +45,28 @@
|
|||||||
first_reply_comment_id
|
first_reply_comment_id
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="selectByCommentIds" parameterType="list" resultMap="BaseResultMap">
|
<select id="selectByCommentIds" resultMap="BaseResultMap" parameterType="list">
|
||||||
select id,
|
select id,
|
||||||
|
user_id,
|
||||||
|
content_uuid,
|
||||||
|
is_content_empty,
|
||||||
|
image_url,
|
||||||
|
like_total,
|
||||||
|
is_top,
|
||||||
|
create_time,
|
||||||
|
first_reply_comment_id,
|
||||||
|
child_comment_total,
|
||||||
level,
|
level,
|
||||||
parent_id,
|
parent_id,
|
||||||
user_id,
|
heat
|
||||||
child_comment_total,
|
|
||||||
like_total,
|
|
||||||
first_reply_comment_id
|
|
||||||
from t_comment
|
from t_comment
|
||||||
where id in
|
where id in
|
||||||
<foreach close=")" collection="commentIds" item="commentId" open="(" separator=",">
|
<foreach collection="commentIds" open="(" separator="," close=")" item="commentId">
|
||||||
#{commentId}
|
#{commentId}
|
||||||
</foreach>
|
</foreach>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
||||||
<insert id="batchInsert" parameterType="list">
|
<insert id="batchInsert" parameterType="list">
|
||||||
insert IGNORE into t_comment (id, note_id, user_id,
|
insert IGNORE into t_comment (id, note_id, user_id,
|
||||||
content_uuid, is_content_empty, image_url,
|
content_uuid, is_content_empty, image_url,
|
||||||
@@ -113,7 +120,8 @@
|
|||||||
is_top,
|
is_top,
|
||||||
create_time,
|
create_time,
|
||||||
first_reply_comment_id,
|
first_reply_comment_id,
|
||||||
child_comment_total
|
child_comment_total,
|
||||||
|
heat
|
||||||
from t_comment
|
from t_comment
|
||||||
where note_id = #{noteId}
|
where note_id = #{noteId}
|
||||||
and level = 1
|
and level = 1
|
||||||
@@ -128,11 +136,21 @@
|
|||||||
is_content_empty,
|
is_content_empty,
|
||||||
image_url,
|
image_url,
|
||||||
like_total,
|
like_total,
|
||||||
create_time
|
create_time,
|
||||||
|
heat
|
||||||
from t_comment
|
from t_comment
|
||||||
where id in
|
where id in
|
||||||
<foreach collection="commentIds" open="(" separator="," close=")" item="commentId">
|
<foreach collection="commentIds" open="(" separator="," close=")" item="commentId">
|
||||||
#{commentId}
|
#{commentId}
|
||||||
</foreach>
|
</foreach>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectHeatComments" resultMap="BaseResultMap">
|
||||||
|
select id, heat
|
||||||
|
from t_comment
|
||||||
|
where note_id = #{noteId}
|
||||||
|
and level = 1
|
||||||
|
order by heat desc
|
||||||
|
limit 500
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
Reference in New Issue
Block a user