From 6985431236ba75ab92339fcd3f4070b3d7f9cc34 Mon Sep 17 00:00:00 2001 From: Hanserwei <2628273921@qq.com> Date: Sun, 9 Nov 2025 15:16:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(comment):=20=E6=96=B0=E5=A2=9E=E8=AF=84?= =?UTF-8?q?=E8=AE=BA=E5=88=A0=E9=99=A4=E5=8F=8A=E7=BC=93=E5=AD=98=E6=B8=85?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增删除一级评论及其子评论的接口与实现- 新增批量删除评论的功能支持 - 新增根据回复评论ID查询评论的方法 - 为CommentLevelEnum添加通过code获取枚举值的方法 - 实现评论本地缓存删除服务接口 - 新增删除评论后的MQ消费者处理逻辑 - 新增删除评论本地缓存的MQ广播消费逻辑 - 扩展NoteCountDOMapper以支持评论总数更新操作 --- .../biz/consumer/DeleteCommentConsumer.java | 192 ++++++++++++++++++ .../DeleteCommentLocalCacheConsumer.java | 29 +++ .../biz/domain/mapper/CommentDOMapper.java | 25 +++ .../biz/domain/mapper/NoteCountDOMapper.java | 11 + .../comment/biz/enums/CommentLevelEnum.java | 17 ++ .../comment/biz/service/CommentService.java | 7 + .../biz/service/impl/CommentServiceImpl.java | 5 + .../resources/mapperxml/CommentDOMapper.xml | 22 ++ .../resources/mapperxml/NoteCountDOMapper.xml | 6 + 9 files changed, 314 insertions(+) create mode 100644 han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/consumer/DeleteCommentConsumer.java create mode 100644 han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/consumer/DeleteCommentLocalCacheConsumer.java diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/consumer/DeleteCommentConsumer.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/consumer/DeleteCommentConsumer.java new file mode 100644 index 0000000..fc63a8b --- /dev/null +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/consumer/DeleteCommentConsumer.java @@ -0,0 +1,192 @@ +package com.hanserwei.hannote.comment.biz.consumer; + +import cn.hutool.core.collection.CollUtil; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.RateLimiter; +import com.hanserwei.framework.common.utils.JsonUtils; +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.mapper.CommentDOMapper; +import com.hanserwei.hannote.comment.biz.domain.mapper.NoteCountDOMapper; +import com.hanserwei.hannote.comment.biz.enums.CommentLevelEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.client.producer.SendCallback; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +@SuppressWarnings("UnstableApiUsage") +@Component +@Slf4j +@RocketMQMessageListener(consumerGroup = "han_note_group_" + MQConstants.TOPIC_DELETE_COMMENT, // Group + topic = MQConstants.TOPIC_DELETE_COMMENT // 消费的主题 Topic +) +public class DeleteCommentConsumer implements RocketMQListener { + + // 每秒创建 1000 个令牌 + private final RateLimiter rateLimiter = RateLimiter.create(1000); + @Resource + private CommentDOMapper commentDOMapper; + @Resource + private NoteCountDOMapper noteCountDOMapper; + @Resource + private RedisTemplate redisTemplate; + @Resource + private RocketMQTemplate rocketMQTemplate; + + @Override + public void onMessage(String body) { + // 令牌桶流控 + rateLimiter.acquire(); + + log.info("## 【删除评论 - 后续业务处理】消费者消费成功, body: {}", body); + + CommentDO commentDO = JsonUtils.parseObject(body, CommentDO.class); + + // 评论级别 + Integer level = null; + if (commentDO != null) { + level = commentDO.getLevel(); + } + + CommentLevelEnum commentLevelEnum = CommentLevelEnum.valueOf(level); + + if (commentLevelEnum != null) { + switch (commentLevelEnum) { + case ONE -> { // 一级评论 + if (commentDO != null) { + handleOneLevelComment(commentDO); + } + } + case TWO -> { // 二级评论 + if (commentDO != null) { + handleTwoLevelComment(commentDO); + } + } + } + } + + } + + /** + * 一级评论处理 + * + * @param commentDO 评论 + */ + private void handleOneLevelComment(CommentDO commentDO) { + Long commentId = commentDO.getId(); + Long noteId = commentDO.getNoteId(); + + // 1. 关联评论删除(一级评论下所有子评论,都需要删除) + int count = commentDOMapper.deleteByParentId(commentId); + + // 2. 计数更新(笔记下总评论数) + // 更新 Redis 缓存 + String redisKey = RedisKeyConstants.buildNoteCommentTotalKey(noteId); + boolean hasKey = redisTemplate.hasKey(redisKey); + + if (hasKey) { + // 笔记评论总数 -1 + redisTemplate.opsForHash().increment(redisKey, RedisKeyConstants.FIELD_COMMENT_TOTAL, -(count + 1)); + } + + // 更新 t_note_count 计数表 + noteCountDOMapper.updateCommentTotalByNoteId(noteId, -(count + 1)); + } + + /** + * 二级评论处理 + * + * @param commentDO 评论 + */ + private void handleTwoLevelComment(CommentDO commentDO) { + Long commentId = commentDO.getId(); + + // 1. 批量删除关联评论(递归查询回复评论,并批量删除) + List replyCommentIds = Lists.newArrayList(); + recurrentGetReplyCommentId(replyCommentIds, commentId); + + // 被删除的行数 + int count = 0; + if (CollUtil.isNotEmpty(replyCommentIds)) { + count = commentDOMapper.deleteByIds(replyCommentIds); + } + + // 2. 更新一级评论的计数 + Long parentCommentId = commentDO.getParentId(); + String redisKey = RedisKeyConstants.buildCountCommentKey(parentCommentId); + + boolean hasKey = redisTemplate.hasKey(redisKey); + if (hasKey) { + redisTemplate.opsForHash().increment(redisKey, RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL, -(count + 1)); + } + + // 3. 若是最早的发布的二级评论被删除,需要更新一级评论的 first_reply_comment_id + + // 查询一级评论 + CommentDO oneLevelCommentDO = commentDOMapper.selectById(parentCommentId); + Long firstReplyCommentId = oneLevelCommentDO.getFirstReplyCommentId(); + + // 若删除的是最早回复的二级评论 + if (Objects.equals(firstReplyCommentId, commentId)) { + // 查询数据库,重新获取一级评论最早回复的评论 + CommentDO earliestCommentDO = commentDOMapper.selectEarliestByParentId(parentCommentId); + + // 最早回复的那条评论 ID。若查询结果为 null, 则最早回复的评论 ID 为 null + Long earliestCommentId = Objects.nonNull(earliestCommentDO) ? earliestCommentDO.getId() : null; + // 更新其一级评论的 first_reply_comment_id + commentDOMapper.updateFirstReplyCommentIdByPrimaryKey(earliestCommentId, parentCommentId); + } + + // 4. 重新计算一级评论的热度值 + Set commentIds = Sets.newHashSetWithExpectedSize(1); + commentIds.add(parentCommentId); + + // 异步发送计数 MQ, 更新评论热度值 + org.springframework.messaging.Message message = MessageBuilder.withPayload(JsonUtils.toJsonString(commentIds)) + .build(); + + // 异步发送 MQ 消息 + rocketMQTemplate.asyncSend(MQConstants.TOPIC_COMMENT_HEAT_UPDATE, message, new SendCallback() { + @Override + public void onSuccess(SendResult sendResult) { + log.info("==> 【评论热度值更新】MQ 发送成功,SendResult: {}", sendResult); + } + + @Override + public void onException(Throwable throwable) { + log.error("==> 【评论热度值更新】MQ 发送异常: ", throwable); + } + }); + } + + /** + * 递归获取全部回复的评论 ID + * + * @param commentIds 评论 ID 列表 + * @param commentId 评论 ID + */ + private void recurrentGetReplyCommentId(List commentIds, Long commentId) { + CommentDO replyCommentDO = commentDOMapper.selectByReplyCommentId(commentId); + + if (Objects.isNull(replyCommentDO)) return; + + commentIds.add(replyCommentDO.getId()); + Long replyCommentId = replyCommentDO.getId(); + // 递归调用 + recurrentGetReplyCommentId(commentIds, replyCommentId); + } + + +} \ No newline at end of file diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/consumer/DeleteCommentLocalCacheConsumer.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/consumer/DeleteCommentLocalCacheConsumer.java new file mode 100644 index 0000000..2eee0de --- /dev/null +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/consumer/DeleteCommentLocalCacheConsumer.java @@ -0,0 +1,29 @@ +package com.hanserwei.hannote.comment.biz.consumer; + +import com.hanserwei.hannote.comment.biz.constants.MQConstants; +import com.hanserwei.hannote.comment.biz.service.CommentService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.annotation.MessageModel; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +@RocketMQMessageListener(consumerGroup = "han_note_group_" + MQConstants.TOPIC_DELETE_COMMENT_LOCAL_CACHE, // Group + topic = MQConstants.TOPIC_DELETE_COMMENT_LOCAL_CACHE, // 消费的主题 Topic + messageModel = MessageModel.BROADCASTING) // 广播模式 +public class DeleteCommentLocalCacheConsumer implements RocketMQListener { + + @Resource + private CommentService commentService; + + @Override + public void onMessage(String body) { + Long commentId = Long.valueOf(body); + log.info("## 消费者消费成功, commentId: {}", commentId); + + commentService.deleteCommentLocalCache(commentId); + } +} \ No newline at end of file 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 de24c78..916fa22 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 @@ -120,4 +120,29 @@ public interface CommentDOMapper extends BaseMapper { */ List selectChildCommentsByParentIdAndLimit(@Param("parentId") Long parentId, @Param("limit") int limit); + + /** + * 删除一级评论下,所有二级评论 + * + * @param commentId 一级评论 ID + * @return 删除数量 + */ + int deleteByParentId(Long commentId); + + /** + * 批量删除评论 + * + * @param commentIds 评论 ID 列表 + * @return 删除数量 + */ + int deleteByIds(@Param("commentIds") List commentIds); + + + /** + * 根据 reply_comment_id 查询 + * + * @param commentId 回复的评论 ID + * @return 评论 + */ + CommentDO selectByReplyCommentId(Long commentId); } \ No newline at end of file diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/domain/mapper/NoteCountDOMapper.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/domain/mapper/NoteCountDOMapper.java index 045f2e4..1048316 100644 --- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/domain/mapper/NoteCountDOMapper.java +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/domain/mapper/NoteCountDOMapper.java @@ -3,6 +3,7 @@ package com.hanserwei.hannote.comment.biz.domain.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.hanserwei.hannote.comment.biz.domain.dataobject.NoteCountDO; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; @Mapper public interface NoteCountDOMapper extends BaseMapper { @@ -14,4 +15,14 @@ public interface NoteCountDOMapper extends BaseMapper { * @return 笔记评论总数 */ Long selectCommentTotalByNoteId(Long noteId); + + /** + * 更新评论总数 + * + * @param noteId 笔记ID + * @param count 评论数 + * @return 更新数量 + */ + int updateCommentTotalByNoteId(@Param("noteId") Long noteId, + @Param("count") int count); } \ 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/CommentLevelEnum.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/enums/CommentLevelEnum.java index daa0327..033353d 100644 --- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/enums/CommentLevelEnum.java +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/enums/CommentLevelEnum.java @@ -3,6 +3,8 @@ package com.hanserwei.hannote.comment.biz.enums; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Objects; + @Getter @AllArgsConstructor public enum CommentLevelEnum { @@ -14,4 +16,19 @@ public enum CommentLevelEnum { private final Integer code; + /** + * 根据类型 code 获取对应的枚举 + * + * @param code 类型 code + * @return 枚举 + */ + public static CommentLevelEnum valueOf(Integer code) { + for (CommentLevelEnum commentLevelEnum : CommentLevelEnum.values()) { + if (Objects.equals(code, commentLevelEnum.getCode())) { + return commentLevelEnum; + } + } + return null; + } + } \ No newline at end of file diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/service/CommentService.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/service/CommentService.java index c251cbb..a302540 100644 --- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/service/CommentService.java +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/service/CommentService.java @@ -54,4 +54,11 @@ public interface CommentService extends IService { * @return 响应 */ Response deleteComment(DeleteCommentReqVO deleteCommentReqVO); + + /** + * 删除本地评论缓存 + * + * @param commentId 评论ID + */ + void deleteCommentLocalCache(Long commentId); } 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 762c6f0..dc1746b 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 @@ -718,6 +718,11 @@ public class CommentServiceImpl extends ServiceImpl return Response.success(); } + @Override + public void deleteCommentLocalCache(Long commentId) { + LOCAL_CACHE.invalidate(commentId); + } + /** * 初始化评论点赞布隆过滤器 * 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 943e5a1..b9e6db4 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 @@ -201,4 +201,26 @@ order by create_time limit #{limit} + + + delete + from t_comment + where parent_id = #{commentId} + + + + delete + from t_comment + where id in + + #{commentId} + + + + \ No newline at end of file diff --git a/han-note-comment/han-note-comment-biz/src/main/resources/mapperxml/NoteCountDOMapper.xml b/han-note-comment/han-note-comment-biz/src/main/resources/mapperxml/NoteCountDOMapper.xml index 1fa5b55..3f3a8d5 100644 --- a/han-note-comment/han-note-comment-biz/src/main/resources/mapperxml/NoteCountDOMapper.xml +++ b/han-note-comment/han-note-comment-biz/src/main/resources/mapperxml/NoteCountDOMapper.xml @@ -20,4 +20,10 @@ from t_note_count where note_id = #{noteId} + + + update t_note_count + set comment_total = comment_total + #{count} + where note_id = #{noteId} + \ No newline at end of file