个人博客网站——留言及多级回复功能


通常情况下,在博客网站中,无论是文章下的评论还是单独的留言,都会有多级的回复功能。这里我们主要介绍多级回复功能的留言实现。

新增留言

在这里的新增留言,由留言板块、姓名板块和邮箱板块组成,其中隐藏获取了用户的电脑和浏览器内核版本。

<!--新增留言-->
        <div id="message-form" class="ui form">
            <input type="hidden" name="parentMessage.id" value="-1">
            <input type="hidden" id="win1_lei" name="window.name" value="unknown">
            <input type="hidden" id="win2_lei" name="browser.name" value="unknown">
            <!--留言区-->
            <div class="field">
                <textarea name="content" placeholder="请输入留言信息..."></textarea>
            </div>
            <div class="ui grid">
                <!--输入姓名-->
                <div class="five wide column">
                    <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>

                <!--输入邮箱-->
                <div class="five wide column">
                    <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>

                <div class="right aligned six wide column">
                    <div class="field m-mobile-wide m-margin-bottom-small">
                        <button id="messagepost-btn" type="button" class="ui teal button m-mobile-wide"><i
                                class="edit icon"></i>发布
                        </button>
                    </div>
                </div>
            </div>
        </div>

这里的js主要是:通过调用Browser这个函数,获取用户的浏览器和电脑内核信息。

<script>
    var info = new Browser();
    currentTimeHtml1 =   + info.os +   + info.osVersion +  ;
    currentTimeHtml2 =   + info.browser + info.version +  ;
    document.getElementById("win1_lei").value = currentTimeHtml1;
    document.getElementById("win2_lei").value = currentTimeHtml2;
</script>

这里是对评论表单的input进行规范

//评论表单验证
    $(.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: 请填写正确的邮箱地址
                }]
            }
        }
    });
$(#messagepost-btn).click(function () {
          
   
        var boo = $(.ui.form).form(validate form);
        if (boo) {
          
   
            console.log(校验成功);
            postData();
        } else {
          
   
            console.log(校验失败);
        }
    });

给后端发送评论的数据(分为两种:一种是单独的留言;另一种是回复留言的留言)

//发送请求给后端
    function postData() {
          
   
        $("#message-container").load(/*[[@{/messages}]]*/"", {
          
   
            "parentMessage.id": $("[name=parentMessage.id]").val(),
            // "blog.id" : $("[name=blog.id]").val(),
            "nickname": $("[name=nickname]").val(),
            "email": $("[name=email]").val(),
            "content": $("[name=content]").val(),
            "windowName": $("[name=window.name]").val(),
            "browserName": $("[name=browser.name]").val()
        }, function (responseTxt, statusTxt, xhr) {
          
   
//        $(window).scrollTo($(#message-container),500);
            clearContent();
        });
    }

后端处理得到的留言(多层留言一会再说)

//新增留言
    @PostMapping("/message")
    public String post(Message message, HttpSession session, Model model){
          
   
        User user = (User) session.getAttribute("user");
        //设置头像
        if(user != null){
          
   
            message.setAvatar(user.getAvatar());
            message.setAdminMessage(true);
        } else {
          
   
            message.setAvatar(avatar);
        }
        if(message.getParentMessage().getId()!=null){
          
   
            message.setParentMessageId(message.getParentMessage().getId());
        }

        //System.out.println(message);
        messageService.saveMessage(message);
        List<Message>  messages = messageService.listMessage();
        model.addAttribute("messages",messages);
        return "message :: messageList";
    }

留言显示

留言显示的主要模块有:留言人的姓名,(被留言的人),留言的时间,电脑和浏览器内核,留言的内容,回复按钮等部分。 这里包含父级留言和子级留言。

