可行的二級評論實現

code發表於2021-09-30

最近閒來無事,在做畢業設計。前臺頁面需要一個評論功能,感覺三方的評論太複雜,功能也太多。就想著自己寫一個簡單點的,本人比較菜,方法比較笨,可能效率不高。

環境介紹

後端:SpringBoot + Mybatis

前端:Vue + Element Plus

效果展示


資料庫設計

這裡展示的是需要登入才能使用評論(依賴使用者表),如果不需要登入,可刪除comment_user_id,新增郵箱和暱稱欄位:emailnickname

1、評論表欄位說明

欄位名 型別 說明
id bigint 主鍵
article_id bigint 評論所在文章ID
parent_comment_id bigint 父評論id,null表示一級評論
comment_user_id bigint 評論釋出使用者ID
content longtext 評論內容
create_time datetime 建立時間

只展示最基本的欄位,需要欄位自行新增,例如:versiondeleted

2、評論表建表語句

CREATE TABLE `comment` (
  `id` bigint(20) NOT NULL COMMENT '評論ID',
  `article_id` bigint(20) DEFAULT NULL COMMENT '文章ID',
  `comment_user_id` bigint(20) DEFAULT NULL COMMENT '評論者ID',
  `parent_comment_id` bigint(20) DEFAULT NULL COMMENT '父評論ID',
  `content` longtext COMMENT '評論內容',
  `create_time` datetime DEFAULT NULL COMMENT '建立時間',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `comment_user` (`comment_user_id`),
  KEY `comment_article` (`article_id`),
  KEY `child_parent` (`parent_comment_id`),
  CONSTRAINT `child_parent` FOREIGN KEY (`parent_comment_id`) REFERENCES `comment` (`id`),
  CONSTRAINT `comment_article` FOREIGN KEY (`article_id`) REFERENCES `article` (`id`),
  CONSTRAINT `comment_user` FOREIGN KEY (`comment_user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3、其他表就不展示了,下面有原始碼案例

後端開發

Comment實體類

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class Comment {
    private Long id;
    private Long articleId;
    private Long parentCommentId;
    private String content;
    private Date createTime;
    private Date updateTime;
    private User commentUser;
    private Comment parentComment;
    private List<Comment> replayComments = new ArrayList<>();
}
  • commentUser:評論使用者,一對一對映
  • parentComment:父評論,一對一對映,這裡你也可以直接用parentCommentId
  • replayComments:子評論,一對多對映

Mapper

mapper檔案

我們來看看,SQL語句怎麼寫,還是比較複雜的,因為考慮到對映,關聯的表比較多

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.swx.blog_test.mapper.CommentMapper">

    <resultMap id="tree" type="Comment">
        <id property="id" column="id"/>
        <result property="content" column="content"/>
        <result property="createTime" column="create_time"/>
        <result property="articleId" column="article_id" />
        <association property="commentUser" javaType="User">
            <id property="id" column="comment_user_id"/>
            <result property="avatar" column="c_u_avatar" />
            <result property="nickname" column="c_u_nickname" />
        </association>
        <association property="parentComment" javaType="Comment">
            <id property="id" column="parent_comment_id"/>
            <association property="commentUser" javaType="User">
                <id property="id" column="p_u_id"/>
                <result property="avatar" column="p_u_avatar" />
                <result property="nickname" column="p_u_nickname" />
            </association>
        </association>
        <collection property="replayComments" select="listTreeComment" column="id"/>
    </resultMap>

    <resultMap id="simple" type="Comment">
        <id property="id" column="id"/>
        <result property="content" column="content"/>
        <result property="createTime" column="create_time"/>
        <result property="articleId" column="article_id" />
        <association property="commentUser" javaType="User">
            <id property="id" column="comment_user_id"/>
            <result property="avatarUrl" column="u_avatar_url" />
            <result property="nickname" column="u_nickname" />
        </association>
    </resultMap>

    <select id="listTreeComment" resultMap="tree" parameterType="long">
        select c.*,cu.avatar c_u_avatar, cu.nickname c_u_nickname, pu.id p_u_id, pu.avatar p_u_avatar, pu.nickname p_u_nickname
        from ((comment c LEFT JOIN comment p on p.id = c.parent_comment_id) LEFT JOIN user cu on cu.id = c.comment_user_id) LEFT JOIN user pu on pu.id = p.comment_user_id
        where c.parent_comment_id = #{id} order by create_time
    </select>

    <select id="pageComment" resultMap="simple">
        select c.*, u.avatar u_avatar, u.nickname u_nickname
        from comment c left join user u on u.id = c.comment_user_id 
        where c.article_id=#{articleId} and parent_comment_id is null
    </select>
</mapper>

這裡使用了很多LEFT JOIN,可以替換成同意思的WHERE

mapper類

@Repository
public interface CommentMapper {
    
    // 列出所有一評論
    List<Comment> listTopComment(@Param("articleId") Long articleId);
        
    //列出一級評論下子評論一級子評論的子評論
    List<Comment> listTreeComment(@Param("id") Long id);
    
}

Service

CommentService

public interface CommentService {
    List<Comment> listTopComment(Long articleId);
}

CommentServiceImpl

@Service
ublic class CommentServiceImpl implements CommentService {

    final CommentMapper commentMapper;
    public CommentServiceImpl(CommentMapper commentMapper) {
        this.commentMapper = commentMapper;
    }

    @Override
    public List<Comment> listTopComment(Long articleId) {
        // 獲取所有一級評論
        List<Comment> topComment = commentMapper.listTopComment(articleId);
        // 獲取所有子評論
        for (Comment comment : topComment) {
            comment.setReplayComments(commentMapper.listTreeComment(comment.getId()));
        }
        return topComment;
    }
}

Controller

@RestController
@RequestMapping("/comment")
public class CommentController {
    
    final CommentService commentService;
    public CommentController(CommentService commentService) {
        this.commentService = commentService;
    }

    @GetMapping("/list/{articleId}")
    List<Comment> listComment(@PathVariable("articleId") Long articleId) {
        return commentService.listTopComment(articleId);
    }
}

測試

弄幾條資料

# 文章資料
insert into article(id, title, content) value(1, '測試', '測試測試測試測試測試測試測試')

# 使用者資料
insert into user(id, nickname, avatar) value(1, '測試員1', '/images/avatar01.jpg')
insert into user(id, nickname, avatar) value(2, '測試員2', '/images/avatar02.jpg')

#評論資料
insert into comment(id, article_id, parent_comment_id, comment_user_id, content,create_time)
value(1, 1, null, 1, '她好美', NOW())
insert into comment(id, article_id, parent_comment_id, comment_user_id, content,create_time)
value(2, 1, 1, 2, '你那是饞她身子', NOW())
insert into comment(id, article_id, parent_comment_id, comment_user_id, content,create_time)
value(3, 1, 2, 2, '哈哈', NOW())

分析結果

[
    {
        "id": 1,
        "articleId": 1,
        "parentCommentId": null,
        "content": "她好美",
        "createTime": "2021-09-30T11:06:19.000+00:00",
        "updateTime": null,
        "commentUser": {
            "id": 1,
            "nickname": "測試員1",
            "avatar": null
        },
        "parentComment": null,
        "replayComments": [
            {
                "id": 2,
                "articleId": 1,
                "parentCommentId": null,
                "content": "你那是饞她身子",
                "createTime": "2021-09-30T11:07:25.000+00:00",
                "updateTime": null,
                "commentUser": {
                    "id": 2,
                    "nickname": "測試員2",
                    "avatar": "/images/avatar02.jpg"
                },
                "parentComment": {
                    "id": 1,
                    "articleId": null,
                    "parentCommentId": null,
                    "content": null,
                    "createTime": null,
                    "updateTime": null,
                    "commentUser": {
                        "id": 1,
                        "nickname": "測試員1",
                        "avatar": "/images/avatar01.jpg"
                    },
                    "parentComment": null,
                    "replayComments": []
                },
                "replayComments": [
                    {
                        "id": 3,
                        "articleId": 1,
                        "parentCommentId": null,
                        "content": "哈哈",
                        "createTime": "2021-09-30T11:36:24.000+00:00",
                        "updateTime": null,
                        "commentUser": {
                            "id": 2,
                            "nickname": "測試員2",
                            "avatar": "/images/avatar02.jpg"
                        },
                        "parentComment": {
                            "id": 2,
                            "articleId": null,
                            "parentCommentId": null,
                            "content": null,
                            "createTime": null,
                            "updateTime": null,
                            "commentUser": {
                                "id": 2,
                                "nickname": "測試員2",
                                "avatar": "/images/avatar02.jpg"
                            },
                            "parentComment": null,
                            "replayComments": []
                        },
                        "replayComments": []
                    }
                ]
            }
        ]
    }
]

我們可以看到,資料是巢狀的,這種套娃格式也是一種方案

[
    {
        "id": 1,
        "content": "她好美",
        "...": ...,
        "replayComments": [
            {
                "id": 2,
                "...": ...,
                "replayComments": [
                    {
                        "id": 3,
        				"...": ...,
        				"replayComments": null,
                    }
                ]
            }
        ]
    }
]

改套娃為二級

將套娃評論改為二級,只需要在邏輯部分將二級評論的子評論(三級)等等(四級,五級...),全部改為二級評論

Service

@Service
public class CommentServiceImpl implements CommentService {

    int commentNum = 0;
    final CommentMapper commentMapper;
    public CommentServiceImpl(CommentMapper commentMapper) {
        this.commentMapper = commentMapper;
    }

    @Override
    public List<Comment> listTopComment(Long articleId) {
        // 獲取所有一級評論
        List<Comment> topComment = commentMapper.listTopComment(articleId);
        // 獲取所有子評論
        for (Comment comment : topComment) {
            comment.setReplayComments(commentMapper.listTreeComment(comment.getId()));
        }
        return eachComment(topComment);
    }
    
    @Override
    public List<Comment> listComment(Long articleId) {
        commentNum = 0;
        // 獲取所有一級評論
        List<Comment> topComment = commentMapper.listTopComment(articleId);
        // 獲取所有子評論
        for (Comment comment : topComment) {
            comment.setReplayComments(commentMapper.listTreeComment(comment.getId()));
        }
        return eachComment(topComment);
    }

    // 迴圈遍歷頂級評論,並放入commentView中
    private List<Comment> eachComment(List<Comment> comments) {
        List<Comment> commentView = new ArrayList<>();
        for (Comment comment : comments) {
            Comment c = new Comment();
            BeanUtils.copyProperties(comment, c);
            commentView.add(c);
        }
        //合併評論的各層子代到一級子代中
        combineChildren(commentView);
        return commentView;
    }

    // 進行所有子評論的遍歷,並新增到頂級評論的子評論中
    private void combineChildren(List<Comment> comments) {
        for (Comment comment : comments) {
            commentNum++;
            List<Comment> replys1 = comment.getReplayComments();
            for (Comment reply1 : replys1) {
                //迴圈迭代找出子代,存放在tampReplys中
                recursively(reply1);
            }
            //修改頂級節點的reply集合為迭代後的集合
            comment.setReplayComments(tempReplys);
            //清除臨時存放區
            tempReplys = new ArrayList<>();
        }
    }

    //存放迭代找出的所有子代集合
    private List<Comment> tempReplys = new ArrayList<>();

    // 遞迴,將頂級評論的子評論以及子評論的子評論全部放到 tempReplys
    private void recursively(Comment comment) {
        tempReplys.add(comment);//頂節點新增到臨時存放區
        commentNum++;
        if (comment.getReplayComments().size() > 0) {
            List<Comment> replys = comment.getReplayComments();
            for (Comment reply : replys) {
                if (reply.getReplayComments().size() > 0) {
                    recursively(reply);
                } else {
                    commentNum++;
                    tempReplys.add(reply);
                }
            }
        }
    }
}

commentNum 記錄了評論數量,有需要的可以返回給前端

測試

Controller呼叫listComment方法

[
    {
        "id": 1,
        "articleId": 1,
        "parentCommentId": null,
        "content": "她好美",
        "createTime": "2021-09-30T11:06:19.000+00:00",
        "updateTime": null,
        "commentUser": {
            "id": 1,
            "nickname": "測試員1",
            "avatar": null
        },
        "parentComment": null,
        "replayComments": [
            {
                "id": 2,
                "articleId": 1,
                "parentCommentId": null,
                "content": "你那是饞她身子",
                "createTime": "2021-09-30T11:07:25.000+00:00",
                "updateTime": null,
                "commentUser": {
                    "id": 2,
                    "nickname": "測試員2",
                    "avatar": "/images/avatar02.jpg"
                },
                "parentComment": {
                    "id": 1,
                    "articleId": null,
                    "parentCommentId": null,
                    "content": null,
                    "createTime": null,
                    "updateTime": null,
                    "commentUser": {
                        "id": 1,
                        "nickname": "測試員1",
                        "avatar": "/images/avatar01.jpg"
                    },
                    "parentComment": null,
                    "replayComments": []
                },
                "replayComments": [
                    {
                        "id": 3,
                        "articleId": 1,
                        "parentCommentId": null,
                        "content": "哈哈",
                        "createTime": "2021-09-30T11:36:24.000+00:00",
                        "updateTime": null,
                        "commentUser": {
                            "id": 2,
                            "nickname": "測試員2",
                            "avatar": "/images/avatar02.jpg"
                        },
                        "parentComment": {
                            "id": 2,
                            "articleId": null,
                            "parentCommentId": null,
                            "content": null,
                            "createTime": null,
                            "updateTime": null,
                            "commentUser": {
                                "id": 2,
                                "nickname": "測試員2",
                                "avatar": "/images/avatar02.jpg"
                            },
                            "parentComment": null,
                            "replayComments": []
                        },
                        "replayComments": []
                    }
                ]
            },
            {
                "id": 3,
                "articleId": 1,
                "parentCommentId": null,
                "content": "哈哈",
                "createTime": "2021-09-30T11:36:24.000+00:00",
                "updateTime": null,
                "commentUser": {
                    "id": 2,
                    "nickname": "測試員2",
                    "avatar": "/images/avatar02.jpg"
                },
                "parentComment": {
                    "id": 2,
                    "articleId": null,
                    "parentCommentId": null,
                    "content": null,
                    "createTime": null,
                    "updateTime": null,
                    "commentUser": {
                        "id": 2,
                        "nickname": "測試員2",
                        "avatar": "/images/avatar02.jpg"
                    },
                    "parentComment": null,
                    "replayComments": []
                },
                "replayComments": []
            }
        ]
    }
]

分析一波

可以看到id為的 3 的評論已經成為二級評論了

[
    {
        "id": 1,
        "content": "她好美",
        "...": ...,
        "replayComments": [
            {
                "id": 2,
                "...": ...,
                "replayComments": [
                    {
                        "id": 3,
        				"...": ...,
        				"replayComments": [],
                    }
                ]
            },
			{
                "id": 3,
                "...": ...,
                "replayComments": []
            }
        ]
    }
]

原始碼

碼雲地址

先到這。。。
可行的二級評論實現

相關文章