springboot + mybaties-plus + thymeleaf实现二级评论区

本文详细介绍了如何构建一个模仿知乎风格的论坛评论区,包括后端数据模型、API接口设计和前端交互实现。着重讨论了查询效率优化和冗余信息存储的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在写一个论坛项目,想做一个评论区,但是在网上也搜不到写的比较好的,就寻思那自己构思写一个吧。

先上效果图:

这个评论区是仿知乎做的,虽然做的比较丑吧,日期也没有做个格式化,但是该有的东西应该是有了。其中点击评论图标,会将隐藏的评论框显示出来。

那么先上后台代码

数据库表分两个

root_comment

aid即文章id,uid即发布者的id,text即评论文本

son_comment

pid表示根评论的id,而psid表示这条评论是回复的哪一条子评论,为null表示回复的是根评论

还要用到用户信息表,展示头像和昵称要用到

user_information

 那么,上后台代码

持久层mapper没什么可说的,由于使用的MP,很简单,省略

获取评论并传输至前端

先来写两个DO将根评论,子评论封装一下

RootCommentDO


import lombok.Data;

import java.util.Date;

@Data
public class RootCommentDO {

    private Integer id;
    private Integer aid;
    private Date gmtCreate;
    private String name;
    private String headImg;
    private String text;
}

SonCommentDO

package com.protal.community.DO;


import lombok.Data;

import java.util.Date;

@Data
public class SonCommentDO {

    private Integer id;
    private String pname;
    private String text;
    private Date gmtCreate;
    private String name;
    private String headImg;
}

然后写个DTO传输对象

ArticalCommentDTO

package com.protal.community.dto;

import com.protal.community.DO.RootCommentDO;
import com.protal.community.DO.SonCommentDO;
import lombok.Data;

import java.util.List;


@Data
public class ArticalCommentDTO {
    private RootCommentDO rootCommentDO;
    private List<SonCommentDO> sonCommentList;
}

后台获取文章就直接按根评论最新在前,子评论最新在后排序

通过文章id获取评论的方法

getCommentByArticalId

@Override
    public List<ArticalCommentDTO> getCommentByArticalId(Integer aid) {

        List<ArticalCommentDTO> list = new ArrayList<>();

        //选出根评论
        QueryWrapper<RootComment> queryWrapper = new QueryWrapper<RootComment>();
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("aid", aid);
        queryWrapper.orderByDesc("gmt_create").allEq(params);
        List<RootComment> rootComments = rootCommentMapper.selectList(queryWrapper);

        List<ArticalCommentDTO> articalCommentDTOS = new ArrayList<>();

        for (RootComment comment : rootComments) {

            ArticalCommentDTO articalCommentDTO = new ArticalCommentDTO();

            //把根评论加入列表
            RootCommentDO rootCommentDO = new RootCommentDO();

            UserInformation userInformation = userInformationMapper.selectById(comment.getUid());
            rootCommentDO.setHeadImg(userInformation.getHeadImg());
            rootCommentDO.setName(userInformation.getName());
            rootCommentDO.setGmtCreate(comment.getGmtCreate());
            rootCommentDO.setId(comment.getId());
            rootCommentDO.setText(comment.getText());
            rootCommentDO.setGmtCreate(comment.getGmtCreate());
            articalCommentDTO.setRootCommentDO(rootCommentDO);

            //将子评论按时间排序后加入列表
            Map<String, Object> params1 = new HashMap<String, Object>();
            List<SonComment> sonComments = new ArrayList<>();
            params1.put("pid", comment.getId());
            QueryWrapper<SonComment> queryWrapper1 = new QueryWrapper<SonComment>();
            queryWrapper1.orderByAsc("gmt_create").allEq(params1);
            sonComments = sonCommentMapper.selectList(queryWrapper1);

            List<SonCommentDO> list1 = new ArrayList<>();

            for (SonComment sonComment : sonComments) {
                UserInformation userInformation1 = userInformationMapper.selectById(sonComment.getUid());
                SonCommentDO sonCommentDO = new SonCommentDO();

                sonCommentDO.setHeadImg(userInformation1.getHeadImg());
                sonCommentDO.setName(userInformation1.getName());
                sonCommentDO.setGmtCreate(sonComment.getGmtCreate());
                sonCommentDO.setText(sonComment.getText());
                sonCommentDO.setId(sonComment.getId());
                if (sonComment.getPsid() == null) {
                    Integer uid = rootCommentMapper.selectById(sonComment.getPid()).getUid();
                    String name = userInformationMapper.selectById(uid).getName();
                    sonCommentDO.setPname(name);
                } else {
                    sonCommentDO.setPname(userInformationMapper.selectById(sonCommentMapper.selectById(sonComment.getPsid()).getUid()).getName());
                }
                list1.add(sonCommentDO);
            }

            articalCommentDTO.setRootCommentDO(rootCommentDO);
            articalCommentDTO.setSonCommentList(list1);
            articalCommentDTOS.add(articalCommentDTO);
        }

        return articalCommentDTOS;
    }