<div class="ui bottom attached m-margin-top">
            <div id="message-container" class="ui teal segment">
                <div th:fragment="messageList">
                    <div class="ui threaded comments" style="max-width: 100%;">
                        <h3 class="ui dividing header">留言</h3>
                        <div class="comment" th:each="message : ${messages}">
                            <a class="avatar">
                                <img src="../static/images/me.jpg" th:src="@{${message.avatar}}">
                            </a>
                            <div class="content">
                                <a class="author">
                                    <span th:text="${message.nickname}">Matt</span>
                                    <div class="ui mini basic teal left pointing label m-padded-mini"
                                         th:if="${message.adminMessage}">栈主
                                    </div>
                                </a>
                                <div class="metadata">
                                    <span class="date"
                                          th:text="${#dates.format(message.createTime,yyyy-MM-dd HH:mm)}">今天下午 5:42</span>
                                </div>
                                <div class="" aria-hidden="true" style="">
                                    <span th:text="${message.windowName}"
                                          style="background-color: #f0eff3; color: #888888; border-radius: 10%">&nbsp; Windows10 &nbsp;</span>&nbsp;
                                    <span th:text="${message.browserName}"
                                          style="background-color: #f0eff3; color: #888888; border-radius: 10%">&nbsp; Chrome 97.0.4692.71 &nbsp;</span>
                                </div>
                                <div class="text" th:text="${message.content}">太赞了!</div>
                                <div class="actions">
                                    <a class="reply" data-messageid="1" data-messagenickname="Matt"
                                       th:attr="data-messageid=${message.id},data-messagenickname=${message.nickname}"
                                       onclick="reply(this)">回复</a>
                                    <a class="delete" href="#" th:href="@{/messages/{id}/delete(id=${message.id})}"
                                       onclick="return confirm(确定要删除该评论吗?三思啊! 删了可就没了!)" th:if="${session.user}">删除</a>
                                </div>
                            </div>
                            <!--子集留言-->
                            <div class="comments" th:if="${#arrays.length(message.replyMessages)}>0">
                                <div class="comment" th:each="reply : ${message.replyMessages}">
                                    <a class="avatar">
                                        <img src="../static/images/me.jpg" 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.adminMessage}">栈主
                                            </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)}">今天下午 5:42</span>
                                        </div>
                                        <div class="" aria-hidden="true" style="">
                                            <span th:text="${reply.windowName}"
                                                  style="background-color: #f0eff3; color: #888888; border-radius: 10%">&nbsp; Windows10 &nbsp;</span>&nbsp;
                                            <span th:text="${reply.browserName}"
                                                  style="background-color: #f0eff3; color: #888888; border-radius: 10%">&nbsp; Chrome 97.0.4692.71 &nbsp;</span>
                                        </div>
                                        <div class="text" th:text="${reply.content}">太赞了!</div>
                                        <div class="actions">
                                            <a class="reply" data-messageid="1" data-messagenickname="Matt"
                                               th:attr="data-messageid=${reply.id},data-messagenickname=${reply.nickname}"
                                               onclick="reply(this)">回复</a>
                                            <a class="delete" href="#"
                                               th:href="@{/messages/{id}/delete(id=${reply.id})}"
                                               onclick="return confirm(确定要删除该评论吗?三思啊! 删了可就没了!)"
                                               th:if="${session.user}">删除</a>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>

                    </div>
                </div>

            </div>

        </div>

打开页面后,先初始化加载留言的内容。

// 初始化加载
    $(function () {
          
   
        $("#message-container").load(/*[[@{/messagecomment}]]*/"messagecomment");
    });

调用后端,查询到留言

//查询留言
    @GetMapping("/messagecomment")
    public String messages(Model model) {
          
   
        List<Message> messages = messageService.listMessage();
        model.addAttribute("messages", messages);
        return "message::messageList";
    }

回复功能的js: 先从html中获取要回复留言的id和该发送者的name,作为parentid和parentname。 然后在要填写的留言内容上,添加“@+parentname”;并且记录parentid的id。 之后就和普通的发送留言一样,点击发布调取后端。

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

