From 8be6719be8427c40b6ee230ce7f9e9ea88676ecf Mon Sep 17 00:00:00 2001 From: Hanserwei <2628273921@qq.com> Date: Sat, 8 Nov 2025 21:58:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(comment):=20=E5=AE=9E=E7=8E=B0=E5=AD=90?= =?UTF-8?q?=E8=AF=84=E8=AE=BA=E5=88=86=E9=A1=B5=E6=9F=A5=E8=AF=A2=E4=B8=8E?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增根据父评论 ID 和限制数量查询子评论的数据库方法 - 实现子评论分页缓存机制,使用 ZSET + String 结构提升查询性能 - 添加子评论详情批量同步到 Redis 的功能 -优化子评论计数数据的缓存读取与数据库同步逻辑 - 新增父评论不存在时的业务异常处理 - 完善子评论缓存失效后的数据库查询与数据回填机制 - 提取公共方法用于批量操作 Redis 数据,提升代码复用性 --- .../biz/constants/RedisKeyConstants.java | 15 + .../biz/domain/mapper/CommentDOMapper.java | 10 + .../comment/biz/enums/ResponseCodeEnum.java | 1 + .../biz/service/impl/CommentServiceImpl.java | 440 +++++++++++++----- .../resources/mapperxml/CommentDOMapper.xml | 9 + 5 files changed, 366 insertions(+), 109 deletions(-) diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/constants/RedisKeyConstants.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/constants/RedisKeyConstants.java index fce6f49..c9a2de0 100644 --- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/constants/RedisKeyConstants.java +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/constants/RedisKeyConstants.java @@ -7,6 +7,11 @@ public class RedisKeyConstants { */ private static final String HAVE_FIRST_REPLY_COMMENT_KEY_PREFIX = "comment:havaFirstReplyCommentId:"; + /** + * Key 前缀:二级评论分页 ZSET + */ + private static final String CHILD_COMMENT_LIST_KEY_PREFIX = "comment:childList:"; + /** * Hash Field: 子评论总数 */ @@ -40,6 +45,16 @@ public class RedisKeyConstants { */ private static final String COMMENT_DETAIL_KEY_PREFIX = "comment:detail:"; + /** + * 构建子评论分页 ZSET 完整 KEY + * + * @param commentId 一级评论 ID + * @return 子评论分页 ZSET 完整 KEY + */ + public static String buildChildCommentListKey(Long commentId) { + return CHILD_COMMENT_LIST_KEY_PREFIX + commentId; + } + /** * 构建评论维度计数 Key * diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/domain/mapper/CommentDOMapper.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/domain/mapper/CommentDOMapper.java index 1298764..de24c78 100644 --- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/domain/mapper/CommentDOMapper.java +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/domain/mapper/CommentDOMapper.java @@ -110,4 +110,14 @@ public interface CommentDOMapper extends BaseMapper { * @return 计数数据 */ List selectCommentCountByIds(@Param("commentIds") List commentIds); + + /** + * 查询子评论 + * + * @param parentId 一级评论 ID + * @param limit 子评论数量限制 + * @return 子评论 + */ + List selectChildCommentsByParentIdAndLimit(@Param("parentId") Long parentId, + @Param("limit") int limit); } \ No newline at end of file diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/enums/ResponseCodeEnum.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/enums/ResponseCodeEnum.java index d042c59..76c35fb 100644 --- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/enums/ResponseCodeEnum.java +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/enums/ResponseCodeEnum.java @@ -14,6 +14,7 @@ public enum ResponseCodeEnum implements BaseExceptionInterface { // ----------- 业务异常状态码 ----------- COMMENT_NOT_FOUND("COMMENT-20001", "此评论不存在"), + PARENT_COMMENT_NOT_FOUND("COMMENT-20000", "此父评论不存在"), ; // 异常码 diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/service/impl/CommentServiceImpl.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/service/impl/CommentServiceImpl.java index 5c1a68f..39cf8db 100644 --- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/service/impl/CommentServiceImpl.java +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/service/impl/CommentServiceImpl.java @@ -38,6 +38,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.util.Strings; import org.jspecify.annotations.NonNull; +import org.springframework.dao.DataAccessException; import org.springframework.data.redis.core.*; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; @@ -311,13 +312,30 @@ public class CommentServiceImpl extends ServiceImpl // 每页展示的二级评论数 (小红书 APP 中是一次查询 6 条) long pageSize = 6; - // TODO:先从缓存中查询,待完善 + // 先从缓存中查询 + String countCommentKey = RedisKeyConstants.buildCountCommentKey(parentCommentId); + // 子评论总数 + Number redisCount = (Number) redisTemplate.opsForHash() + .get(countCommentKey, RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL); + long count = Objects.isNull(redisCount) ? 0L : redisCount.longValue(); - // 查询一级评论下的子评论数目 - Long count = commentDOMapper.selectChildCommentTotalById(parentCommentId); + // 若缓存不存在,走数据库查询 + if (Objects.isNull(redisCount)) { + // 查询一级评论下子评论的总数 (直接查询 t_comment 表的 child_comment_total 字段,提升查询性能, 避免 count(*)) + Long dbCount = commentDOMapper.selectChildCommentTotalById(parentCommentId); - // 若一级评论不存在或者评论数目为0,则直接返回 - if (Objects.isNull(count) || count == 0) { + // 若数据库中也不存在,则抛出业务异常 + if (Objects.isNull(dbCount)) { + throw new ApiException(ResponseCodeEnum.PARENT_COMMENT_NOT_FOUND); + } + + count = dbCount; + // 异步将子评论总数同步到 Redis 中 + threadPoolTaskExecutor.execute(() -> syncCommentCount2Redis(countCommentKey, dbCount)); + } + + // 若子评论总数为 0,直接返参 + if (count == 0) { return PageResponse.success(null, pageNo, 0); } @@ -327,9 +345,217 @@ public class CommentServiceImpl extends ServiceImpl // 计算分页查询的偏移量offset(需要加1,因为最早回复的二级评论已经被展示了) long offset = PageResponse.getOffset(pageNo, pageSize) + 1; + // 子评论分页缓存使用Zset+String实现 + // 构建子评论ZSETKey + String childCommentZsetKey = RedisKeyConstants.buildChildCommentListKey(parentCommentId); + // 先判断ZSET是否存在 + boolean hasKey = redisTemplate.hasKey(childCommentZsetKey); + // 若不存在 + if (!hasKey) { + // 异步将子评论同步到 Redis 中(最多同步 6*10 条) + threadPoolTaskExecutor.execute(() -> syncChildComments2Redis(parentCommentId, childCommentZsetKey)); + } + + // 若子评论 ZSET 缓存存在, 并且查询的是前 10 页的子评论 + if (hasKey && offset < 6 * 10) { + // 使用ZRevRange获取某个一级评论下的子评论,按回复时间升序排序 + Set childCommentIds = redisTemplate + .opsForZSet().rangeByScore(childCommentZsetKey, 0, Double.MAX_VALUE, offset, pageSize); + + // 若结果不为空 + if (CollUtil.isNotEmpty(childCommentIds)) { + // Set转List + List childCommentIdList = Lists.newArrayList(childCommentIds); + // 构建 MGET 批量查询子评论详情的 Key 集合 + List commentIdKeys = childCommentIds.stream() + .map(RedisKeyConstants::buildCommentDetailKey) + .toList(); + + // MGET 批量获取评论数据 + List commentsJsonList = redisTemplate.opsForValue().multiGet(commentIdKeys); + + // 可能存在部分评论不在缓存中,已经过期被删除,这些评论 ID 需要提取出来,等会查数据库 + List expiredChildCommentIds = Lists.newArrayList(); + + if (commentsJsonList != null) { + for (int i = 0; i < commentsJsonList.size(); i++) { + String commentJson = (String) commentsJsonList.get(i); + Long commentId = Long.valueOf(childCommentIdList.get(i).toString()); + if (Objects.nonNull(commentJson)) { + // 缓存中存在的评论 Json,直接转换为 VO 添加到返参集合中 + FindChildCommentItemRspVO childCommentRspVO = JsonUtils.parseObject(commentJson, FindChildCommentItemRspVO.class); + childCommentRspVOS.add(childCommentRspVO); + } else { + // 评论失效,添加到失效评论列表 + expiredChildCommentIds.add(commentId); + } + } + } + + // 对于缓存中存在的子评论, 需要再次查询 Hash, 获取其计数数据 + if (CollUtil.isNotEmpty(childCommentRspVOS)) { + setChildCommentCountData(childCommentRspVOS, expiredChildCommentIds); + } + + // 对于不存在的子评论,需要批量从数据库中查询,并添加到 commentRspVOS 中 + if (CollUtil.isNotEmpty(expiredChildCommentIds)) { + List commentDOS = commentDOMapper.selectByCommentIds(expiredChildCommentIds); + getChildCommentDataAndSync2Redis(commentDOS, childCommentRspVOS); + } + + // 按评论 ID 升序排列(等同于按回复时间升序) + childCommentRspVOS = childCommentRspVOS.stream() + .sorted(Comparator.comparing(FindChildCommentItemRspVO::getCommentId)) + .collect(Collectors.toList()); + + return PageResponse.success(childCommentRspVOS, pageNo, count, pageSize); + } + } + // 分页查询子评论 List childCommentDOS = commentDOMapper.selectChildPageList(parentCommentId, offset, pageSize); + getChildCommentDataAndSync2Redis(childCommentDOS, childCommentRspVOS); + + return PageResponse.success(childCommentRspVOS, pageNo, count, pageSize); + } + + /** + * 设置子评论计数数据 + * + * @param commentRspVOS 子评论VO + * @param expiredCommentIds 失效的子评论ID + */ + private void setChildCommentCountData(List commentRspVOS, List expiredCommentIds) { + // 准备从评论 Hash 中查询计数 (被点赞数) + // 缓存中存在的子评论 ID + List notExpiredCommentIds = Lists.newArrayList(); + + // 遍历从缓存中解析出的 VO 集合,提取二级评论 ID + commentRspVOS.forEach(commentRspVO -> { + Long childCommentId = commentRspVO.getCommentId(); + notExpiredCommentIds.add(childCommentId); + }); + + // 从 Redis 中查询评论计数 Hash 数据 + Map> commentIdAndCountMap = getCommentCountDataAndSync2RedisHash(notExpiredCommentIds); + + // 遍历 VO, 设置对应子评论的点赞数 + for (FindChildCommentItemRspVO commentRspVO : commentRspVOS) { + // 评论 ID + Long commentId = commentRspVO.getCommentId(); + + // 若当前这条评论是从数据库中查询出来的, 则无需设置点赞数,以数据库查询出来的为主 + if (CollUtil.isNotEmpty(expiredCommentIds) + && expiredCommentIds.contains(commentId)) { + continue; + } + + // 设置子评论的点赞数 + Map hash = commentIdAndCountMap.get(commentId); + if (CollUtil.isNotEmpty(hash)) { + Long likeTotal = Long.valueOf(hash.get(RedisKeyConstants.FIELD_LIKE_TOTAL).toString()); + commentRspVO.setLikeTotal(likeTotal); + } + } + } + + /** + * 获取评论计数数据,并同步到 Redis 中 + * + * @param notExpiredCommentIds 缓存中存在的评论 ID + * @return 评论计数数据 + */ + @SuppressWarnings("unchecked") + private Map> getCommentCountDataAndSync2RedisHash(List notExpiredCommentIds) { + // 已失效的 Hash 评论 ID + List expiredCountCommentIds = Lists.newArrayList(); + // 构建需要查询的 Hash Key 集合 + List commentCountKeys = notExpiredCommentIds.stream() + .map(RedisKeyConstants::buildCountCommentKey).toList(); + + // 使用 RedisTemplate 执行管道批量操作 + List results = redisTemplate.executePipelined(new SessionCallback<>() { + @Override + public Object execute(@NonNull RedisOperations operations) { + // 遍历需要查询的评论计数的 Hash 键集合 + commentCountKeys.forEach(key -> + // 在管道中执行 Redis 的 hash.entries 操作 + // 此操作会获取指定 Hash 键中所有的字段和值 + operations.opsForHash().entries(key)); + return null; + } + }); + + // 评论 ID - 计数数据字典 + Map> commentIdAndCountMap = Maps.newHashMap(); + // 遍历未过期的评论 ID 集合 + for (int i = 0; i < notExpiredCommentIds.size(); i++) { + // 当前评论 ID + Long currCommentId = Long.valueOf(notExpiredCommentIds.get(i).toString()); + // 从缓存查询结果中,获取对应 Hash + Map hash = (Map) results.get(i); + // 若 Hash 结果为空,说明缓存中不存在,添加到 expiredCountCommentIds 中,保存一下 + if (CollUtil.isEmpty(hash)) { + expiredCountCommentIds.add(currCommentId); + continue; + } + // 若存在,则将数据添加到 commentIdAndCountMap 中,方便后续读取 + commentIdAndCountMap.put(currCommentId, hash); + } + + // 若已过期的计数评论 ID 集合大于 0,说明部分计数数据不在 Redis 缓存中 + // 需要查询数据库,并将这部分的评论计数 Hash 同步到 Redis 中 + if (CollUtil.size(expiredCountCommentIds) > 0) { + // 查询数据库 + List commentDOS = commentDOMapper.selectCommentCountByIds(expiredCountCommentIds); + + commentDOS.forEach(commentDO -> { + Integer level = commentDO.getLevel(); + Map map = Maps.newHashMap(); + map.put(RedisKeyConstants.FIELD_LIKE_TOTAL, commentDO.getLikeTotal()); + // 只有一级评论需要统计子评论总数 + if (Objects.equals(level, CommentLevelEnum.ONE.getCode())) { + map.put(RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL, commentDO.getChildCommentTotal()); + } + // 统一添加到 commentIdAndCountMap 字典中,方便后续查询 + commentIdAndCountMap.put(commentDO.getId(), map); + }); + + // 异步同步到 Redis 中 + threadPoolTaskExecutor.execute(() -> redisTemplate.executePipelined(new SessionCallback<>() { + @Override + public Object execute(@NonNull RedisOperations operations) { + commentDOS.forEach(commentDO -> { + // 构建 Hash Key + String key = RedisKeyConstants.buildCountCommentKey(commentDO.getId()); + // 评论级别 + Integer level = commentDO.getLevel(); + // 设置 Field 数据 + Map fieldsMap = Objects.equals(level, CommentLevelEnum.ONE.getCode()) ? + Map.of(RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL, commentDO.getChildCommentTotal(), + RedisKeyConstants.FIELD_LIKE_TOTAL, commentDO.getLikeTotal()) : Map.of(RedisKeyConstants.FIELD_LIKE_TOTAL, commentDO.getLikeTotal()); + // 添加 Hash 数据 + operations.opsForHash().putAll(key, fieldsMap); + + // 设置随机过期时间 (5小时以内) + long expireTime = 60 * 60 + RandomUtil.randomInt(4 * 60 * 60); + operations.expire(key, expireTime, TimeUnit.SECONDS); + }); + return null; + } + })); + } + return commentIdAndCountMap; + } + + /** + * 获取子评论数据 + * + * @param childCommentDOS 子评论DO + * @param childCommentRspVOS 子评论VO + */ + private void getChildCommentDataAndSync2Redis(List childCommentDOS, List childCommentRspVOS) { // 调用KV服务需要的入参 List findCommentContentReqDTOS = Lists.newArrayList(); // 调用用户服务需要的入参 @@ -426,7 +652,99 @@ public class CommentServiceImpl extends ServiceImpl childCommentRspVOS.add(childCommentRspVO); } - return PageResponse.success(childCommentRspVOS, pageNo, count, pageSize); + // 异步将笔记详情,同步到 Redis 中 + threadPoolTaskExecutor.execute(() -> { + // 准备批量写入的数据 + Map data = Maps.newHashMap(); + childCommentRspVOS.forEach(commentRspVO -> { + // 评论 ID + Long commentId = commentRspVO.getCommentId(); + // 构建 Key + String key = RedisKeyConstants.buildCommentDetailKey(commentId); + data.put(key, JsonUtils.toJsonString(commentRspVO)); + }); + + batchAddCommentDetailJson2Redis(data); + }); + } + + /** + * 批量添加评论详情 Json 到 Redis 中 + * + * @param data 批量写入的数据 + */ + private void batchAddCommentDetailJson2Redis(Map data) { + // 使用 Redis Pipeline 提升写入性能 + redisTemplate.executePipelined((RedisCallback) (connection) -> { + for (Map.Entry entry : data.entrySet()) { + // 将 Java 对象序列化为 JSON 字符串 + String jsonStr = JsonUtils.toJsonString(entry.getValue()); + + // 随机生成过期时间 (5小时以内) + int randomExpire = 60 * 60 + RandomUtil.randomInt(4 * 60 * 60); + + // 批量写入并设置过期时间 + connection.stringCommands().setEx( + Objects.requireNonNull(redisTemplate.getStringSerializer().serialize(entry.getKey())), + randomExpire, + Objects.requireNonNull(redisTemplate.getStringSerializer().serialize(jsonStr)) + ); + } + return null; + }); + } + + /** + * 同步子评论到 Redis 中 + * + * @param parentCommentId 父评论ID + * @param childCommentZSetKey 子评论ZSet Key + */ + private void syncChildComments2Redis(Long parentCommentId, String childCommentZSetKey) { + List childCommentDOS = commentDOMapper.selectChildCommentsByParentIdAndLimit(parentCommentId, 6 * 10); + if (CollUtil.isNotEmpty(childCommentDOS)) { + // 使用 Redis Pipeline 提升写入性能 + redisTemplate.executePipelined((RedisCallback) connection -> { + ZSetOperations zSetOps = redisTemplate.opsForZSet(); + + // 遍历子评论数据并批量写入 ZSet + for (CommentDO childCommentDO : childCommentDOS) { + Long commentId = childCommentDO.getId(); + // create_time 转时间戳 + long commentTimestamp = DateUtils.localDateTime2Timestamp(childCommentDO.getCreateTime()); + zSetOps.add(childCommentZSetKey, commentId, commentTimestamp); + } + + // 设置随机过期时间,(保底1小时 + 随机时间),单位:秒 + int randomExpiryTime = 60 * 60 + RandomUtil.randomInt(4 * 60 * 60); // 5小时以内 + redisTemplate.expire(childCommentZSetKey, randomExpiryTime, TimeUnit.SECONDS); + return null; // 无返回值 + }); + } + } + + /** + * 同步评论数量到 Redis 中 + * + * @param countCommentKey 评论数量缓存的键值 + * @param dbCount 数据库中的评论数量 + */ + @SuppressWarnings("unchecked") + private void syncCommentCount2Redis(String countCommentKey, Long dbCount) { + redisTemplate.executePipelined(new SessionCallback() { + @Override + public Void execute(@NonNull RedisOperations operations) throws DataAccessException { + // 这里用强制类型转换指定类型 + RedisOperations ops = (RedisOperations) operations; + + ops.opsForHash() + .put(countCommentKey, RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL, dbCount); + + long expireTime = 60 * 60 + RandomUtil.randomInt(4 * 60 * 60); + ops.expire(countCommentKey, expireTime, TimeUnit.SECONDS); + return null; + } + }); } /** @@ -574,25 +892,7 @@ public class CommentServiceImpl extends ServiceImpl data.put(key, JsonUtils.toJsonString(commentRspVO)); }); - // 使用 Redis Pipeline 提升写入性能 - redisTemplate.executePipelined((RedisCallback) (connection) -> { - for (Map.Entry entry : data.entrySet()) { - // 将 Java 对象序列化为 JSON 字符串 - String jsonStr = JsonUtils.toJsonString(entry.getValue()); - - // 随机生成过期时间 (5小时以内) - int randomExpire = RandomUtil.randomInt(5 * 60 * 60); - - - // 批量写入并设置过期时间 - connection.stringCommands().setEx( - Objects.requireNonNull(redisTemplate.getStringSerializer().serialize(entry.getKey())), - randomExpire, - Objects.requireNonNull(redisTemplate.getStringSerializer().serialize(jsonStr)) - ); - } - return null; - }); + batchAddCommentDetailJson2Redis(data); }); } @@ -642,7 +942,7 @@ public class CommentServiceImpl extends ServiceImpl /** * 设置评论 VO 的计数 * - * @param commentRspVOS 返参 VO 集合 + * @param commentRspVOS 返参 VO 集合 * @param expiredCommentIds 缓存中已失效的评论 ID 集合 */ private void setCommentCountData(List commentRspVOS, @@ -662,85 +962,7 @@ public class CommentServiceImpl extends ServiceImpl }); // 已失效的 Hash 评论 ID - List expiredCountCommentIds = Lists.newArrayList(); - // 构建需要查询的 Hash Key 集合 - List commentCountKeys = notExpiredCommentIds.stream() - .map(RedisKeyConstants::buildCountCommentKey).toList(); - - // 使用 RedisTemplate 执行管道批量操作 - List results = redisTemplate.executePipelined(new SessionCallback<>() { - @Override - public Object execute(@NonNull RedisOperations operations) { - // 遍历需要查询的评论计数的 Hash 键集合 - commentCountKeys.forEach(key -> - // 在管道中执行 Redis 的 hash.entries 操作 - // 此操作会获取指定 Hash 键中所有的字段和值 - operations.opsForHash().entries(key)); - return null; - } - }); - - // 评论 ID - 计数数据字典 - Map> commentIdAndCountMap = Maps.newHashMap(); - // 遍历未过期的评论 ID 集合 - for (int i = 0; i < notExpiredCommentIds.size(); i++) { - // 当前评论 ID - Long currCommentId = Long.valueOf(notExpiredCommentIds.get(i).toString()); - // 从缓存查询结果中,获取对应 Hash - Map hash = (Map) results.get(i); - // 若 Hash 结果为空,说明缓存中不存在,添加到 expiredCountCommentIds 中,保存一下 - if (CollUtil.isEmpty(hash)) { - expiredCountCommentIds.add(currCommentId); - continue; - } - // 若存在,则将数据添加到 commentIdAndCountMap 中,方便后续读取 - commentIdAndCountMap.put(currCommentId, hash); - } - - // 若已过期的计数评论 ID 集合大于 0,说明部分计数数据不在 Redis 缓存中 - // 需要查询数据库,并将这部分的评论计数 Hash 同步到 Redis 中 - if (CollUtil.size(expiredCountCommentIds) > 0) { - // 查询数据库 - List commentDOS = commentDOMapper.selectCommentCountByIds(expiredCountCommentIds); - - commentDOS.forEach(commentDO -> { - Integer level = commentDO.getLevel(); - Map map = Maps.newHashMap(); - map.put(RedisKeyConstants.FIELD_LIKE_TOTAL, commentDO.getLikeTotal()); - // 只有一级评论需要统计子评论总数 - if (Objects.equals(level, CommentLevelEnum.ONE.getCode())) { - map.put(RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL, commentDO.getChildCommentTotal()); - } - // 统一添加到 commentIdAndCountMap 字典中,方便后续查询 - commentIdAndCountMap.put(commentDO.getId(), map); - }); - - // 异步同步到 Redis 中 - threadPoolTaskExecutor.execute(() -> { - redisTemplate.executePipelined(new SessionCallback<>() { - @Override - public Object execute(RedisOperations operations) { - commentDOS.forEach(commentDO -> { - // 构建 Hash Key - String key = RedisKeyConstants.buildCountCommentKey(commentDO.getId()); - // 评论级别 - Integer level = commentDO.getLevel(); - // 设置 Field 数据 - Map fieldsMap = Objects.equals(level, CommentLevelEnum.ONE.getCode()) ? - Map.of(RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL, commentDO.getChildCommentTotal(), - RedisKeyConstants.FIELD_LIKE_TOTAL, commentDO.getLikeTotal()) : Map.of(RedisKeyConstants.FIELD_LIKE_TOTAL, commentDO.getLikeTotal()); - // 添加 Hash 数据 - operations.opsForHash().putAll(key, fieldsMap); - - // 设置随机过期时间 (5小时以内) - long expireTime = RandomUtil.randomInt(5 * 60 * 60); - operations.expire(key, expireTime, TimeUnit.SECONDS); - }); - return null; - } - }); - }); - } + Map> commentIdAndCountMap = getCommentCountDataAndSync2RedisHash(notExpiredCommentIds); // 遍历 VO, 设置对应评论的二级评论数、点赞数 for (FindCommentItemRspVO commentRspVO : commentRspVOS) { @@ -756,10 +978,9 @@ public class CommentServiceImpl extends ServiceImpl // 设置一级评论的子评论总数、点赞数 Map hash = commentIdAndCountMap.get(commentId); if (CollUtil.isNotEmpty(hash)) { - Object childCommentTotalObj = hash.get(RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL); - Long childCommentTotal = Objects.isNull(childCommentTotalObj) ? 0 : Long.parseLong(childCommentTotalObj.toString()); - Object likeTotalObj = hash.get(RedisKeyConstants.FIELD_LIKE_TOTAL); - Long likeTotal = Objects.isNull(likeTotalObj) ? 0 : Long.parseLong(likeTotalObj.toString()); + Object likeTotalObj = hash.get(RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL); + Long childCommentTotal = Objects.isNull(likeTotalObj) ? 0 : Long.parseLong(likeTotalObj.toString()); + Long likeTotal = Long.valueOf(hash.get(RedisKeyConstants.FIELD_LIKE_TOTAL).toString()); commentRspVO.setChildCommentTotal(childCommentTotal); commentRspVO.setLikeTotal(likeTotal); // 最初回复的二级评论 @@ -776,4 +997,5 @@ public class CommentServiceImpl extends ServiceImpl } } + } diff --git a/han-note-comment/han-note-comment-biz/src/main/resources/mapperxml/CommentDOMapper.xml b/han-note-comment/han-note-comment-biz/src/main/resources/mapperxml/CommentDOMapper.xml index 55a61f4..943e5a1 100644 --- a/han-note-comment/han-note-comment-biz/src/main/resources/mapperxml/CommentDOMapper.xml +++ b/han-note-comment/han-note-comment-biz/src/main/resources/mapperxml/CommentDOMapper.xml @@ -192,4 +192,13 @@ #{commentId} + + \ No newline at end of file