效果:

  • 实体层
  • 业务层:
  • 控制层:
  • 前端:

实体层

省略了get set方法

@Entity
@Table(name = "t_comment")
public class Comment {@Id@GeneratedValueprivate Long id;private String nickname;private String email;private String content;private String avatar;@Temporal(TemporalType.TIMESTAMP)private Date createTime;@ManyToOneprivate Blog blog;@OneToMany(mappedBy = "parentComment")private List<Comment> replyComments = new ArrayList<>();@ManyToOneprivate Comment parentComment;private boolean adminComment;
}

业务层:

@Service
public class CommentServiceImpl implements CommentService {@Autowiredprivate CommentRepository commentRepository;@Overridepublic List<Comment> listCommentByBlogId(Long blogId) {Sort sort = new Sort("createTime");List<Comment> comments = commentRepository.findByBlogIdAndParentCommentNull(blogId,sort);return eachComment(comments);}@Transactional@Overridepublic Comment saveComment(Comment comment) {Long parentCommentId = comment.getParentComment().getId();if (parentCommentId != -1) {comment.setParentComment(commentRepository.findOne(parentCommentId));} else {comment.setParentComment(null);}comment.setCreateTime(new Date());return commentRepository.save(comment);}/*** 循环每个顶级的评论节点* @param comments* @return*/private List<Comment> eachComment(List<Comment> comments) {List<Comment> commentsView = new ArrayList<>();for (Comment comment : comments) {Comment c = new Comment();BeanUtils.copyProperties(comment,c);commentsView.add(c);}//合并评论的各层子代到第一级子代集合中combineChildren(commentsView);return commentsView;}/**** @param comments root根节点,blog不为空的对象集合* @return*/private void combineChildren(List<Comment> comments) {for (Comment comment : comments) {List<Comment> replys1 = comment.getReplyComments();for(Comment reply1 : replys1) {//循环迭代,找出子代,存放在tempReplys中recursively(reply1);}//修改顶级节点的reply集合为迭代处理后的集合comment.setReplyComments(tempReplys);//清除临时存放区tempReplys = new ArrayList<>();}}//存放迭代找出的所有子代的集合private List<Comment> tempReplys = new ArrayList<>();/*** 递归迭代,剥洋葱* @param comment 被迭代的对象* @return*/private void recursively(Comment comment) {tempReplys.add(comment);//顶节点添加到临时存放集合if (comment.getReplyComments().size()>0) {List<Comment> replys = comment.getReplyComments();for (Comment reply : replys) {tempReplys.add(reply);if (reply.getReplyComments().size()>0) {recursively(reply);}}}}
}

控制层:

@Controller
public class CommentController {@Autowiredprivate CommentService commentService;@Autowiredprivate BlogService blogService;@Value("${comment.avatar}")//comment.avatar: /images/avatar.pngprivate String avatar;@GetMapping("/comments/{blogId}")public String comments(@PathVariable Long blogId, Model model) {model.addAttribute("comments", commentService.listCommentByBlogId(blogId));return "blog :: commentList";//<div th:fragment="commentList">}//   <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>@PostMapping("/comments")public String post(Comment comment, HttpSession session) {Long blogId = comment.getBlog().getId();comment.setBlog(blogService.getBlog(blogId));User user = (User) session.getAttribute("user");if (user != null) {comment.setAvatar(user.getAvatar());comment.setAdminComment(true);} else {comment.setAvatar(avatar);}commentService.saveComment(comment);return "redirect:/comments/" + blogId;}
}

前端:

<!--留言区域列表--><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>&nbsp;<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')}"></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 id="comment-form" class="ui form"><input type="hidden" name="blog.id" 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="姓名" 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>

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(/*[[@{/comments/{id}(id=${blog.id})}]]*/"comments/6");});$('#commentpost-btn').click(function () {var boo = $('.ui.form').form('validate form');if (boo) {console.log('校验成功');postData();} else {console.log('校验失败');}});function postData() {$("#comment-container").load(/*[[@{/comments}]]*/"",{"parentComment.id" : $("[name='parentComment.id']").val(),"blog.id" : $("[name='blog.id']").val(),"nickname": $("[name='nickname']").val(),"email"   : $("[name='email']").val(),"content" : $("[name='content']").val()},function (responseTxt, statusTxt, xhr) {//        $(window).scrollTo($('#comment-container'),500);clearContent();});}function clearContent() {$("[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);}

springboot+thymeleaf+jpa博客多级评论展示案例相关推荐

  1. SpringBoot的个人博客管理系统(毕业论文范文)

     基于spring boot 的个人博客系统 摘要 随着社会上计算机行业的发展,记录知识的手段就不仅限于笔记,逐渐由纸质衍生到电子笔记,继而随着电子记录文档方式的多样性发展,大家将所学的知识进行回顾总 ...

  2. Springboot搭建个人博客系列

    前言 为什么想要搭建这个博客? 我还记得,在大二寒假的某天,同往常一样的在家解决这某个bug,不停地问度娘,很巧的碰到了一个同行在他的博客中完美的记录了我的bug的解决方案,随后我又看了看他写的其他博 ...

  3. 基于SpringBoot的个人博客系统设计与实现

    摘 要 作为计算机的学生,我们学习的方法是通过老师,书籍,论文等.对很多从事计算机方面的人来说,他们学习知识是通过官方文档,以及相关博客.现在知名博客网站有很多,比如CSDN,博客园,还有全球最知名的 ...

  4. 基于SpringBoot的个人博客系统

    项目编号:BS-PT-042 该博客是基于SpringBoot + Mybatis + Thymeleaf 等技术实现的 Java 博客系统,页面美观.功能齐全.部署简单及完善的代码,做为毕设项目的话 ...

  5. SpringBoot+Vue+Mybatis-plus 博客(一):完成博客后台前端登录页面、后端登录接口

    SpringBoot+Vue+Mybatis-plus 博客:个人博客介绍及效果展示 SpringBoot+Vue+Mybatis-plus 博客(一):完成博客后台前端登录页面.后端登录接口 Spr ...

  6. 基于 Vue 和 SpringBoot 实现的博客系统(附源码)

    今天给大家分享一个基于 Vue 和 SpringBoot 实现的博客系统! 源码在文章结尾处,大家自行下载即可,我设置的免积分下载! 一.主要功能 1.前端 后台管理系统采用Vue开发. 文章模块,支 ...

  7. 教学妹开发springboot+vue的博客论坛系统,so esay

    今天给大家介绍一个简单的系统. 基于springboot+vue的博客论坛系统,如果你想学习更多的项目源码,可以在文章的末尾领取源码资料. 当然前面已经开源了很多的项目源码,都是免费学习的: 1,挑战 ...

  8. (附源码)springboot掌上博客系统 毕业设计 063131

    Springboot掌上博客系统的设计与实现 摘 要 掌上博客系统是当今网络的热点,博客技术的出现使得每个人可以零成本.零维护地创建自己的网络媒体,Blog站点所形成的网状结构促成了不同于以往社区的B ...

  9. SpringBoot+Vue+Mybatis-plus 博客(七):完成友链管理前后端对接

    SpringBoot+Vue+Mybatis-plus 博客:个人博客介绍及效果展示 SpringBoot+Vue+Mybatis-plus 博客(一):完成博客后台前端登录页面.后端登录接口 Spr ...

最新文章

  1. centos 非root用户(普通用户)替换yum安装软件方法
  2. C语言的双向链表头插法和尾插法,指定节点删除
  3. 灵魂调参师被AutoGluon打爆,李沐:调得一手好参的时代要过去了
  4. asp.net数据格式的Format-- DataFormatString
  5. 撕掉伪善——用人话解释马云的996两次发言
  6. 部署DNS服务和管理DNS
  7. 2020北京智源大会 图神经网络专题 总结
  8. 如何把Linux工具里的“军刀”BusyBox移植到RT-Thread Smart?
  9. kubernetes集群使用GPU及安装kubeflow1.0.RC操作步骤
  10. this指向 - 总结
  11. 一个IO的传奇一生 (9) -- Noop和Deadline调度器
  12. 通过还款计划表监控还款异常
  13. 浅谈长尾理论--《Makers》读后感
  14. java 长整型long_C语言和java 长整型为何打印不同?
  15. MDIO接口FPGA代码
  16. 一文让你理解什么是shallow heap及retained heap
  17. PHP 把ofd格式文件转PDF,打开OFD格式文件及将OFD格式文件转换成PDF文件
  18. 下列签名无效: EXPKEYSIG CDFB5FA52007B954 Metasploit 解决办法
  19. 将xlsx文件转换成CSV文件方法
  20. vue中倒计时(日,时,分,秒)的计算和当前时间计时读秒

热门文章

  1. python无需修改是什么特性_用户编写的python程序无需修改就可以在不同的平台运行,是python的什么特征...
  2. mysql底层用什么写的_天天写order by,你知道Mysql底层如何执行吗?
  3. 大庆师范学院计算机系徐媛老师,大庆师范学院课程表(未添加英语课).xls
  4. android单片机蓝牙小车,手把手教你做蓝牙小车
  5. cpp加密php,常用的数据加密规则算法(php包含MD5和RSA)
  6. php篮球比赛,篮球数据API接口 - 【篮球比赛动画直播变化数据】API调用示例代码...
  7. 弹出确定_Redmi K30 Pro再剧透:弹出式全面屏,没有高刷
  8. 华为校园招聘Java机试题
  9. 【大数据教程】HDFS基本架构、基本命令、回收站机制和API操作
  10. 深圳内推 | 粤港澳大湾区数字经济研究院招聘NLP算法研究员(可实习)