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 '',