controller获取articalCommentDTOS并传递给前端省略(命名为comment)

前端显示代码

<div th:fragment="comment" th:class="comment" id="comment">
                <div th:class="error" th:value="${error}"></div>
                <div class="comment">
                    <div class="comment-box">
                        <textarea name="comment-textarea" id="comment-textarea" cols="100%" rows="1" tabindex="1"></textarea>
                        <div class="comment-ctrl" >
                            <button type="submit" name="comment-submit" id="comment-submit" tabindex="1" onclick="submitRootComment(this)">评论</button>
                        </div>
                    </div>
                </div>
                <div id="postcomments">
                    <div th:each="comment:${comment}">
                        <div style="width: 30px; height: 30px; float:left; border-radius: 50%; border: 1px solid #eee; overflow: hidden;">
                            <img th:src="${comment.getRootCommentDO().getHeadImg()}"  width="29" height="29" />
                        </div>
                        <div><a href="#" rel="nofollow" th:text="${comment.getRootCommentDO().getName()}"></a>(<span class="time" th:text="${comment.getRootCommentDO().getGmtCreate()}"></span>)</div>
                        <br><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span th:text="${comment.getRootCommentDO().getText()}"></span>
                        <div>
                            <span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
                            <a onclick="getThis(this)" >
                                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-dots" viewBox="0 0 16 16">
                                    <path d="M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
                                    <path d="m2.165 15.803.02-.004c1.83-.363 2.948-.842 3.468-1.105A9.06 9.06 0 0 0 8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6a10.437 10.437 0 0 1-.524 2.318l-.003.011a10.722 10.722 0 0 1-.244.637c-.079.186.074.394.273.362a21.673 21.673 0 0 0 .693-.125zm.8-3.108a1 1 0 0 0-.287-.801C1.618 10.83 1 9.468 1 8c0-3.192 3.004-6 7-6s7 2.808 7 6c0 3.193-3.004 6-7 6a8.06 8.06 0 0 1-2.088-.272 1 1 0 0 0-.711.074c-.387.196-1.24.57-2.634.893a10.97 10.97 0 0 0 .398-2z"/>
                                </svg>
                            </a>
                            <div style="padding-left: 40px;display: none" >
                                <div class="comment">
                                    <div class="comment-box">
                                        <textarea name="comment-textarea" id="comment-textarea" cols="100%" rows="1" tabindex="1"></textarea>
                                        <div class="comment-ctrl">
                                            <button type="submit" name="comment-submit" id="comment-submit" tabindex="1" onclick="submitLevelOneComment(this)">评论</button>
                                            <div th:value="${comment.getRootCommentDO().getId()}"></div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <div th:each="sonComment:${comment.getSonCommentList()}">
                            <div style="padding-left: 40px">
                                <div style="width: 30px; height: 30px; float:left; border-radius: 50%; border: 1px solid #eee; overflow: hidden;">
                                    <img th:src="${sonComment.getHeadImg()}"  width="29" height="29" />
                                </div>
                                <div><a href="#" rel="nofollow" th:text="${sonComment.getName()}"></a>(<span class="time" th:text="${sonComment.getGmtCreate()}"></span>)</div>
                                <br><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>@</span><span th:text="${sonComment.getPname()}"></span><span>:</span><span th:text="${sonComment.getText()}"></span>
                                <div>
                                    <span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
                                    <a onclick="getThis(this)" >
                                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-dots" viewBox="0 0 16 16">
                                            <path d="M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
                                            <path d="m2.165 15.803.02-.004c1.83-.363 2.948-.842 3.468-1.105A9.06 9.06 0 0 0 8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6a10.437 10.437 0 0 1-.524 2.318l-.003.011a10.722 10.722 0 0 1-.244.637c-.079.186.074.394.273.362a21.673 21.673 0 0 0 .693-.125zm.8-3.108a1 1 0 0 0-.287-.801C1.618 10.83 1 9.468 1 8c0-3.192 3.004-6 7-6s7 2.808 7 6c0 3.193-3.004 6-7 6a8.06 8.06 0 0 1-2.088-.272 1 1 0 0 0-.711.074c-.387.196-1.24.57-2.634.893a10.97 10.97 0 0 0 .398-2z"/>
                                        </svg>
                                    </a>
                                    <div  style="padding-left: 40px;display: none" >
                                        <div class="comment">
                                            <div class="comment-box">
                                                <textarea  name="comment-textarea" id="comment-textarea" cols="100%" rows="1" tabindex="1"></textarea>
                                                <div class="comment-ctrl">
                                                    <button type="submit" name="comment-submit" id="comment-submit" tabindex="1" onclick="submitLevelTwoComment(this)">评论</button>
                                                    <div th:value="${comment.getRootCommentDO().getId()}"></div>
                                                    <div th:value="${sonComment.getId()}"></div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

