最近閒來無事,在做畢業設計。前臺頁面需要一個評論功能,感覺三方的評論太複雜,功能也太多。就想著自己寫一個簡單點的,本人比較菜,方法比較笨,可能效率不高。
環境介紹
後端:SpringBoot + Mybatis
前端:Vue + Element Plus
效果展示
資料庫設計
這裡展示的是需要登入才能使用評論(依賴使用者表),如果不需要登入,可刪除comment_user_id
,新增郵箱和暱稱欄位:email
,nickname
1、評論表欄位說明
欄位名 | 型別 | 說明 |
---|---|---|
id | bigint | 主鍵 |
article_id | bigint | 評論所在文章ID |
parent_comment_id | bigint | 父評論id,null表示一級評論 |
comment_user_id | bigint | 評論釋出使用者ID |
content | longtext | 評論內容 |
create_time | datetime | 建立時間 |
只展示最基本的欄位,需要欄位自行新增,例如:
version
、deleted
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
:父評論,一對一對映,這裡你也可以直接用parentCommentIdreplayComments
:子評論,一對多對映
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": []
}
]
}
]
原始碼
先到這。。。