一,后端
1. 解决的关键点
实现这个功能的关键点在于怎么获取根评论及其相关的子评论,这里就需要了解以下加自关联查询就是通过本表的一个或者多个字段继续去表中查找相符的记录。这里通过字段originId作为查找的依据,我们先查询根结点即originId为null的记录,然后遍历这些根节点,去查询originId等于根节点的userId的记录即所要的孩子节点,这里还需要注意因为根节点的用户id可能为一样的,所以我们这里用到了utools工具包的uuid来对一个对话进行独立封装,这样就可以避免不同的对话混在一起,然后通过多表查询相关的其他字段的值后,统一以返回数据实体,返回给前端。
前端页面效果的实现就是双重的循环渲染即可实现。这里就不展开来讲了!如果不懂可以去学一学vue的v-for属性。
1.1 评论表的建立
CREATE TABLE comments (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
user_id int NOT NULL,
article_id int NOT NULL,
message text NOT NULL,
parent_id INT,
parent_name VARCHAR(50),
origin_id int,
uuid varchar(40),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user(id) on delete cascade on update cascade,
FOREIGN KEY (article_id) REFERENCES article(id) on delete cascade on update cascade
);
1.2 实体类comment
package com.ghosn.pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
//评论实体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Comment {
// 评论id
private Integer id;
//评论昵称
private String username;
//评论用户id
private Integer userId;
//评论文章id
private Integer articleId;
// 评论内容
private String message;
//评论的父id
private Integer parentId;
//评论的根id
private Integer originId;
//评论组的唯一标识符
private String uuid;
//评论父昵称
private String parentName;
//评论时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createdAt;
}
1.3 数据实体返回类
package com.ghosn.pojo.dat;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
/**
* 数据实体返回类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommentDat {
private Integer id;
// 评论人昵称
private String username;
// 评论人的头像
private String userHeaderImage;
// 评论人的id
private Integer userId;
// 评论文章id
private Integer articleId;
// 评论文章标题
private String articleTitle;
// 评论内容
private String message;
// 回复人id
private Integer parentId;
// 回复人昵称
private String parentName;
// 回复人头像
private String parentHeaderImage;
// 根评论人
private Integer originId;
// 评论组的唯一标识符
private String uuid;
// 根评论人的昵称
private String originName;
// 评论时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createdAt;
// 评论的子评论
private List<CommentDat> children;
}
1.4 controller层实现添加评论和删除评论
import com.ghosn.mapper.CommentMapper;
import com.ghosn.pojo.Comment;
import com.ghosn.pojo.dat.ArticleDat;
import com.ghosn.pojo.dat.CommentDat;
import com.ghosn.service.CommentService;
import com.ghosn.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.util.List;
/**
* 评论模块
*/
@RestController
@RequestMapping("/comment")
public class CommentController {
@Autowired
private CommentService commentService;
@Autowired
private CommentMapper commentMapper;
/**
* 添加评论
* @param comment
* @return
*/
@PostMapping("/add")
public Result add(@RequestBody Comment comment) {
try {
commentService.add(comment);
return Result.success();
}catch (Exception e) {
throw new ServiceException(Constants.CODE_401,"评论失败!");
}
}
/**
* 获取文章的所有评论
* @param id
* @return
*/
@GetMapping("/get/{id}")
public Result get(@PathVariable Integer id){
return Result.success(commentService.get(id));
}
/**
* 删除评论
* @param id:用户的id
* @return
*/
@GetMapping("/del/{id}")
public Result delete(@PathVariable Integer id){
try {
commentMapper.delete(id);
return Result.success();
}catch (Exception e) {
throw new ServiceException(Constants.CODE_401,"删除失败!");
}
}
}
1.5 service层的实现
package com.ghosn.service;
import com.ghosn.mapper.ArticleMapper;
import com.ghosn.mapper.CommentMapper;
import com.ghosn.pojo.Comment;
import com.ghosn.pojo.User;
import com.ghosn.pojo.dat.CommentDat;
import com.ghosn.utils.StringUtil;
import org.apache.ibatis.annotations.Delete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CommentService {
@Autowired
private CommentMapper commentMapper;
private final StringUtil stringUtil = new StringUtil();
@Autowired
private ArticleMapper articleMapper;
/**
* 获取文章的所有评论信息
* @param id
* @return
*/
public List<CommentDat> get(Integer id) {
List<CommentDat> comments = commentMapper.getAllRoot(id);
comments.forEach(comment -> {
comment.setMessage(stringUtil.parseSimpleChar(comment.getMessage()));
comment.setUserHeaderImage(commentMapper.getUserInfo(comment.getUserId()).get(0).getHeaderImage());
List<CommentDat> children = commentMapper.getChildren(comment.getUserId(),id,comment.getUuid());
children.forEach(child -> {
child.setUserHeaderImage(commentMapper.getUserInfo(child.getUserId()).get(0).getHeaderImage());
child.setMessage(stringUtil.parseSimpleChar(child.getMessage()));
});
comment.setChildren(children);
});
return comments;
}
/**
* 添加评论
* @param comment
* @throws Exception
*/
public void add(Comment comment) throws Exception {
comment.setMessage(stringUtil.replaceSimpleChar(comment.getMessage()));
if(comment.getOriginId() == null && comment.getParentId() == null){
String uuid = IdUtil.fastSimpleUUID();
comment.setUuid(uuid);
}
commentMapper.add(comment);
}
}
1.6 Mapper层的实现
package com.ghosn.mapper;
import com.ghosn.pojo.Comment;
import com.ghosn.pojo.User;
import com.ghosn.pojo.dat.CommentDat;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface CommentMapper {
void add(Comment comment) throws Exception;
List<CommentDat> getAllRoot(Integer id);
@Select("select * from user where id =${id}")
List<User> getUserInfo(Integer id);
@Select("select * from comments where origin_id=${originId} and article_id=${articleId} and uuid='${uuid}' order by created_at desc ")
List<CommentDat> getChildren(Integer originId,Integer articleId,String uuid);
}
1.7 CommentMapper.xml的实现
<?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.ghosn.mapper.CommentMapper">
<insert id="add">
<if test="parentId != null and originId != null">
insert into comments(username, user_id, article_id, message, parent_id, parent_name,origin_id,uuid)
values ('${username}', ${userId}, ${articleId}, '${message}', ${parentId}, '${parentName}',${originId},'${uuid}')
</if>
<if test="parentId == null and originId == null">
insert into comments(username, user_id, article_id, message,uuid)
values ('${username}', ${userId}, ${articleId}, '${message}','${uuid}')
</if>
</insert>
<select id="getAllRoot" resultType="com.ghosn.pojo.dat.CommentDat">
select * from comments where article_id=${id} and parent_id is null and origin_id is null order by created_at desc
</select>
</mapper>
二,前端
1. template部分
<div class="commentBox">
<h3>评论列表</h3>
<div class="commentList" v-for="item in comments" :key="item.id">
<!-- 父评论 -->
<el-row class="parent">
<div class="commentUserInfo">
<el-image :src="item.userHeaderImage" alt="" lazy></el-image>
<div class="userNickname">
<span class="nickname">{{ item.username }}</span>
<span class="createdAt">{{ item.createdAt }}</span>
</div>
</div>
<div class="comment">
<p>{{ item.message }}</p>
</div>
<div class="buttons">
<el-button @click="reply(item.userId, item.username, item.userId,item.uuid)" style="margin-left: 5px;"
type="primary" plain size="mini">回复
</el-button>
<el-button @click="del(item.id)" v-if="user.id === item.userId" style="margin-left: 5px;" type="danger"
plain size="mini">删除
</el-button>
</div>
</el-row>
<!-- 子评论 -->
<el-row class="children" v-for="child in item.children" :key="child.id">
<div class="commentUserInfo">
<el-image :src="child.userHeaderImage" alt="" lazy></el-image>
<div class="userNickname">
<span class="nickname">{{ child.username }} 回复 {{ child.parentName }}</span>
<span class="createdAt">{{ child.createdAt }}</span>
</div>
</div>
<div class="comment">
<p>{{ child.message }}</p>
</div>
<div class="buttons">
<el-button @click="reply(child.userId, child.username, item.userId,item.uuid)" style="margin-left: 5px;"
type="primary" plain size="mini">回复
</el-button>
<el-button @click="del(child.id)" v-if="user.id === child.userId" style="margin-left: 5px;"
type="danger" plain size="mini">删除
</el-button>
</div>
</el-row>
</div>
</div>
2. script部分
<script>
export default {
name: "DetailArticle",
data() {
return {
scrollTopNum: '',
replyFlag: false,
user: localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')) : {},
starStatus: false,
commentForm: {
message: ''
},
comments: [],
};
},
methods: {
del(id) {
this.request.get('/comment/del/' + id).then(res => {
if (res.code === '200') {
this.$message.success("删除成功!")
this.getComments()
} else {
this.$message.error(res.msg)
}
})
},
getComments() {
this.request.get('/comment/get/' + this.$route.params.id).then(res => {
this.comments = res.data
})
},
reset() {
this.commentForm = {}
},
cancel() {
this.commentForm = {}
this.replyFlag = false
},
reply(id, name, originId,uuid) {
this.replyFlag = true;
this.commentForm = {};
this.commentForm.parentId = id
this.commentForm.parentName = name
this.commentForm.originId = originId
this.commentForm.uuid = uuid
},
add() {
this.commentForm.userId = this.user.id
this.commentForm.username = this.user.nickname
this.commentForm.articleId = this.$route.params.id
if (this.commentForm.message === '') {
this.$message.error("评论内容不能为空!")
} else {
this.request.post('/comment/add', this.commentForm).then(res => {
if (res.code === '200') {
this.$message.success("发布成功!")
this.getComments()
this.cancel()
} else {
this.$message.error(res.msg)
}
})
}
},
created() {
this.getComments()
}
}
</script>
3. 效果展示
如果喜欢的话,点点关注吧!希望对大家有用!