那么前台需要的一些js函数

function getThis(obj){
            var pinglun = $(obj).next();
            pinglun.css({'display':'block'});
        }
function submitRootComment(obj){
            var text = $(obj).parent().prev().val();
            var aid = $("#articalId").attr("value");
            $.ajax({
                url: "http://localhost:8080/submitRootComment",
                type: 'GET',
                data:{
                    text: text,
                    aid:aid
                },
                success: function (data) {
                    $(".comment").html(data);
                    if ($(".error").attr("value") == '1'){
                        alert("评论请先登录");
                    }
                }
            })
        }

        function submitLevelOneComment(obj){
            var text = $(obj).parent().prev().val();
            var fatherId = $(obj).next().attr("value");
            var aid = $("#articalId").attr("value");
            $.ajax({
                url: "http://localhost:8080/submitLevelOneComment",
                type: 'GET',
                data:{
                    text: text,
                    fatherId:fatherId,
                    aid:aid
                },
                success: function (data) {
                    $(".comment").html(data);
                    if ($(".error").attr("value") == '1'){
                        alert("评论请先登录");
                    }
                }
            })
        }

        function submitLevelTwoComment(obj){
            var text = $(obj).parent().prev().val();
            var fatherId = $(obj).next().attr("value");
            var answerId = $(obj).next().next().attr("value");
            var aid = $("#articalId").attr("value");
            $.ajax({
                url: "http://localhost:8080/submitLevelTwoComment",
                type: 'GET',
                data:{
                    text: text,
                    fatherId:fatherId,
                    aid:aid,
                    answerId:answerId
                },
                success: function (data) {
                    $(".comment").html(data);
                    if ($(".error").attr("value") == '1'){
                        alert("评论请先登录");
                    }
                }
            })
        }

后台controller的对应处理办法

package com.protal.community.controller;


import com.protal.community.dto.ArticalCommentDTO;
import com.protal.community.pojo.ArticalInformation;
import com.protal.community.pojo.RootComment;
import com.protal.community.pojo.SonComment;
import com.protal.community.pojo.UserInformation;
import com.protal.community.service.ArticalInformationService;
import com.protal.community.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;

@Controller
public class CommentController {

    @Autowired
    private CommentService commentService;

    @Autowired
    private ArticalInformationService articalInformationService;


