From 85e0238857afb5f99dd78a52019826dcb5e45764 Mon Sep 17 00:00:00 2001 From: Hanserwei <2628273921@qq.com> Date: Sun, 9 Nov 2025 14:12:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(comment):=20=E6=96=B0=E5=A2=9E=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E8=AF=84=E8=AE=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增删除评论接口,支持物理删除评论及关联内容 - 添加权限校验,仅允许评论创建者删除评论- 使用编程式事务保证删除操作的原子性- 删除评论后清理 Redis 缓存(ZSet 和 String 类型) - 发送 MQ 消息异步更新计数、删除关联数据及本地缓存 - 新增 DeleteCommentReqVO 请求参数类校验评论 ID - 补充 KeyValueRpcService 删除评论内容方法 - 新增相关 MQ Topic 常量及响应码枚举 - 更新 HTTP 接口测试用例 --- .../comment/biz/constants/MQConstants.java | 10 ++ .../biz/controller/CommentController.java | 6 ++ .../comment/biz/enums/ResponseCodeEnum.java | 1 + .../biz/model/vo/DeleteCommentReqVO.java | 18 ++++ .../comment/biz/rpc/KeyValueRpcService.java | 31 +++++- .../comment/biz/service/CommentService.java | 8 ++ .../biz/service/impl/CommentServiceImpl.java | 98 +++++++++++++++++++ http-client/gateApi.http | 9 ++ 8 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/model/vo/DeleteCommentReqVO.java diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/constants/MQConstants.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/constants/MQConstants.java index 25b2a16..40b2212 100644 --- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/constants/MQConstants.java +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/constants/MQConstants.java @@ -22,6 +22,16 @@ public interface MQConstants { */ String TOPIC_COMMENT_LIKE_OR_UNLIKE = "CommentLikeUnlikeTopic"; + /** + * Topic: 删除本地缓存 —— 评论详情 + */ + String TOPIC_DELETE_COMMENT_LOCAL_CACHE = "DeleteCommentDetailLocalCacheTopic"; + + /** + * Topic: 删除评论 + */ + String TOPIC_DELETE_COMMENT = "DeleteCommentTopic"; + /** * Tag 标签:点赞 */ diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/controller/CommentController.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/controller/CommentController.java index 55275dd..b4dddf0 100644 --- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/controller/CommentController.java +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/controller/CommentController.java @@ -51,4 +51,10 @@ public class CommentController { return commentService.unlikeComment(unLikeCommentReqVO); } + @PostMapping("/delete") + @ApiOperationLog(description = "删除评论") + public Response deleteComment(@Validated @RequestBody DeleteCommentReqVO deleteCommentReqVO) { + return commentService.deleteComment(deleteCommentReqVO); + } + } \ 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 6d09284..8e2b5a7 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 @@ -17,6 +17,7 @@ public enum ResponseCodeEnum implements BaseExceptionInterface { PARENT_COMMENT_NOT_FOUND("COMMENT-20000", "此父评论不存在"), COMMENT_ALREADY_LIKED("COMMENT-20002", "您已经点赞过该评论"), COMMENT_NOT_LIKED("COMMENT-20003", "您未点赞该评论,无法取消点赞"), + COMMENT_CANT_OPERATE("COMMENT-20004", "您无法操作该评论"), ; // 异常码 diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/model/vo/DeleteCommentReqVO.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/model/vo/DeleteCommentReqVO.java new file mode 100644 index 0000000..1f2ba32 --- /dev/null +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/model/vo/DeleteCommentReqVO.java @@ -0,0 +1,18 @@ +package com.hanserwei.hannote.comment.biz.model.vo; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DeleteCommentReqVO { + + @NotNull(message = "评论 ID 不能为空") + private 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/rpc/KeyValueRpcService.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/rpc/KeyValueRpcService.java index cb6cd33..a2f1084 100644 --- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/rpc/KeyValueRpcService.java +++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/rpc/KeyValueRpcService.java @@ -6,14 +6,12 @@ import com.hanserwei.framework.common.constant.DateConstants; import com.hanserwei.framework.common.response.Response; import com.hanserwei.hannote.comment.biz.model.bo.CommentBO; import com.hanserwei.hannote.kv.api.KeyValueFeignApi; -import com.hanserwei.hannote.kv.dto.req.BatchAddCommentContentReqDTO; -import com.hanserwei.hannote.kv.dto.req.BatchFindCommentContentReqDTO; -import com.hanserwei.hannote.kv.dto.req.CommentContentReqDTO; -import com.hanserwei.hannote.kv.dto.req.FindCommentContentReqDTO; +import com.hanserwei.hannote.kv.dto.req.*; import com.hanserwei.hannote.kv.dto.resp.FindCommentContentRspDTO; import jakarta.annotation.Resource; import org.springframework.stereotype.Component; +import java.time.LocalDateTime; import java.util.List; import java.util.Objects; @@ -81,4 +79,29 @@ public class KeyValueRpcService { return response.getData(); } + /** + * 删除评论内容 + * + * @param noteId 笔记ID + * @param createTime 创建时间 + * @param contentId 评论内容ID + * @return 是否成功 + */ + public boolean deleteCommentContent(Long noteId, LocalDateTime createTime, String contentId) { + DeleteCommentContentReqDTO deleteCommentContentReqDTO = DeleteCommentContentReqDTO.builder() + .noteId(noteId) + .yearMonth(DateConstants.DATE_FORMAT_Y_M.format(createTime)) + .contentId(contentId) + .build(); + + // 调用 KV 存储服务 + Response response = keyValueFeignApi.deleteCommentContent(deleteCommentContentReqDTO); + + if (!response.isSuccess()) { + throw new RuntimeException("删除评论内容失败"); + } + + return true; + } + } \ 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 7b18ebc..c251cbb 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 @@ -46,4 +46,12 @@ public interface CommentService extends IService { * @return 响应 */ Response unlikeComment(UnLikeCommentReqVO unLikeCommentReqVO); + + /** + * 删除评论 + * + * @param deleteCommentReqVO 删除评论请求 + * @return 响应 + */ + Response deleteComment(DeleteCommentReqVO deleteCommentReqVO); } 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 7a8ca8b..762c6f0 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 @@ -52,6 +52,7 @@ import org.springframework.messaging.support.MessageBuilder; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; import java.time.LocalDateTime; import java.util.*; @@ -82,6 +83,8 @@ public class CommentServiceImpl extends ServiceImpl private ThreadPoolTaskExecutor threadPoolTaskExecutor; @Resource private CommentLikeDOMapper commentLikeDOMapper; + @Resource + private TransactionTemplate transactionTemplate; /** * 评论详情本地缓存 @@ -620,6 +623,101 @@ public class CommentServiceImpl extends ServiceImpl return Response.success(); } + @Override + @SuppressWarnings("unchecked") + public Response deleteComment(DeleteCommentReqVO deleteCommentReqVO) { + // 被删除的评论 ID + Long commentId = deleteCommentReqVO.getCommentId(); + + // 1. 校验评论是否存在 + CommentDO commentDO = commentDOMapper.selectById(commentId); + + if (Objects.isNull(commentDO)) { + throw new ApiException(ResponseCodeEnum.COMMENT_NOT_FOUND); + } + + // 2. 校验是否有权限删除 + Long currUserId = LoginUserContextHolder.getUserId(); + if (!Objects.equals(currUserId, commentDO.getUserId())) { + throw new ApiException(ResponseCodeEnum.COMMENT_CANT_OPERATE); + } + + // 3. 物理删除评论、评论内容 + // 编程式事务,保证多个操作的原子性 + transactionTemplate.execute(status -> { + try { + // 删除评论元数据 + commentDOMapper.deleteById(commentId); + + // 删除评论内容 + keyValueRpcService.deleteCommentContent(commentDO.getNoteId(), + commentDO.getCreateTime(), + commentDO.getContentUuid()); + + return null; + } catch (Exception ex) { + status.setRollbackOnly(); // 标记事务为回滚 + log.error("", ex); + throw ex; + } + }); + + // 4. 删除 Redis 缓存(ZSet 和 String) + Integer level = commentDO.getLevel(); + Long noteId = commentDO.getNoteId(); + Long parentCommentId = commentDO.getParentId(); + + // 根据评论级别,构建对应的 ZSet Key + String redisZSetKey = Objects.equals(level, 1) ? + RedisKeyConstants.buildCommentListKey(noteId) : RedisKeyConstants.buildChildCommentListKey(parentCommentId); + + // 使用 RedisTemplate 执行管道操作 + redisTemplate.executePipelined(new SessionCallback<>() { + @Override + public Object execute(@NonNull RedisOperations operations) { + // 删除 ZSet 中对应评论 ID + operations.opsForZSet().remove(redisZSetKey, commentId); + + // 删除评论详情 + operations.delete(RedisKeyConstants.buildCommentDetailKey(commentId)); + return null; + } + }); + + // 5. 发布广播 MQ, 将本地缓存删除 + rocketMQTemplate.asyncSend(MQConstants.TOPIC_DELETE_COMMENT_LOCAL_CACHE, commentId, new SendCallback() { + @Override + public void onSuccess(SendResult sendResult) { + log.info("==> 【删除评论详情本地缓存】MQ 发送成功,SendResult: {}", sendResult); + } + + @Override + public void onException(Throwable throwable) { + log.error("==> 【删除评论详情本地缓存】MQ 发送异常: ", throwable); + } + }); + + // 6. 发送 MQ, 异步去更新计数、删除关联评论、热度值等 + // 构建消息对象,并将 DO 转成 Json 字符串设置到消息体中 + Message message = MessageBuilder.withPayload(JsonUtils.toJsonString(commentDO)) + .build(); + + // 异步发送 MQ 消息,提升接口响应速度 + rocketMQTemplate.asyncSend(MQConstants.TOPIC_DELETE_COMMENT, message, new SendCallback() { + @Override + public void onSuccess(SendResult sendResult) { + log.info("==> 【评论删除】MQ 发送成功,SendResult: {}", sendResult); + } + + @Override + public void onException(Throwable throwable) { + log.error("==> 【评论删除】MQ 发送异常: ", throwable); + } + }); + + return Response.success(); + } + /** * 初始化评论点赞布隆过滤器 * diff --git a/http-client/gateApi.http b/http-client/gateApi.http index 4d2b3bd..e517dd0 100644 --- a/http-client/gateApi.http +++ b/http-client/gateApi.http @@ -394,3 +394,12 @@ Content-Type: application/json "yearMonth": "2025-11", "contentId": "0fa4376f-a098-4fee-821b-f5b7e627a72c" } + +### 删除评论,同步删除一切相关缓存 +POST http://localhost:8000/comment/comment/delete +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "commentId": 8001 +}