参考:
https://www.jb51.net/article/198874.htm

一、概述

这篇文章主要给大家介绍了关于Java实现评论回复功能的完整步骤
评论功能或许是大多数的单体应用之中会用到的功能,我们会在自己所开发的项目之中进行集成该功能
大多数时候我们会将评论功能划分成以下几种:

  • 单一型
  • 嵌套型
  • 两层型

    1、单一型

    单一型评论方式就是日常论坛之中的盖楼的方式
    用户只能根据所在的文章或者问题进行单一回复,评论之间没有互动
    类似于问答形式。提出问题,然后回答,一对多关系。这些回答之间没有任何联系
    评论回复功能 - 图1

    2、嵌套型【不推荐】

    嵌套型评论方式会对有回复的评论进行递归,会造成后端性能不佳,而且对于前端的展示也不是很友好
    image.png

    3、两层型

    两层型评论方式就是除了一级评论之外,无论是对于该评论的回复还是对于回复的回复都统一在第二层
    image.png

    Bilibili评论回复功能就采用的是两层型

image.png
image.png
总结:

  • 评论可以被回复,回复也可以被回复;
  • 针对某篇文章或视频的评论,在第一级目录;
  • 针对某个评论的回复或者回复的回复在二级目录(回复的回复格式上要区别于直接对评论的回复);

    二、实现原理

    就以最常见的博客来说,不同的分类方式实现原理不一样

    1、单一型

    我们只需要在评论的数据表格中添加博客id即可,查询出相对应的数据直接进行展示即可

    1. create table `comment` (
    2. `id` int(11) not null auto_increment comment '主键id',
    3. `nickname` varchar(255) default null comment '评论者昵称',
    4. `avatar` varchar(255) comment '评论头像',
    5. `content` varchar(255) default null comment '评论的内容',
    6. `blog_id` int(11) default null comment '评论的博客id',
    7. primary key (`id`)
    8. ) comment '评论表';

    在业务之中根据博客id查询出来,传递给前端展示出来即可

    1. select * from comment where blog_id=#{blog_id}

    2、嵌套型

    嵌套型的评论方式所需要的数据结构是树状型的,评论多起来的话层级结构会变得很复杂,对于性能消耗也是很巨大,【不推荐】
    实现原理为我们会在评论表之中添加一个【parent_id】字段,定义评论和回复为父子级的关系,评论为父级,回复为子级,默认为【-1】,表示为没有父级,

    1. create table `comment` (
    2. `id` int(11) not null auto_increment comment '主键id',
    3. `nickname` varchar(255) default null comment '评论者昵称',
    4. `avatar` varchar(255) comment '评论头像',
    5. `content` varchar(255) default null comment '评论的内容',
    6. `blog_id` int(11) default null comment '评论的博客id',
    7. `parent_id` int(11) default '-1' comment '父级评论id',
    8. primary key (`id`)
    9. ) comment '评论表';

    需要使用递归和链表进行循环遍历插入回复
    设计如下:
    Content.java

    1. private static final long serialVersionUID = 1L;
    2. @ApiModelProperty(value = "主键id")
    3. @TableId(value = "id", type = IdType.ASSIGN_ID)
    4. private Integer id;
    5. @ApiModelProperty(value = "用户昵称")
    6. @TableField("nickname")
    7. private String nickname;
    8. @ApiModelProperty(value = "头像")
    9. @TableField("avatar")
    10. private String avatar;
    11. @ApiModelProperty(value = "评论")
    12. @TableField("comment")
    13. private String comment;
    14. @ApiModelProperty(value = "博客id ")
    15. @TableField("blog_id")
    16. private Integer blogId;
    17. @ApiModelProperty(value = "回复评论id")
    18. @TableField("parent_id")
    19. private Integer parentId;

    DTO设计
    ContentDTO.java

    1. @Data
    2. @NoArgsConstructor
    3. @AllArgsConstructor
    4. @Accessors(chain = true)
    5. @ApiModel(value = "评论模型")
    6. @JsonIgnoreProperties(value = { "handler" })
    7. public class ContentDTO {
    8. private int id;
    9. private String nickname;
    10. private String content;
    11. private List<ContentDTO> children;
    12. }

    使用mybatis做为持久层框架,编写sql查询语句进行嵌套查询,

    1. <resultMap id="commentDTOMap" type="com.zukxu.items.comment.entity.ContentDTO">
    2. <id property="id" column="comment_id"></id>
    3. <result property="nickname" column="nickname"></result>
    4. <result property="content" column="content"></result>
    5. <association property="children"
    6. select="com.zukxu.items.comment.mapper.ContentMapper.selectCommentById" column="{blogId=blog_id,parentId=comment_id}"
    7. fetchType="lazy">
    8. </association>
    9. </resultMap>
    10. <select id="selectCommentById" resultMap="commentDTOMap">
    11. SELECT comment_id,nickname,content,blog_id,parent_id FROM blog WHERE blog_id = #{blogId} AND parent_id = #{parentId}
    12. </select>

    结果如下:

    1. [
    2. {
    3. "id": "1309302063977304065",
    4. "nickname": "1",
    5. "content": "这次该可以了吧",
    6. "children": [
    7. {
    8. "id": "1309319425866698753",
    9. "nickname": "1",
    10. "content": "好了?",
    11. "children": []
    12. }
    13. ]
    14. },
    15. {
    16. "id": "1309341283121154994",
    17. "nickname": "4",
    18. "content": "为什么呢",
    19. "children": [
    20. {
    21. "id": "1309373849414787073",
    22. "nickname": "1",
    23. "content": "好了?",
    24. "children": []
    25. },
    26. {
    27. "id": "1309308402422091778",
    28. "nickname": "1",
    29. "content": "可以了吧",
    30. "children": []
    31. },
    32. {
    33. "id": "1309373675783184385",
    34. "nickname": "1",
    35. "content": "好了?",
    36. "children": [
    37. {
    38. "id": "1309373886580514817",
    39. "nickname": "1",
    40. "content": "???",
    41. "children": []
    42. }
    43. ]
    44. }
    45. ]
    46. }
    47. ]

    结果会造成多重嵌套,不是很友好

    3、两层型

    比单一型多了互动的功能,比嵌套型更加简洁,方便操作管理
    设计和嵌套型保持一致,只需要在查询出来数据之后对数据进行处理即可
    将嵌套型转为两层型结构,处理每个父级评论的子级及其嵌套子级

    1. public List<CommentDTO> findParent(List<CommentDTO> comments) {
    2. for (CommentDTO comment : comments) {
    3. // 防止checkForComodification(),而建立一个新集合
    4. ArrayList<CommentDTO> fatherChildren = new ArrayList<>();
    5. // 递归处理子级的回复,即回复内有回复
    6. findChildren(comment, fatherChildren);
    7. // 将递归处理后的集合放回父级的孩子中
    8. comment.setChildren(fatherChildren);
    9. }
    10. return comments;
    11. }
    12. public void findChildren(CommentDTO parent, List<CommentDTO> fatherChildren) {
    13. // 找出直接子级
    14. List<CommentDTO> comments = parent.getChildren();
    15. // 遍历直接子级的子级
    16. for (CommentDTO comment : comments) {
    17. // 若非空,则还有子级,递归
    18. if (!comment.getChildren().isEmpty()) {
    19. findChildren(comment, fatherChildren);
    20. }
    21. // 已经到了最底层的嵌套关系,将该回复放入新建立的集合
    22. fatherChildren.add(comment);
    23. // 容易忽略的地方:将相对底层的子级放入新建立的集合之后
    24. // 则表示解除了嵌套关系,对应的其父级的子级应该设为空
    25. comment.setChildren(new ArrayList<>());
    26. }
    27. }
    28. }

    最后的结果如下:

    1. [
    2. {
    3. "id": "1309302063977304065",
    4. "userId": "1",
    5. "comment": "这次该可以了吧",
    6. "children": [
    7. {
    8. "id": "1309319425866698753",
    9. "userId": "1",
    10. "comment": "好了?",
    11. "children": []
    12. }
    13. ]
    14. },
    15. {
    16. "id": "1309341283121154994",
    17. "userId": "4",
    18. "comment": "为什么呢",
    19. "children": [
    20. {
    21. "id": "1309373849414787073",
    22. "userId": "1",
    23. "comment": "好了?",
    24. "children": []
    25. },
    26. {
    27. "id": "1309308402422091778",
    28. "userId": "1",
    29. "comment": "可以了吧",
    30. "children": []
    31. },
    32. {
    33. "id": "1309373886580514817",
    34. "userId": "1",
    35. "comment": "???",
    36. "children": []
    37. },
    38. {
    39. "id": "1309373675783184385",
    40. "userId": "1",
    41. "comment": "好了?",
    42. "children": []
    43. }
    44. ]
    45. }
    46. ]

    绝大多数时候我们都会去使用两层型的评论方式做评论