    @GetMapping("/submitRootComment")
    public String submitRootComment(HttpServletRequest request, Model model) throws IOException {
        String text = request.getParameter("text");
        Integer aid = Integer.valueOf(request.getParameter("aid"));

        if(request.getSession().getAttribute("userInformation") == null) {
            model.addAttribute("error","1");
            ArticalInformation articalInformation = articalInformationService.selectArticalInformationById(aid);
            model.addAttribute("articalInformation",articalInformation);
            List<ArticalCommentDTO> commentByArticalId = commentService.getCommentByArticalId(aid);
            model.addAttribute("comment",commentByArticalId);
            return "artical::comment";
        }else {
            UserInformation userInformation = (UserInformation) request.getSession().getAttribute("userInformation");
            Integer uid = userInformation.getId();
            RootComment comment = new RootComment();
            comment.setAid(aid);
            comment.setUid(uid);
            comment.setText(text);
            commentService.insertOneRootComment(comment);

            //重新拉取评论并显示
            List<ArticalCommentDTO> commentByArticalId = commentService.getCommentByArticalId(aid);
            model.addAttribute("comment",commentByArticalId);
            ArticalInformation articalInformation = articalInformationService.selectArticalInformationById(aid);
            model.addAttribute("articalInformation",articalInformation);
            return "artical::comment";
        }
    }

    @GetMapping("/submitLevelOneComment")
    public String submitLevelOneComment(HttpServletRequest request,Model model) {
        Integer fatherId = Integer.valueOf(request.getParameter("fatherId"));
        String text = request.getParameter("text");
        Integer aid = Integer.valueOf(request.getParameter("aid"));
        if (request.getSession().getAttribute("userInformation") == null) {
            model.addAttribute("error", "1");
            List<ArticalCommentDTO> commentByArticalId = commentService.getCommentByArticalId(aid);
            model.addAttribute("comment", commentByArticalId);
            return "artical::comment";
        } else {
            UserInformation userInformation = (UserInformation) request.getSession().getAttribute("userInformation");
            Integer uid = userInformation.getId();
            SonComment sonComment = new SonComment();
            sonComment.setText(text);
            sonComment.setUid(uid);
            sonComment.setPid(fatherId);
            commentService.insertOneSonComment(sonComment);

            //重新拉取评论并显示
            List<ArticalCommentDTO> commentByArticalId = commentService.getCommentByArticalId(aid);
            model.addAttribute("comment",commentByArticalId);
            ArticalInformation articalInformation = articalInformationService.selectArticalInformationById(aid);
            model.addAttribute("articalInformation",articalInformation);

            return "artical::comment";
        }
    }

    @GetMapping("/submitLevelTwoComment")
    public String submitLevelTwoComment(HttpServletRequest request,Model model){
        Integer fatherId = Integer.valueOf(request.getParameter("fatherId"));
        String text = request.getParameter("text");
        Integer aid = Integer.valueOf(request.getParameter("aid"));
        Integer answerId = Integer.valueOf(request.getParameter("answerId"));
        if (request.getSession().getAttribute("userInformation") == null) {
            model.addAttribute("error", "1");
            List<ArticalCommentDTO> commentByArticalId = commentService.getCommentByArticalId(aid);
            model.addAttribute("comment", commentByArticalId);
            return "artical::comment";
        }else {
            UserInformation userInformation = (UserInformation) request.getSession().getAttribute("userInformation");
            Integer uid = userInformation.getId();
            SonComment sonComment = new SonComment();
            sonComment.setText(text);
            sonComment.setUid(uid);
            sonComment.setPid(fatherId);
            sonComment.setPsid(answerId);
            commentService.insertOneSonComment(sonComment);

            //重新拉取评论并显示
            List<ArticalCommentDTO> commentByArticalId = commentService.getCommentByArticalId(aid);
            model.addAttribute("comment",commentByArticalId);
            ArticalInformation articalInformation = articalInformationService.selectArticalInformationById(aid);
            model.addAttribute("articalInformation",articalInformation);

            return "artical::comment";
        }
    }
}
@Override
    public void insertOneRootComment(RootComment rootComment) {
        rootCommentMapper.insert(rootComment);
    }

    @Override
    public void insertOneSonComment(SonComment sonComment) {
        sonCommentMapper.insert(sonComment);
    }

然后可以优化的点:由于有“@某人”这个东西,导致查询的时候很费劲,为了这个昵称去查询一遍user_information感觉太得不偿失了,不如直接插入评论的时候直接插入这个名字,保留一定的冗余性,可以节省很多curd操作,所以更好的是在评论表里加一列信息,直接保存。

然后就是发表评论时每次都是重新从后台获取,或许前端可以直接实现,让后台边做事情,也不耽误前端使用,但是我前端太菜了,就没做,不过这个也是毕设的一个项目而已,先做完再想优化吧~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值