From a37e76c87c73e33ebc60c9eb405fe6ccde0793fa Mon Sep 17 00:00:00 2001
From: Hanserwei <2628273921@qq.com>
Date: Wed, 5 Nov 2025 19:19:19 +0800
Subject: [PATCH] =?UTF-8?q?feat(comment):=20=E5=AE=9E=E7=8E=B0=E8=AF=84?=
=?UTF-8?q?=E8=AE=BA=E5=BC=82=E6=AD=A5=E6=B6=88=E8=B4=B9=E4=B8=8E=E5=86=85?=
=?UTF-8?q?=E5=AE=B9=E5=AD=98=E5=82=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增评论内容批量存储接口与实现
- 实现MQ消息消费端处理评论发布逻辑
- 支持一级与二级评论的层级关系构建
- 添加评论内容与元数据分离存储机制
- 集成分布式ID生成服务用于评论ID生成
- 完善评论相关DTO、DO、BO模型类
- 添加Cassandra数据库操作支持
- 实现Feign接口调用与事务控制
---
han-note-comment/han-note-comment-biz/pom.xml | 10 ++
.../biz/HannoteCommentApplication.java | 2 +
.../biz/consumer/Comment2DBConsumer.java | 122 +++++++++++++++++-
.../biz/domain/mapper/CommentDOMapper.java | 20 +++
.../comment/biz/enums/CommentLevelEnum.java | 17 +++
.../comment/biz/model/bo/CommentBO.java | 46 +++++++
.../biz/model/dto/PublishCommentMqDTO.java | 5 +
.../rpc/DistributedIdGeneratorRpcService.java | 22 ++++
.../comment/biz/rpc/KeyValueRpcService.java | 57 ++++++++
.../biz/service/impl/CommentServiceImpl.java | 7 +
.../resources/mapperxml/CommentDOMapper.xml | 27 ++++
.../hannote/kv/api/KeyValueFeignApi.java | 4 +
.../dto/req/BatchAddCommentContentReqDTO.java | 21 +++
.../kv/dto/req/CommentContentReqDTO.java | 26 ++++
han-note-kv/han-note-kv-biz/pom.xml | 10 ++
.../controller/CommentContentController.java | 29 +++++
.../domain/dataobject/CommentContentDO.java | 21 +++
.../dataobject/CommentContentPrimaryKey.java | 37 ++++++
.../kv/biz/service/CommentContentService.java | 9 ++
.../impl/CommentContentServiceImpl.java | 50 +++++++
http-client/gateApi.http | 34 ++++-
sql/leafcreatetable.sql | 3 +
22 files changed, 575 insertions(+), 4 deletions(-)
create mode 100644 han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/enums/CommentLevelEnum.java
create mode 100644 han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/model/bo/CommentBO.java
create mode 100644 han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/rpc/DistributedIdGeneratorRpcService.java
create mode 100644 han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/rpc/KeyValueRpcService.java
create mode 100644 han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/BatchAddCommentContentReqDTO.java
create mode 100644 han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/CommentContentReqDTO.java
create mode 100644 han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/controller/CommentContentController.java
create mode 100644 han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/dataobject/CommentContentDO.java
create mode 100644 han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/dataobject/CommentContentPrimaryKey.java
create mode 100644 han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/CommentContentService.java
create mode 100644 han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/impl/CommentContentServiceImpl.java
diff --git a/han-note-comment/han-note-comment-biz/pom.xml b/han-note-comment/han-note-comment-biz/pom.xml
index 8de0fc3..a229522 100644
--- a/han-note-comment/han-note-comment-biz/pom.xml
+++ b/han-note-comment/han-note-comment-biz/pom.xml
@@ -103,6 +103,16 @@
rocketmq-client
+
+ com.hanserwei
+ han-note-distributed-id-generator-api
+
+
+
+ com.hanserwei
+ han-note-kv-api
+
+
diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/HannoteCommentApplication.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/HannoteCommentApplication.java
index 9e0938d..229eed1 100644
--- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/HannoteCommentApplication.java
+++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/HannoteCommentApplication.java
@@ -3,10 +3,12 @@ package com.hanserwei.hannote.comment.biz;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.retry.annotation.EnableRetry;
@SpringBootApplication
@MapperScan("com.hanserwei.hannote.comment.biz.domain.mapper")
+@EnableFeignClients("com.hanserwei.hannote")
@EnableRetry
public class HannoteCommentApplication {
public static void main(String[] args) {
diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/consumer/Comment2DBConsumer.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/consumer/Comment2DBConsumer.java
index 81a895c..6b9052e 100644
--- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/consumer/Comment2DBConsumer.java
+++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/consumer/Comment2DBConsumer.java
@@ -1,9 +1,21 @@
package com.hanserwei.hannote.comment.biz.consumer;
+import cn.hutool.core.collection.CollUtil;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
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.domain.dataobject.CommentDO;
+import com.hanserwei.hannote.comment.biz.domain.mapper.CommentDOMapper;
+import com.hanserwei.hannote.comment.biz.enums.CommentLevelEnum;
+import com.hanserwei.hannote.comment.biz.model.bo.CommentBO;
+import com.hanserwei.hannote.comment.biz.model.dto.PublishCommentMqDTO;
+import com.hanserwei.hannote.comment.biz.rpc.KeyValueRpcService;
import jakarta.annotation.PreDestroy;
+import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
@@ -14,8 +26,13 @@ import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
+import org.springframework.transaction.support.TransactionTemplate;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.UUID;
+import java.util.stream.Collectors;
@SuppressWarnings("UnstableApiUsage")
@Component
@@ -25,6 +42,13 @@ public class Comment2DBConsumer {
@Value("${rocketmq.name-server}")
private String nameServer;
+ @Resource
+ private CommentDOMapper commentDOMapper;
+ @Resource
+ private TransactionTemplate transactionTemplate;
+ @Resource
+ private KeyValueRpcService keyValueRpcService;
+
private DefaultMQPushConsumer consumer;
// 每秒创建 1000 个令牌
@@ -53,6 +77,9 @@ public class Comment2DBConsumer {
// 设置每批次消费的最大消息数量,这里设置为 30,表示每次拉取时最多消费 30 条消息
consumer.setConsumeMessageBatchMaxSize(30);
+ // 消息体 Json 字符串转 DTO
+ List publishCommentMqDTOS = Lists.newArrayList();
+
// 注册消息监听器
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
log.info("==> 本批次消息大小: {}", msgs.size());
@@ -64,8 +91,101 @@ public class Comment2DBConsumer {
String message = new String(msg.getBody());
log.info("==> Consumer - Received message: {}", message);
- // TODO: 业务处理
+ publishCommentMqDTOS.add(JsonUtils.parseObject(message, PublishCommentMqDTO.class));
}
+ // 提取所有不为空的回复评论 ID
+ List replyCommentIds = publishCommentMqDTOS.stream()
+ .filter(Objects::nonNull)
+ .map(PublishCommentMqDTO::getReplyCommentId)
+ .toList();
+ // 批量查询相关回复评论记录
+ List replyCommentDOS = null;
+ if (CollUtil.isNotEmpty(replyCommentIds)) {
+ // 批量查询数据库
+ replyCommentDOS = commentDOMapper.selectByCommentIds(replyCommentIds);
+ }
+ // DO 集合转 <评论 ID - 评论 DO> 字典, 以方便后续查找
+ Map commentIdAndCommentDOMap = Maps.newHashMap();
+ if (CollUtil.isNotEmpty(replyCommentDOS)) {
+ commentIdAndCommentDOMap = replyCommentDOS.stream()
+ .collect(Collectors.toMap(CommentDO::getId, commentDO -> commentDO));
+ }
+
+ // DTO 转 BO
+ List commentBOS = Lists.newArrayList();
+ for (PublishCommentMqDTO publishCommentMqDTO : publishCommentMqDTOS) {
+ String imageUrl = publishCommentMqDTO.getImageUrl();
+ CommentBO commentBO = CommentBO.builder()
+ .id(publishCommentMqDTO.getCommentId())
+ .noteId(publishCommentMqDTO.getNoteId())
+ .userId(publishCommentMqDTO.getCreatorId())
+ .isContentEmpty(true) // 默认评论内容为空
+ .imageUrl(StringUtils.isBlank(imageUrl) ? "" : imageUrl)
+ .level(CommentLevelEnum.ONE.getCode()) // 默认为一级评论
+ .parentId(publishCommentMqDTO.getNoteId()) // 默认设置为所属笔记 ID
+ .createTime(publishCommentMqDTO.getCreateTime())
+ .updateTime(publishCommentMqDTO.getCreateTime())
+ .isTop(false)
+ .replyTotal(0L)
+ .likeTotal(0L)
+ .replyCommentId(0L)
+ .replyUserId(0L)
+ .build();
+
+ // 评论内容若不为空
+ String content = publishCommentMqDTO.getContent();
+ if (StringUtils.isNotBlank(content)) {
+ commentBO.setContentUuid(UUID.randomUUID().toString()); // 生成评论内容的 UUID 标识
+ commentBO.setIsContentEmpty(false);
+ commentBO.setContent(content);
+ }
+
+ // 设置评论级别、回复用户 ID (reply_user_id)、父评论 ID (parent_id)
+ Long replyCommentId = publishCommentMqDTO.getReplyCommentId();
+ if (Objects.nonNull(replyCommentId)) {
+ CommentDO replyCommentDO = commentIdAndCommentDOMap.get(replyCommentId);
+
+ if (Objects.nonNull(replyCommentDO)) {
+ // 若回复的评论 ID 不为空,说明是二级评论
+ commentBO.setLevel(CommentLevelEnum.TWO.getCode());
+
+ commentBO.setReplyCommentId(publishCommentMqDTO.getReplyCommentId());
+ // 父评论 ID
+ commentBO.setParentId(replyCommentDO.getId());
+ if (Objects.equals(replyCommentDO.getLevel(), CommentLevelEnum.TWO.getCode())) { // 如果回复的评论属于二级评论
+ commentBO.setParentId(replyCommentDO.getParentId());
+ }
+ // 回复的哪个用户
+ commentBO.setReplyUserId(replyCommentDO.getUserId());
+ }
+ }
+
+ commentBOS.add(commentBO);
+ }
+
+ log.info("## 清洗后的 CommentBOS: {}", JsonUtils.toJsonString(commentBOS));
+ // 编程式事务,保证整体操作的原子性
+ transactionTemplate.execute(status -> {
+ try {
+ // 先批量存入评论元数据
+ commentDOMapper.batchInsert(commentBOS);
+
+ // 过滤出评论内容不为空的 BO
+ List commentContentNotEmptyBOS = commentBOS.stream()
+ .filter(commentBO -> Boolean.FALSE.equals(commentBO.getIsContentEmpty()))
+ .toList();
+ if (CollUtil.isNotEmpty(commentContentNotEmptyBOS)) {
+ // 批量存入评论内容
+ keyValueRpcService.batchSaveCommentContent(commentContentNotEmptyBOS);
+ }
+
+ return true;
+ } catch (Exception ex) {
+ status.setRollbackOnly(); // 标记事务为回滚
+ log.error("", ex);
+ throw ex;
+ }
+ });
// 手动 ACK,告诉 RocketMQ 这批次消息消费成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
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 e484c93..3e2d1be 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
@@ -2,8 +2,28 @@ package com.hanserwei.hannote.comment.biz.domain.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hanserwei.hannote.comment.biz.domain.dataobject.CommentDO;
+import com.hanserwei.hannote.comment.biz.model.bo.CommentBO;
import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
@Mapper
public interface CommentDOMapper extends BaseMapper {
+
+ /**
+ * 根据评论 ID 批量查询
+ *
+ * @param commentIds 评论 ID 列表
+ * @return 评论列表
+ */
+ List selectByCommentIds(@Param("commentIds") List commentIds);
+
+ /**
+ * 批量插入评论
+ *
+ * @param comments 评论列表
+ * @return 插入数量
+ */
+ int batchInsert(@Param("comments") List comments);
}
\ 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
new file mode 100644
index 0000000..daa0327
--- /dev/null
+++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/enums/CommentLevelEnum.java
@@ -0,0 +1,17 @@
+package com.hanserwei.hannote.comment.biz.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum CommentLevelEnum {
+ // 一级评论
+ ONE(1),
+ // 二级评论
+ TWO(2),
+ ;
+
+ private final Integer code;
+
+}
\ No newline at end of file
diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/model/bo/CommentBO.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/model/bo/CommentBO.java
new file mode 100644
index 0000000..d7189ed
--- /dev/null
+++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/model/bo/CommentBO.java
@@ -0,0 +1,46 @@
+package com.hanserwei.hannote.comment.biz.model.bo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class CommentBO {
+ private Long id;
+
+ private Long noteId;
+
+ private Long userId;
+
+ private String contentUuid;
+
+ private String content;
+
+ private Boolean isContentEmpty;
+
+ private String imageUrl;
+
+ private Integer level;
+
+ private Long replyTotal;
+
+ private Long likeTotal;
+
+ private Long parentId;
+
+ private Long replyCommentId;
+
+ private Long replyUserId;
+
+ private Boolean isTop;
+
+ private LocalDateTime createTime;
+
+ private LocalDateTime updateTime;
+}
\ No newline at end of file
diff --git a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/model/dto/PublishCommentMqDTO.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/model/dto/PublishCommentMqDTO.java
index 34651d9..f964380 100644
--- a/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/model/dto/PublishCommentMqDTO.java
+++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/model/dto/PublishCommentMqDTO.java
@@ -40,4 +40,9 @@ public class PublishCommentMqDTO {
*/
private Long creatorId;
+ /**
+ * 评论 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/DistributedIdGeneratorRpcService.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/rpc/DistributedIdGeneratorRpcService.java
new file mode 100644
index 0000000..d9d6df4
--- /dev/null
+++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/rpc/DistributedIdGeneratorRpcService.java
@@ -0,0 +1,22 @@
+package com.hanserwei.hannote.comment.biz.rpc;
+
+import com.hanserwei.hannote.distributed.id.generator.api.DistributedIdGeneratorFeignApi;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DistributedIdGeneratorRpcService {
+
+ @Resource
+ private DistributedIdGeneratorFeignApi distributedIdGeneratorFeignApi;
+
+ /**
+ * 生成评论 ID
+ *
+ * @return 评论 ID
+ */
+ public String generateCommentId() {
+ return distributedIdGeneratorFeignApi.getSegmentId("leaf-segment-comment-id");
+ }
+
+}
\ 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
new file mode 100644
index 0000000..d5aa10d
--- /dev/null
+++ b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/rpc/KeyValueRpcService.java
@@ -0,0 +1,57 @@
+package com.hanserwei.hannote.comment.biz.rpc;
+
+import com.google.common.collect.Lists;
+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.CommentContentReqDTO;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class KeyValueRpcService {
+
+ @Resource
+ private KeyValueFeignApi keyValueFeignApi;
+
+ /**
+ * 批量存储评论内容
+ *
+ * @param commentBOS 评论 BO
+ * @return 批量保存结果
+ */
+ public boolean batchSaveCommentContent(List commentBOS) {
+ List comments = Lists.newArrayList();
+
+ // BO 转 DTO
+ commentBOS.forEach(commentBO -> {
+ CommentContentReqDTO commentContentReqDTO = CommentContentReqDTO.builder()
+ .noteId(commentBO.getNoteId())
+ .content(commentBO.getContent())
+ .contentId(commentBO.getContentUuid())
+ .yearMonth(commentBO.getCreateTime().format(DateConstants.DATE_FORMAT_Y_M))
+ .build();
+ comments.add(commentContentReqDTO);
+ });
+
+ // 构建接口入参实体类
+ BatchAddCommentContentReqDTO batchAddCommentContentReqDTO = BatchAddCommentContentReqDTO.builder()
+ .comments(comments)
+ .build();
+
+ // 调用 KV 存储服务
+ Response> response = keyValueFeignApi.batchAddCommentContent(batchAddCommentContentReqDTO);
+
+ // 若返参中 success 为 false, 则主动抛出异常,以便调用层回滚事务
+ 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/impl/CommentServiceImpl.java b/han-note-comment/han-note-comment-biz/src/main/java/com/hanserwei/hannote/comment/biz/service/impl/CommentServiceImpl.java
index 91eef53..c72c231 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
@@ -11,6 +11,7 @@ import com.hanserwei.hannote.comment.biz.domain.mapper.CommentDOMapper;
import com.hanserwei.hannote.comment.biz.model.dto.PublishCommentMqDTO;
import com.hanserwei.hannote.comment.biz.model.vo.PublishCommentReqVO;
import com.hanserwei.hannote.comment.biz.retry.SendMqRetryHelper;
+import com.hanserwei.hannote.comment.biz.rpc.DistributedIdGeneratorRpcService;
import com.hanserwei.hannote.comment.biz.service.CommentService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -25,6 +26,8 @@ public class CommentServiceImpl extends ServiceImpl
@Resource
private SendMqRetryHelper sendMqRetryHelper;
+ @Resource
+ private DistributedIdGeneratorRpcService distributedIdGeneratorRpcService;
@Override
public Response> publishComment(PublishCommentReqVO publishCommentReqVO) {
@@ -40,6 +43,9 @@ public class CommentServiceImpl extends ServiceImpl
// 发布者ID
Long creatorId = LoginUserContextHolder.getUserId();
+ // RPC: 调用分布式 ID 生成服务,生成评论 ID
+ String commentId = distributedIdGeneratorRpcService.generateCommentId();
+
// 发送消息
// 构造MQ消息体
PublishCommentMqDTO publishCommentMqDTO = PublishCommentMqDTO.builder()
@@ -49,6 +55,7 @@ public class CommentServiceImpl extends ServiceImpl
.replyCommentId(publishCommentReqVO.getReplyCommentId())
.createTime(LocalDateTime.now())
.creatorId(creatorId)
+ .commentId(Long.valueOf(commentId))
.build();
// 发送 MQ 消息,包含重试机制
sendMqRetryHelper.asyncSend(MQConstants.TOPIC_PUBLISH_COMMENT, JsonUtils.toJsonString(publishCommentMqDTO));
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 6997dce..7751a6d 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
@@ -25,4 +25,31 @@
id, note_id, user_id, content_uuid, is_content_empty, image_url, `level`, reply_total,
like_total, parent_id, reply_comment_id, reply_user_id, is_top, create_time, update_time
+
+
+
+
+ insert IGNORE into t_comment (id, note_id, user_id,
+ content_uuid, is_content_empty, image_url,
+ `level`, reply_total, like_total,
+ parent_id, reply_comment_id, reply_user_id,
+ is_top, create_time, update_time)
+ values
+
+ ( #{comment.id}, #{comment.noteId}, #{comment.userId}, #{comment.contentUuid}, #{comment.isContentEmpty}
+ , #{comment.imageUrl}, #{comment.level}, #{comment.replyTotal}, #{comment.likeTotal}, #{comment.parentId}
+ , #{comment.replyCommentId}, #{comment.replyUserId}, #{comment.isTop}, #{comment.createTime}
+ , #{comment.updateTime})
+
+
\ No newline at end of file
diff --git a/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/api/KeyValueFeignApi.java b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/api/KeyValueFeignApi.java
index 6222690..5bb7366 100644
--- a/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/api/KeyValueFeignApi.java
+++ b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/api/KeyValueFeignApi.java
@@ -3,6 +3,7 @@ package com.hanserwei.hannote.kv.api;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.kv.constant.ApiConstants;
import com.hanserwei.hannote.kv.dto.req.AddNoteContentReqDTO;
+import com.hanserwei.hannote.kv.dto.req.BatchAddCommentContentReqDTO;
import com.hanserwei.hannote.kv.dto.req.DeleteNoteContentReqDTO;
import com.hanserwei.hannote.kv.dto.req.FindNoteContentReqDTO;
import com.hanserwei.hannote.kv.dto.resp.FindNoteContentRspDTO;
@@ -24,4 +25,7 @@ public interface KeyValueFeignApi {
@PostMapping(value = PREFIX + "/note/content/delete")
Response> deleteNoteContent(@RequestBody DeleteNoteContentReqDTO deleteNoteContentReqDTO);
+ @PostMapping(value = PREFIX + "/comment/content/batchAdd")
+ Response> batchAddCommentContent(@RequestBody BatchAddCommentContentReqDTO batchAddCommentContentReqDTO);
+
}
\ No newline at end of file
diff --git a/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/BatchAddCommentContentReqDTO.java b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/BatchAddCommentContentReqDTO.java
new file mode 100644
index 0000000..5bcf8ff
--- /dev/null
+++ b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/BatchAddCommentContentReqDTO.java
@@ -0,0 +1,21 @@
+package com.hanserwei.hannote.kv.dto.req;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class BatchAddCommentContentReqDTO {
+
+ @Valid
+ @NotEmpty(message = "评论内容集合不能为空")
+ private List comments;
+}
diff --git a/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/CommentContentReqDTO.java b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/CommentContentReqDTO.java
new file mode 100644
index 0000000..d144f96
--- /dev/null
+++ b/han-note-kv/han-note-kv-api/src/main/java/com/hanserwei/hannote/kv/dto/req/CommentContentReqDTO.java
@@ -0,0 +1,26 @@
+package com.hanserwei.hannote.kv.dto.req;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class CommentContentReqDTO {
+
+ @NotNull(message = "笔记noteId不能为空")
+ private Long noteId;
+
+ @NotNull(message = "发布年月不能为空")
+ private String yearMonth;
+
+ @NotNull(message = "评论正文id不能为空")
+ private String contentId;
+
+ @NotNull(message = "评论正文内容不能为空")
+ private String content;
+}
diff --git a/han-note-kv/han-note-kv-biz/pom.xml b/han-note-kv/han-note-kv-biz/pom.xml
index 558ef57..bf59e62 100644
--- a/han-note-kv/han-note-kv-biz/pom.xml
+++ b/han-note-kv/han-note-kv-biz/pom.xml
@@ -49,6 +49,16 @@
spring-boot-starter-data-cassandra
+
+ com.hanserwei
+ hanserwei-spring-boot-starter-biz-operationlog
+
+
+
+ com.hanserwei
+ hanserwei-spring-boot-starter-jackson
+
+
com.hanserwei
han-note-kv-api
diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/controller/CommentContentController.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/controller/CommentContentController.java
new file mode 100644
index 0000000..a5b4f16
--- /dev/null
+++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/controller/CommentContentController.java
@@ -0,0 +1,29 @@
+package com.hanserwei.hannote.kv.biz.controller;
+
+import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
+import com.hanserwei.framework.common.response.Response;
+import com.hanserwei.hannote.kv.biz.service.CommentContentService;
+import com.hanserwei.hannote.kv.dto.req.BatchAddCommentContentReqDTO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/kv")
+@Slf4j
+public class CommentContentController {
+
+ @Resource
+ private CommentContentService commentContentService;
+
+ @PostMapping("/comment/content/batchAdd")
+ @ApiOperationLog(description = "批量添加评论内容")
+ public Response> batchAddCommentContent(@Validated @RequestBody BatchAddCommentContentReqDTO batchAddCommentContentReqDTO) {
+ return commentContentService.batchAddCommentContent(batchAddCommentContentReqDTO);
+ }
+
+}
diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/dataobject/CommentContentDO.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/dataobject/CommentContentDO.java
new file mode 100644
index 0000000..d2095bf
--- /dev/null
+++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/dataobject/CommentContentDO.java
@@ -0,0 +1,21 @@
+package com.hanserwei.hannote.kv.biz.domain.dataobject;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.cassandra.core.mapping.PrimaryKey;
+import org.springframework.data.cassandra.core.mapping.Table;
+
+@Table("comment_content")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class CommentContentDO {
+
+ @PrimaryKey
+ private CommentContentPrimaryKey primaryKey;
+
+ private String content;
+}
diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/dataobject/CommentContentPrimaryKey.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/dataobject/CommentContentPrimaryKey.java
new file mode 100644
index 0000000..88f1256
--- /dev/null
+++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/domain/dataobject/CommentContentPrimaryKey.java
@@ -0,0 +1,37 @@
+package com.hanserwei.hannote.kv.biz.domain.dataobject;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.cassandra.core.cql.PrimaryKeyType;
+import org.springframework.data.cassandra.core.mapping.PrimaryKeyClass;
+import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn;
+
+import java.util.UUID;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@PrimaryKeyClass
+public class CommentContentPrimaryKey {
+
+ /**
+ * 分区键1
+ */
+ @PrimaryKeyColumn(name = "note_id", type = PrimaryKeyType.PARTITIONED)
+ private Long noteId;
+
+ /**
+ * 分区键2
+ */
+ @PrimaryKeyColumn(name = "year_month", type = PrimaryKeyType.PARTITIONED)
+ private String yearMonth;
+
+ /**
+ * 聚簇键
+ */
+ @PrimaryKeyColumn(name = "content_id", type = PrimaryKeyType.PARTITIONED)
+ private UUID contentId;
+}
diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/CommentContentService.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/CommentContentService.java
new file mode 100644
index 0000000..ba8918b
--- /dev/null
+++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/CommentContentService.java
@@ -0,0 +1,9 @@
+package com.hanserwei.hannote.kv.biz.service;
+
+import com.hanserwei.framework.common.response.Response;
+import com.hanserwei.hannote.kv.dto.req.BatchAddCommentContentReqDTO;
+
+public interface CommentContentService {
+
+ Response> batchAddCommentContent(BatchAddCommentContentReqDTO batchAddCommentContentReqDTO);
+}
diff --git a/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/impl/CommentContentServiceImpl.java b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/impl/CommentContentServiceImpl.java
new file mode 100644
index 0000000..a528167
--- /dev/null
+++ b/han-note-kv/han-note-kv-biz/src/main/java/com/hanserwei/hannote/kv/biz/service/impl/CommentContentServiceImpl.java
@@ -0,0 +1,50 @@
+package com.hanserwei.hannote.kv.biz.service.impl;
+
+import com.hanserwei.framework.common.response.Response;
+import com.hanserwei.hannote.kv.biz.domain.dataobject.CommentContentDO;
+import com.hanserwei.hannote.kv.biz.domain.dataobject.CommentContentPrimaryKey;
+import com.hanserwei.hannote.kv.biz.service.CommentContentService;
+import com.hanserwei.hannote.kv.dto.req.BatchAddCommentContentReqDTO;
+import com.hanserwei.hannote.kv.dto.req.CommentContentReqDTO;
+import jakarta.annotation.Resource;
+import org.springframework.data.cassandra.core.CassandraTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.UUID;
+
+@Service
+public class CommentContentServiceImpl implements CommentContentService {
+
+ @Resource
+ private CassandraTemplate cassandraTemplate;
+
+ @Override
+ public Response> batchAddCommentContent(BatchAddCommentContentReqDTO batchAddCommentContentReqDTO) {
+
+ List comments = batchAddCommentContentReqDTO.getComments();
+
+ //DTO转DO
+
+ List contentDOS = comments.stream()
+ .map(comment -> {
+ // 构建主键类
+ CommentContentPrimaryKey commentContentPrimaryKey = CommentContentPrimaryKey.builder()
+ .noteId(comment.getNoteId())
+ .yearMonth(comment.getYearMonth())
+ .contentId(UUID.fromString(comment.getContentId()))
+ .build();
+ // 构建DO
+ return CommentContentDO.builder()
+ .primaryKey(commentContentPrimaryKey)
+ .content(comment.getContent())
+ .build();
+ }).toList();
+
+ // 批量插入数据
+ cassandraTemplate.batchOps()
+ .insert(contentDOS)
+ .execute();
+ return Response.success();
+ }
+}
diff --git a/http-client/gateApi.http b/http-client/gateApi.http
index 1317ad0..c5bc373 100644
--- a/http-client/gateApi.http
+++ b/http-client/gateApi.http
@@ -291,13 +291,41 @@ Content-Type: application/json
"pageNo": 1
}
-###
+### 发布评论
POST http://localhost:8000/comment/comment/publish
Content-Type: application/json
Authorization: Bearer {{token}}
{
"noteId": 1862481582414102549,
- "content": "这是第三个评论,测试一下异步消息重试",
- "imageUrl": "https://cdn.pixabay.com/photo/2025/10/05/15/06/autumn-9875155_1280.jpg"
+ "content": "这是一条回复测试评论",
+ "imageUrl": "https://cdn.pixabay.com/photo/2025/10/05/15/06/autumn-9875155_1280.jpg",
+ "replyCommentId": 2001
+}
+
+### 批量添加评论
+POST http://localhost:8084/kv/comment/content/batchAdd
+Content-Type: application/json
+
+{
+ "comments": [
+ {
+ "noteId": 1862481582414102548,
+ "yearMonth": "2024-12",
+ "contentId": "db8339cd-beba-40a5-9182-c51c2588ae04",
+ "content": "这是一条评论内容1"
+ },
+ {
+ "noteId": 1862481582414102539,
+ "yearMonth": "2024-12",
+ "contentId": "db8339cd-beba-40a5-9182-c51c2588ae05",
+ "content": "这是一条评论内容2"
+ },
+ {
+ "noteId": 1862481582414102540,
+ "yearMonth": "2024-12",
+ "contentId": "db8339cd-beba-40a5-9182-c51c2588ae06",
+ "content": "这是一条评论内容3"
+ }
+ ]
}
\ No newline at end of file
diff --git a/sql/leafcreatetable.sql b/sql/leafcreatetable.sql
index dbcfc60..47aadd7 100644
--- a/sql/leafcreatetable.sql
+++ b/sql/leafcreatetable.sql
@@ -4,6 +4,9 @@ VALUES ('leaf-segment-hannote-id', 10100, 2000, '小憨书 ID', now());
INSERT INTO `leaf`.`leaf` (`biz_tag`, `max_id`, `step`, `description`, `update_time`)
VALUES ('leaf-segment-user-id', 100, 2000, '用户 ID', now());
+INSERT INTO `leaf`.`leaf` (`biz_tag`, `max_id`, `step`, `description`, `update_time`)
+VALUES ('leaf-segment-comment-id', 1, 2000, '评论 ID', NOW());
+
CREATE TABLE `leaf`
(
`biz_tag` varchar(128) NOT NULL DEFAULT '',