当前位置: 首页 > news >正文

评论设计开发

1、需求

假如我们开发了一个文库的网站,用户可以对文章进行评论,要实现二级评论的功能。就是只展示两层,第二层包含对第一层的回复和第二层回复的回复....

效果例如bilibili:

2、评论表设计(comment)

字段

描述

id

评论记录id,主键

doc_id

被评论的文章id

user_id

用户id

root_comment_id

根评论id,如果是一级评论,则为0,如果是二级评论,则为一级评论的id

reply_to_comment_id

被回复的评论id,如果是一级评论,则为0

reply_to_user_id

被回复的用户id,如果是一级评论,则为null;

content

评论的内容

count

一级评论下二级评论数量,

DROP TABLE IF EXISTS "public"."comment";
CREATE TABLE "public"."comment" ("id" int4 NOT NULL DEFAULT nextval('comment_id_seq'::regclass),"doc_id" int4 NOT NULL,"user_id" varchar(32) COLLATE "pg_catalog"."default","root_comment_id" int4,"reply_to_comment_id" int4,"reply_to_user_id" varchar(32) COLLATE "pg_catalog"."default","content" text COLLATE "pg_catalog"."default","count" int4,"create_time" timestamp(6),"update_time" timestamp(6),"creator" varchar(64) COLLATE "pg_catalog"."default","updater" varchar(64) COLLATE "pg_catalog"."default"
)
;
COMMENT ON COLUMN "public"."comment"."id" IS '主键';
COMMENT ON COLUMN "public"."comment"."doc_id" IS '文档id';
COMMENT ON COLUMN "public"."comment"."user_id" IS '评论用户id';
COMMENT ON COLUMN "public"."comment"."root_comment_id" IS '根评论id,如果是一级评论,则为0,如果是二级评论,则为一级评论的id';
COMMENT ON COLUMN "public"."comment"."reply_to_comment_id" IS '指向被回复的评论id,如果是一级评论,则为0';
COMMENT ON COLUMN "public"."comment"."reply_to_user_id" IS '指向被回复的用户id,如果是一级评论,则为null;这个属于冗余字段,便于查询';
COMMENT ON COLUMN "public"."comment"."content" IS '内容';
COMMENT ON COLUMN "public"."comment"."count" IS '一级评论下二级评论数量,当前记录是一级评论时,此值才有意义,当前记录是二级评论,此值为0';
COMMENT ON COLUMN "public"."comment"."create_time" IS '创建时间';
COMMENT ON COLUMN "public"."comment"."update_time" IS '更新时间';
COMMENT ON COLUMN "public"."comment"."creator" IS '创建人';
COMMENT ON COLUMN "public"."comment"."updater" IS '更新人';

  1. 性能优先:单次查询解决排序,避免递归开销。
  2. 简单直接:字段少、索引轻、逻辑清晰,降低开发复杂度。
  3. 扩展灵活:支持无限嵌套和动态调整,适配评论、消息流等场景。

4、通过sql模拟实现大致功能

批量插入

sql脚本如下:

-- 插入测试数据(假设 video_id=1001)
INSERT INTO comment (id, doc_id, user_id, content, create_time, root_comment_id, reply_to_comment_id, reply_to_user_id
) VALUES
-- 一级评论(根评论)
(1, 1001, 'userA', '文章真棒!', '2025-07-12 10:00:00', 0, 0, NULL),
-- 二级评论:回复 userA
(2, 1001, 'userB', '同意!', '2025-07-12 10:01:00', 1, 1, 'userA',),
-- 三级评论:回复 userB(支持多级扩展)
(3, 1001, 'userC', '在线', '2025-07-12 10:02:00', 1, 2, 'userB'),
-- 二级评论:再回复 userA(跨回复链)
(4, 1001, 'userD', '绝了', '2025-05-12 10:03:00', 1, 1, 'userA'),
-- 一级评论(新根评论)
(5, 1001, 'userE', '好听', '2025-07-12 10:04:00', 0, 0, NULL);

查询一级评论

SELECTid, user_id, content, create_time,reply_to_user_id, thread_path
FROMcomment
WHEREdoc_id = 1001
AND root_comment_id = 0
ORDER BYcreate_time DESC 
LIMIT 20 OFFSET 0;

5 userE 好听 2025-07-12 10:04:00 5

1 userA 文章真棒! 2025-07-12 10:00:00 1

在一级评论下查询二级评论

SELECT id, user_id, content, create_time,reply_to_user_id, thread_path
FROM comment
WHERE doc_id = 1001  AND root_comment_id = 1   -- 指定文章
ORDER BYcreate_time DESC,thread_path;    -- 同层级内按时间正序

结果:

6 userB 同意! 2025-07-12 12:01:00 userA 

3 userC 在线 2025-07-12 10:02:00 userB 

2 userB 同意! 2025-07-12 10:01:00 userA 

4 userD 绝了 2025-05-12 10:03:00 userA 

添加索引优化性能

