实现思路:
数据库设计:评论表需要定义出当前博客id以便做关联,因为评论需要有回复功能,则需要定义当前评论有无上一级评论,需要定义出上级评论id;博主回复评论需要带有标签,所以需要定义Boolean类型判断是否为博主。
代码方面:点击评论需要获取当前博客id与自己评论数据进行插入,点击回复按钮需要获取上一条评论的id以及用户姓名作为回复,回复成功后,刷新页面,后台则是在数据库中查找出所有parentCommentId为-1的进行遍历,因为上级id为-1则证明当前评论无父节点。在通过对父节点id的遍历查询出所有对应评论的子节点。
页面展示
博客实体类
1 |
private Long id; private String nickname; private String email; private String content; //头像 private String avatar; private Date createTime; private Long blogId; //关联博客id private Long parentCommentId; //上级评论id private String parentNickname; //回复评论 private List<Comment> replyComments = new ArrayList<>(); private Comment parentComment; private boolean adminComment; //是不是博主 private DetailedBlog blog; |
评论的博客表单html
1 |
<div id="comment-form" class="ui form"> <!--获取当前博客id进行评论 使用隐藏域--> <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="姓名" 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> |
点击发布按钮触发
1 |
$('#commentpost-btn').click(function () { var boo = $('.ui.form').form('validate form'); if (boo) { console.log('校验成功'); postData(); } else { console.log('校验失败'); } }); //评论表单验证 $('.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: '请填写正确的邮箱地址' }] } } }); |
表单验证后发送请求到后端,提交成功之后清空表单
1 |
function postData() { $("#comment-container").load(/*[[@{/comments}]]*/"",{ "parentComment.id" : $("[name='parentComment.id']").val(), "blogId" : $("[name='blogId']").val(), "nickname": $("[name='nickname']").val(), "email" : $("[name='email']").val(), "content" : $("[name='content']").val() },function (responseTxt, statusTxt, xhr) { $(window).scrollTo($('#goto'),500); //提交成功之后滚动到评论位置 clearContent(); }); } function clearContent() { $("[name='nickname']").val(''); $("[name='email']").val(''); $("[name='content']").val(''); $("[name='parentComment.id']").val(-1); $("[name='content']").attr("placeholder", "请输入评论信息..."); } |
点击回复按钮,在评论区显示回复给哪个用户
1 |
<a class="reply" data-messageid="1" data-messagenickname="Matt" th:attr="data-messageid=${reply.id},data-messagenickname=${reply.nickname}" onclick="reply(this)">回复</a> |
对应函数
1 |
function reply(obj) { var messageId = $(obj).data('messageid'); var messageNickname = $(obj).data('messagenickname'); $("[name='content']").attr("placeholder", "@"+messageNickname).focus();//在回复时显示 @对应人 $("[name='parentMessage.id']").val(messageId);//为隐藏域赋值 $(window).scrollTo(0,500); //滚动 } |
对应后端代码
1 |
@Controller public class CommentController { @Autowired private CommentService commentService; @Autowired private BlogService blogService; @Value("${comment.avatar}") private String avatar; // 查询评论列表 @GetMapping("/comments/{blogId}") public String comments(@PathVariable Long blogId, Model model) { List<Comment> comments = commentService.listCommentByBlogId(blogId); model.addAttribute("comments", comments); return "blog :: commentList"; //返回到blog下面的commentList片段 } // 新增评论 @PostMapping("/comments") public String post(Comment comment, HttpSession session,Model model) { Long blogId = comment.getBlogId(); User user = (User) session.getAttribute("user"); if (user != null) { comment.setAvatar(user.getAvatar()); comment.setAdminComment(true); } else { //设置头像 comment.setAvatar(avatar); } //页面中定义为-1 不会出现空指针 if (comment.getParentComment().getId() != null) { //获取父对象设置对应关系 comment.setParentCommentId(comment.getParentComment().getId()); } commentService.saveComment(comment); List<Comment> comments = commentService.listCommentByBlogId(blogId); model.addAttribute("comments", comments); return "blog :: commentList"; } |
列表设置,前端遍历显示层级关系
1 |
<div class="ui bottom attached 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> <a class="delete" href="#" th:href="@{/comment/{param1}/{param2}/delete(param1=${comment.blogId},param2=${comment.id})}" onclick="return confirm('确定要删除该评论吗?')" th:if="${session.user}">删除</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.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}"> 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> <a class="delete" href="#" th:href="@{/comment/{param1}/{param2}/delete(param1=${reply.blogId},param2=${reply.id})}" onclick="return confirm('确定要删除该评论吗!')" th:if="${session.user}">删除</a> </div> </div> </div> </div> </div> </div> </div> </div> |
后端实现逻辑:先获取顶级的数据,在一层一层往下找、放入集合
1 |
@Service public class CommentServiceImpl implements CommentService { @Autowired private CommentDao commentDao; @Autowired private BlogDao blogDao; //存放迭代找出的所有子代的集合 private List<Comment> tempReplys = new ArrayList<>(); @Override public List<Comment> listCommentByBlogId(Long blogId) { //查询出父节点 List<Comment> comments = commentDao.findByBlogIdParentIdNull(blogId, Long.parseLong("-1")); for(Comment comment : comments){ Long id = comment.getId(); String parentNickname1 = comment.getNickname(); List<Comment> childComments = commentDao.findByBlogIdParentIdNotNull(blogId,id); // 查询出子评论 combineChildren(blogId, childComments, parentNickname1); comment.setReplyComments(tempReplys); tempReplys = new ArrayList<>(); } return comments; } private void combineChildren(Long blogId, 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(blogId, childId, parentNickname); } } } private void recursively(Long blogId, Long childId, String parentNickname1) { // 根据子一级评论的id找到子二级评论 List<Comment> replayComments = commentDao.findByBlogIdAndReplayId(blogId,childId); if(replayComments.size() > 0){ for(Comment replayComment : replayComments){ String parentNickname = replayComment.getNickname(); replayComment.setParentNickname(parentNickname1); Long replayId = replayComment.getId(); tempReplys.add(replayComment); recursively(blogId,replayId,parentNickname); } } } // 新增评论 @Override public int saveComment(Comment comment) { comment.setCreateTime(new Date()); int comments = commentDao.saveComment(comment); // 文章评论计数 blogDao.getCommentCountById(comment.getBlogId()); return comments; } } |
总结:本章主要是简单介绍博客评论功能的实现,功能并不全面。实现逻辑:因为是博客普通用户不需要登录即可浏览,所以没有做普通用户登录功能,评论时候需要输入自己的姓名和邮箱进行评论,若为博主评论则是通过实体类定义的字段在前端做判断,若为博主则增加标签。