最近在写一个论坛项目,想做一个评论区,但是在网上也搜不到写的比较好的,就寻思那自己构思写一个吧。
先上效果图:
这个评论区是仿知乎做的,虽然做的比较丑吧,日期也没有做个格式化,但是该有的东西应该是有了。其中点击评论图标,会将隐藏的评论框显示出来。
那么先上后台代码
数据库表分两个
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> </span><span th:text="${comment.getRootCommentDO().getText()}"></span>
<div>
<span> </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> </span><span>@</span><span th:text="${sonComment.getPname()}"></span><span>:</span><span th:text="${sonComment.getText()}"></span>
<div>
<span> </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操作,所以更好的是在评论表里加一列信息,直接保存。
然后就是发表评论时每次都是重新从后台获取,或许前端可以直接实现,让后台边做事情,也不耽误前端使用,但是我前端太菜了,就没做,不过这个也是毕设的一个项目而已,先做完再想优化吧~