目前表中没有添加索引,所以上面这个sql查询比较慢,我们可以添加一个组合索引,4个字段:(doc_id, root_comment_id, create_time)

创建索引脚本如下

create index idx_doc_id on comment (doc_id, root_comment_id, create_time,thread_path);

5、发表评论的接口

不管是发表一级评论,还是对一级评论进行回复,还是对二级评论进行回复,还是对回复进行回复,可以共用一个接口。

下面看下这个接口如何设计,代码如下

入参:

@Data
public class AddCommentDTO {private Integer docId;private String userId;private String content;private Long replyToCommentId;
}

实现:

 @Override@Transactional(rollbackFor = Exception.class)public Boolean addComment(AddCommentDTO dto) {//处理一级评论if (dto.getReplyToCommentId() == null){Comment comment = new Comment();comment.setContent(dto.getContent());comment.setDocId(dto.getDocId());comment.setUserId(dto.getUserId());comment.setRootCommentId(0L);comment.setReplyToCommentId(dto.getReplyToCommentId());comment.setCount(0);comment.setCreateTime(LocalDateTime.now());return save(comment);}//处理二级评论else {//查询被回复的评论Comment comment = this.getOne(new LambdaQueryWrapper<Comment>().eq(Comment::getId, dto.getReplyToCommentId()));//获取一级评论idLong rootCommentId = comment.getRootCommentId();if (rootCommentId == 0){rootCommentId = comment.getId();}//被回复用户idString replyToUserId = comment.getUserId();Comment replyComment = new Comment();replyComment.setContent(dto.getContent());replyComment.setDocId(dto.getDocId());replyComment.setUserId(dto.getUserId());replyComment.setRootCommentId(rootCommentId);replyComment.setReplyToCommentId(rootCommentId);replyComment.setReplyToUserId(replyToUserId);replyComment.setCount(0);replyComment.setCreateTime(LocalDateTime.now());boolean save = save(replyComment);if (save){//更新根节点countthis.update(new LambdaUpdateWrapper<Comment>().set(Comment::getCount, comment.getCount() + 1).eq(Comment::getId, rootCommentId));}return save;}}

查询:

查询入参

@Data
public class QueryCommentDTO {private Integer pageNo;private Integer pageSize;private Long commentId;private Integer docId;
}
 @Overridepublic IPage<Comment> queryComment(QueryCommentDTO dto) {LambdaQueryWrapper<Comment> commentLambdaQueryWrapper = new LambdaQueryWrapper<Comment>().eq(Comment::getRootCommentId, dto.getCommentId()).eq(Comment::getDocId, dto.getDocId()).orderByDesc(Comment::getCreateTime);IPage<Comment> objectPage = new Page<>(dto.getPageNo(), dto.getPageSize());return   this.page(objectPage,commentLambdaQueryWrapper);}

新增查询一级评论

新增二级评论

对二级评论回复

http://www.dtcms.com/a/279685.html

相关文章:

  • RedisJSON 技术揭秘`JSON.DEBUG MEMORY` 量化 JSON 键的内存占用
  • Python深浅拷贝全解析:从原理到实战的避坑指南
  • 深度解析:htmlspecialchars 与 nl2br 结合使用的前后端协作之道,大学毕业论文——仙盟创梦IDE
  • 工业场合需要千变万化的模拟信号,如何获取?
  • B4016 树的直径
  • 阿尔卡特ASM180TD181TD氦检漏器ALCATEL
  • 使用dify生成测试用例
  • 【第一章编辑器开发基础第二节编辑器布局_3间距控制(4/4)】
  • OpenCV C++ 中的掩码(Mask)操作
  • 微服务初步入门
  • 设计模式之适配器模式:让不兼容的接口协同工作的艺术
  • Unreal5从入门到精通之如何实现UDP Socket通讯
  • 【C++进阶】---- 多态
  • 解锁文档处理新体验:Python库Agentic Document Extraction
  • OneCode3.0 通信架构简介——MCPServer微内核设计哲学与实现
  • Web学习笔记4
  • 算法训练营day16 513.找树左下角的值、112. 路径总和、106.从中序与后序遍历序列构造二叉树
  • 探索 Sort.h:多功能排序算法模板库
  • [element-ui]el-table在可视区域底部固定一个横向滚动条
  • 智源全面开源RoboBrain 2.0与RoboOS 2.0:刷新10项评测基准,多机协作加速群体智能
  • MCP 第三波升级!Function Call 多步调用 + 流式输出详解
  • QWidget 和 QML 的本质和使用上的区别
  • 慢查询日志监控:定位性能瓶颈的第一步
  • 【抖音滑动验证码风控分析】
  • 小架构step系列14:白盒集成测试原理
  • C# TCP粘包与拆包深度了解
  • spark广播表大小超过Spark默认的8GB限制
  • FatJar打包和FatJar启动配置文件修改。
  • pattern of distributed system 读书笔记-Overview of the Patterns
  • Rsyslog介绍及运用