——本文转自onestar :SpringBoot和Mybatis实现评论楼中楼功能(一张表搞定
在网上搜索了很多,发现很多都是用两张表或者使用jpa实现的,本篇文章将讲述使用一张表来实现评论回复楼中楼功能,使用Mybatis作为持久层框架,有图有真相,先来看看最终效果
首先来看看有哪些字段,既然是评论回复,你觉得应该有哪些字段呢,带着功能去思考这个问题
首先是主键(id),既然是评论,必须要有评论人的姓名(nickname),为了以后能联系到评论人,需要评论人的邮箱(email),然后就是评论内容(content),为了方便显示,还需要评论人的头像(avatar),评论的时间当然少不了(create_time),既然是一张表,要如何确定回复的所属关系呢,那就需要知道是回复谁的评论(parent_comment_id)。bingo!这些字段就OK了,可能你会问,那回复怎么办,问题不大,可以在实体类中创建一个回复评论的集合replyComments,用来存储回复消息。
建表语句如下:
create database comment; DROP TABLE IF EXISTS `comment`; CREATE TABLE `comment` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `nickname` varchar(255) DEFAULT NULL, `email` varchar(255) DEFAULT NULL, `content` varchar(255) DEFAULT NULL, `avatar` varchar(255) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `parent_comment_id` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;如图,选择相应的组件,创建SpringBoot项目
将application.properties配置文件改成yml后缀,即application.yml,主要对thymeleaf模板、数据库、mybatis、评论头像进行配置,配置如下:
spring: #配置thymeleaf模板 thymeleaf: mode: HTML #配置数据库 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/comment?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC username: root password: 806188 #配置mybatis mybatis: type-aliases-package: com.star.entity mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true #配置评论头像 comment.avatar: /images/avatar.png根据id为“-1”查询出所有父评论 根据父评论的id查询出一级子回复 根据子回复的id循环迭代查询出所有子集回复 将查询出来的子回复放到一个集合中 要注意将父评论的姓名给set进去,代码如下:
package com.star.service.impl; import com.star.dao.CommentDao; import com.star.entity.Comment; import com.star.service.CommentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * @Description: */ @Service public class CommentServiceImpl implements CommentService { @Autowired private CommentDao commentDao; //存放迭代找出的所有子代的集合 private List<Comment> tempReplys = new ArrayList<>(); /** * @Description: 查询评论 * @Param: * @Return: 评论消息 */ @Override public List<Comment> listComment() { //查询出父节点 List<Comment> comments = commentDao.findByParentIdNull(Long.parseLong("-1")); for(Comment comment : comments){ Long id = comment.getId(); String parentNickname1 = comment.getNickname(); List<Comment> childComments = commentDao.findByParentIdNotNull(id); //查询出子评论 combineChildren(childComments, parentNickname1); comment.setReplyComments(tempReplys); tempReplys = new ArrayList<>(); } return comments; } /** * @Description: 查询出子评论 * @Param: childComments:所有子评论 * @Param: parentNickname1:父评论的姓名 * @Return: */ private void combineChildren(List<Comment> childComments, String parentNickname1) { //判断是否有一级子回复 if(childComments.size() > 0){ //循环找出子评论的id for(Comment childComment : childComments){ String parentNickname = childComment.getNickname(); childComment.setParentNickname(parentNickname1); tempReplys.add(childComment); Long childId = childComment.getId(); //查询二级以及所有子集回复 recursively(childId, parentNickname); } } } /** * @Description: 循环迭代找出子集回复 * @Param: childId:子评论的id * @Param: parentNickname1:子评论的姓名 * @Return: */ private void recursively(Long childId, String parentNickname1) { //根据子一级评论的id找到子二级评论 List<Comment> replayComments = commentDao.findByReplayId(childId); if(replayComments.size() > 0){ for(Comment replayComment : replayComments){ String parentNickname = replayComment.getNickname(); replayComment.setParentNickname(parentNickname1); Long replayId = replayComment.getId(); tempReplys.add(replayComment); //循环迭代找出子集回复 recursively(replayId,parentNickname); } } } @Override //存储评论信息 public int saveComment(Comment comment) { comment.setCreateTime(new Date()); return commentDao.saveComment(comment); } } 前端页面comment.html 最后是前端显示页面,这里采用semantic-ui作为UI组件,使用thymeleaf模板解析,记得在static文件夹下创建images文件夹,并放一张名为avatar.png的图片 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>评论楼中楼功能</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css"> </head> <body> <div id="waypoint" class="m-margin- animated fadeIn"> <div class="ui container m-opacity box-shadow-max"> <div class="ui bottom attached segment"> <!--评论区域列表--> <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="../static/images/avatar.png" th:src="@{${comment.avatar}}"> </a> <div class="content"> <a class="author" > <span th:text="${comment.nickname}">小红</span> </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}"> 愿你走出半生,归来仍是少年! </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="../static/images/avatar.png" th:src="@{${reply.avatar}}"> </a> <div class="content"> <a class="author" > <span th:text="${reply.nickname}">小白</span> <span th:text="|@ ${reply.parentNickname}|" 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}"> 你也是! </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 id="comment-form" class="ui form"> <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="姓名" th:value="${session.user}!=null ? ${session.user.nickname}"> </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="邮箱" th:value="${session.user}!=null ? ${session.user.email}"> </div> </div> <div class="field m-margin-bottom-small m-mobile-wide"> <button id="commentpost-btn" type="button" class="ui teal button m-mobile-wide"><i class="edit icon"></i>发布</button> </div> </div> </div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/jquery@3.2/dist/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js"></script> <script th:inline="javascript"> //评论表单验证 $('.ui.form').form({ fields: { title: { identifier: 'content', rules: [{ type: 'empty', prompt: '请输入评论内容' } ] }, content: { identifier: 'nickname', rules: [{ type: 'empty', prompt: '请输入你的大名' }] }, type: { identifier: 'email', rules: [{ type: 'email', prompt: '请填写正确的邮箱地址' }] } } }); $(function () { $("#comment-container").load(/*[[@{/comment}]]*/"comment/"); }); $('#commentpost-btn').click(function () { var boo = $('.ui.form').form('validate form'); if (boo) { console.log('校验成功'); postData(); } else { console.log('校验失败'); } }); function postData() { $("#comment-container").load(/*[[@{/comment}]]*/"",{ "parentComment.id" : $("[name='parentComment.id']").val(), "nickname": $("[name='nickname']").val(), "email" : $("[name='email']").val(), "content" : $("[name='content']").val() },function (responseTxt, statusTxt, xhr) { // $(window).scrollTo($('#goto'),0); clearContent(); }); } function clearContent() { $("[name='nickname']").val(''); $("[name='email']").val(''); $("[name='content']").val(''); $("[name='parentComment.id']").val(-1); $("[name='content']").attr("placeholder", "请输入评论信息..."); } 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); } </script> </body> </html> 目录结构