重点:多层留言功能实现

首先清楚我们向后端发送的数据种类,这里的parentmessage.id是看是否有父级,要是有父级的话,id为父级留言id,无则为null

然后主要是后端对多层留言的处理: 这里首先判断父级id,然后set进值。之后的service方法直接调用了message。 在service中,直接把message的信息存入数据库中即可。

@Override
    public int saveMessage(Message message) {
          
   
        message.setCreateTime(new Date());
        return messageDao.saveMessage(message);
    }

到这里都没有问题,就是简单的获取数据,存放在数据库中。 主要是对留言的展示处理: 调用后端查询留言,这里的messages用List存储,调用messageservice中的listmessage()方法。

//查询留言
    @GetMapping("/messagecomment")
    public String messages(Model model) {
          
   
        List<Message> messages = messageService.listMessage();
        model.addAttribute("messages", messages);
        return "message::messageList";
    }

这里的messageservice中的三个方法很重要,listMessage、combineChildren、recursively。分别的功能是查询留言、查询出子留言、迭代查询出子集回复。

我们先看listMessage()方法: 先查询出所有parentid为-1的的message,这些message为父留言。 分别对每个parentid=-1的留言进行处理,先获得该message的id和name。 然后查询以该留言的id作为parentid的留言,查询的子级留言为childmessage。 然后调用combineChildren(childMessages, parentNickname1)方法。 把存储该留言的所有子留言、多层留言的tempReplys存入ReplyMessages中,作为父级留言下面的所有留言。

private List<Message> tempReplys = new ArrayList<>();

@Override
    public List<Message> listMessage() {
          
   
        //查询出父节点
        List<Message> messages = messageDao.findByParentIdNull(Long.parseLong("-1"));
        for(Message message : messages){
          
   
            Long id = message.getId();
            String parentNickname1 = message.getNickname();
            List<Message> childMessages = messageDao.findByParentIdNotNull(id);
            //查询出子留言
            combineChildren(childMessages, parentNickname1);
            message.setReplyMessages(tempReplys);
            tempReplys = new ArrayList<>();
        }
        return messages;
    }

我们再看combineChildren方法: 传入了childmessage和parentnickname,对childmessage>0的进行处理。 先获取第一层子留言的id和name,然后存入tempReplys中。 然后调用recursively(childId, parentNickname)方法。

private void combineChildren(List<Message> childMessages, String parentNickname1) {
          
   
        //判断是否有一级子回复
        if(childMessages.size() > 0){
          
   
            //循环找出子留言的id
            for(Message childMessage : childMessages){
          
   
                String parentNickname = childMessage.getNickname();
                childMessage.setParentNickname(parentNickname1);
                tempReplys.add(childMessage);
                Long childId = childMessage.getId();
                //查询二级以及所有子集回复
                recursively(childId, parentNickname);
            }
        }
    }

再看trecursively()方法: 传入childid和childname,然后查询所有parentid为childid的留言。 查询该留言的id和name,存入tempReplys中。 然后再调用trecursively()方法,迭代查询以该留言作为父类留言的留言。

private void recursively(Long childId, String parentNickname1) {
          
   
        //根据子一级留言的id找到子二级留言
        List<Message> replayMessages = messageDao.findByReplayId(childId);

        if(replayMessages.size() > 0){
          
   
            for(Message replayMessage : replayMessages){
          
   
                String parentNickname = replayMessage.getNickname();
                replayMessage.setParentNickname(parentNickname1);
                Long replayId = replayMessage.getId();
                tempReplys.add(replayMessage);
                //循环迭代找出子集回复
                recursively(replayId,parentNickname);
            }
        }
    }

然后返回list类型的messages,通过model.addAttribute()发送给前端。 然后是留言的展示,首先是一级留言:(这里的messages中的每一个message都是一级留言) 然后是重要的多级留言展示:

经验分享 程序员 微信小程序 职场和发展