如何实现博客的评论和回复功能

实现思路:

数据库设计:评论表需要定义出当前博客id以便做关联,因为评论需要有回复功能,则需要定义当前评论有无上一级评论,需要定义出上级评论id;博主回复评论需要带有标签,所以需要定义Boolean类型判断是否为博主。

代码方面:点击评论需要获取当前博客id与自己评论数据进行插入,点击回复按钮需要获取上一条评论的id以及用户姓名作为回复,回复成功后,刷新页面,后台则是在数据库中查找出所有parentCommentId为-1的进行遍历,因为上级id为-1则证明当前评论无父节点。在通过对父节点id的遍历查询出所有对应评论的子节点。

页面展示

博客实体类

    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

<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>

点击发布按钮触发


$('#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: '请填写正确的邮箱地址'
                }]
            }
        }
    });   

表单验证后发送请求到后端,提交成功之后清空表单

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", "请输入评论信息...");
    }

点击回复按钮,在评论区显示回复给哪个用户

<a class="reply" data-messageid="1" data-messagenickname="Matt" th:attr="data-messageid=${reply.id},data-messagenickname=${reply.nickname}" onclick="reply(this)">回复</a>

对应函数

 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);  //滚动
    }

对应后端代码

@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";
    }

列表设置,前端遍历显示层级关系

 <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>

后端实现逻辑:先获取顶级的数据,在一层一层往下找、放入集合

@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;
    }
}

总结:本章主要是简单介绍博客评论功能的实现,功能并不全面。实现逻辑:因为是博客普通用户不需要登录即可浏览,所以没有做普通用户登录功能,评论时候需要输入自己的姓名和邮箱进行评论,若为博主评论则是通过实体类定义的字段在前端做判断,若为博主则增加标签。

猜你喜欢

转载自blog.csdn.net/weixin_52210557/article/details/124161315