一张表实现楼层评论功能
由于本博客站原型的DAO层是Springdata JPA实现的。本站将DAO层使用Mybatis进行改写,并且后端几乎都是自己重新独立编写的。之前在做评论功能时考虑到实现起来可能有点复杂,就先将这一块搁置起来,只实现了一级评论的功能,并不能实现楼层评论。
这几天重新对博客项目进行升级改造,考虑到评论功能的缺陷,所以重新做了评论功能,实现用一张表实现楼层评论的功能。
先看前端页面原型,本站前端页面由开源项目提供,前端是不需要自己设计的,通过看前端需要怎样的数据,后端提供相应的数据即可。
添加评论form表单:
<form id="blog-form" th:action="@{/comments}" method="post" class="ui form">
<div id="comment-form" class="ui form">
<input type="hidden" name="blogId" th:value="${blog.id}">
<input type="hidden" name="parentComment.id" value="-1">
<div class="field">
<textarea name="content" placeholder="请输入评论信息..."></textarea>
</div>
<div class="fields">
<div class="field m-mobile-wide m-margin-bottom-small">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" name="nickname" placeholder="姓名">
</div>
</div>
<div class="field m-mobile-wide m-margin-bottom-small">
<div class="ui left icon input">
<i class="mail icon"></i>
<input type="text" name="email" placeholder="邮箱">
</div>
</div>
<div class="field m-margin-bottom-small m-mobile-wide">
<button id="commentpost-btn" type="submit" class="ui teal button m-mobile-wide"><i class="edit icon"></i>发布</button>
</div>
</div>
</div>
</form>
相关JS:
function reply(obj) {
var commentId = $(obj).data('commentid');
var commentNickname = $(obj).data('commentnickname');
$("[name='content']").attr("placeholder", "@"+commentNickname).focus();
$("[name='parentComment.id']").val(commentId);
$(window).scrollTo($('#comment-form'),500);
}
展示评论功能:
<!--留言信息-->
<div class="ui attached bottom segment" th:if="${blog.commentabled}">
<div id="comment-container" class="ui teal segment">
<div th:fragment="commentList">
<div class="ui threaded comments" style="max-width: 100%;">
<h3 class="ui dividing header">评论</h3>
-- <div class="comment" th:each="comment : ${comments}">
<a class="avatar">
<img src="https://unsplash.it/100/100?image=1005" th:src="@{${comment.avatar}}">
</a>
<div class="content">
<a class="author" >
<span th:text="${comment.nickname}">Matt</span>
<!--<div class="ui mini basic teal left pointing label m-padded-mini" th:if="${comment.adminComment}">博主</div>-->
</a>
<div class="metadata">
<span class="date" th:text="${#dates.format(comment.createTime,'yyyy-MM-dd HH:mm')}">Today at 5:42PM</span>
</div>
<div class="text" th:text="${comment.content}">
How artistic!
</div>
<div class="actions">
<a class="reply" data-commentid="1" data-commentnickname="Matt" th:attr="data-commentid=${comment.id},data-commentnickname=${comment.nickname}" onclick="reply(this)">回复</a>
</div>
</div>
<div class="comments" th:if="${#arrays.length(comment.replyComments)}>0">
<div class="comment" th:each="reply : ${comment.replyComments}">
<a class="avatar">
<img src="https://unsplash.it/100/100?image=1005" th:src="@{${reply.avatar}}">
</a>
<div class="content">
<a class="author" >
<span th:text="${reply.nickname}">小红</span>
<div class="ui mini basic teal left pointing label m-padded-mini" th:if="${reply.adminComment}">博主</div>
<span th:text="|@ ${reply.parentComment.nickname}|" class="m-teal">@ 小白</span>
</a>
<div class="metadata">
<span class="date" th:text="${#dates.format(reply.createTime,'yyyy-MM-dd HH:mm')}">Today at 5:42PM</span>
</div>
<div class="text" th:text="${reply.content}">
How artistic!
</div>
<div class="actions">
<a class="reply" data-commentid="1" data-commentnickname="Matt" th:attr="data-commentid=${reply.id},data-commentnickname=${reply.nickname}" onclick="reply(this)">回复</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
可见,新增评论信息需要往后端传blogId,parentComment.id,content,nickname,email这些信息,展示评论需要往前端传一个Comment类型的List集合,每一个Comment又包含一个Comment类型的List集合,作为该父评论的子评论集。
所以,合理的建表语句:
CREATE TABLE `t_comment` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ip` varchar(20) DEFAULT NULL,
`nickname` varchar(20) DEFAULT NULL,
`email` varchar(30) DEFAULT NULL,
`content` varchar(255) DEFAULT NULL,
`avatar` varchar(100) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`blog_id` int(11) DEFAULT NULL,
`parent_comment_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=225 DEFAULT CHARSET=utf8
写评论就是向表中添加数据,没什么难度。主要就是获取评论时将所有父评论及子评论全部合理获取出来的问题。
DAO层接口:
@Mapper
public interface CommentDao {
//不使用@Param注解
//不使用@Param注解时,参数只能有一个,并且是Javabean。在SQL语句里可以引用JavaBean的属性,而且只能引用JavaBean的属性。
//查询父级评论
Comment findByParentCommentId(Integer parentCommentId);
//通过父评论查询其子级评论
List<Comment> findByBlogIdParentIdNull(@Param("blogId") Integer blogId, @Param("blogParentId") Integer blogParentId);
//添加一个评论
int saveComment(Comment comment);
}
Mapper映射:
<?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="cn.ftf.myblog.dao.CommentDao">
<insert id="saveComment" parameterType="cn.ftf.myblog.pojo.Comment">
insert into t_comment (nickname,email,content,avatar,create_time,blog_id,parent_comment_id)
values (#{nickname},#{email},#{content},#{avatar},#{createTime},#{blogId},#{parentCommentId});
</insert>
<select id="findByBlogIdParentIdNull" resultType="cn.ftf.myblog.pojo.Comment">
select c.id,c.nickname,c.email,c.content,c.avatar,c.create_time createTime,c.blog_id blogId,c.parent_comment_id parentCommentId
from t_comment c
where c.blog_id = #{blogId} and c.parent_comment_id = #{blogParentId}
order by c.create_time desc
</select>
<select id="findByParentCommentId" resultType="cn.ftf.myblog.pojo.Comment">
select c.id,c.nickname,c.email,c.content,c.avatar,c.create_time createTime,c.blog_id blogId,
c.parent_comment_id parentCommentId from t_comment c where c.parent_comment_id = #{parentCommentId}
</select>
</mapper>
接下来是service实现类,也是功能的核心处理模块,楼层评论的重点和难点就在这里,用两个方法巧妙精简的得到评论内容集合,包含前端所需要的所有信息。
@Service
public class CommentServiceImpl implements CommentService {
@Autowired
private CommentDao commentDao;
@Autowired
private BlogDao blogDao;
@Override
public List<Comment> listCommentByBlogId(Integer blogId) {
List<Comment> comments = commentDao.findByBlogIdParentIdNull(blogId, -1); //装所有一级评论的集合
if(comments!=null&&comments.size()>0){
for(Comment comment:comments){
List<Comment> subComments=new ArrayList<>(); //装所有子评论的集合
comment.setReplyComments(listAllSubComment(comment,subComments));
}
}
return comments;
}
private List<Comment> listAllSubComment(Comment comment,List<Comment> subComments){
List<Comment> oneSubComments=commentDao.findByParentCommentId(comment.getId());
if(oneSubComments!=null&&oneSubComments.size()>0) {
for (Comment comment1 : oneSubComments) {
comment1.setParentComment(comment);
subComments.add(comment1);
listAllSubComment(comment1, subComments);
}
}
return subComments;
}
@Override
//接收回复的表单
public int saveComment(Comment comment) {
System.out.println("comment:" + comment);
comment.setCreateTime(new Date());
//comment.setBlog(blogDao.getDetailedBlog(comment.getBlogId()));
return commentDao.saveComment(comment);
}
}
有了Service层的基础,接下来只需要Controller层简单调用就可以了。
List<Comment> comments = commentService.listCommentByBlogId(id);
model.addAttribute("comments", comments);