1. 效果预览
1.1进入首页
进入首页后点击一篇文章进入:
1.2 测试评论功能
进入文章详细列表后,上方为文章内容区域,下方为文章评论区域
对此评论框做了数据校验,如果评论内容为空,给出提示并返回,否则评论成功,我们输入一条评论信息,然后点击回复,如下:
测试二级评论功能:
我们点击一级评论下的评论图标,如果有二级评论则显示二级评论和二级评论框,如果没有只显示二级评论框
测试二级评论:
2. 功能实现
2.1 文章详情页布局(articleDetail.html)
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<!-- 踩过的坑,springboot引入静态资源路径不要加/static/,否则会报404-->
<title th:text="${ArticleDetail.title}"></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="/bootstrap-3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/community.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_1643567_yxz5icboc4.css">
<link rel="stylesheet" href="/bootstrap-3.3.7/css/bootstrap-theme.min.css">
<script src="/jquery-1.12.4/jquery-1.12.4.min.js"></script>
<script src="/bootstrap-3.3.7/js/bootstrap.min.js"></script>
<script src="/js/community.js"></script>
</head>
<body>
<div th:replace="navigation :: nav"></div>
<div class="row main">
<div class="col-lg-9 col-md-12 col-sm-12 col-xs-12 col-left">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class=""> <h4 th:text="${ArticleDetail.title}"></h4></div>
<hr>
<div class="detail-msg">
作者: <span th:text="${ArticleDetail.user.name}"></span> | 发布时间:<span th:text="${#dates.format(ArticleDetail.createTime,'yyyy-MM-dd')}"></span> | 阅读数: <span th:text="${ArticleDetail.readCount}"></span>
<div class="link-items" th:if="${session.user !=null && ArticleDetail.user.id==session.user.id}" >
<a th:href="@{/article/edit(id=${ArticleDetail.getId()})}"><i class="iconfont icon-fabu2"></i> 编辑</a>
</div>
</div>
<div>
<span th:text="${ArticleDetail.description}"></span>
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="comment">
<h4><span th:text="${ArticleDetail.getAnswerCount()}"></span> 个回复</h4>
</div>
<div class="media media_list" th:each="comment :${comments}">
<div class="comment-head">
<div class="media-left">
<a href="#">
<img class="img-rounded img-comment" th:src="${comment.getUser().getAvatarUrl()}" >
</a>
</div>
<div class="media-body">
<span class="media-heading" th:text="${comment.getUser().getName()}"></span>
<span class="float-right" th:text="${#dates.format(comment.createTime,'yyyy-MM-dd')}"></span>
</div>
</div>
<div class="comment-style comment-body" th:text="${comment.getContent()}">
</div>
<div class="comment-style comment-footer">
<div>
<span class="operate like">
<i class="iconfont icon-z-like" th:like-id="${comment.getId()}" onclick="like(this)"></i>
<span id="likeCount" th:text="${comment.getLikeCount()==0?'':comment.getLikeCount()}"></span>
</span>
<span class="operate answer" th:data-id="${comment.getId()}" onclick="showChildComment(this)">
<i class="iconfont icon-pinglun"></i>
<span th:id="${'commentCount-'+comment.getId()}" th:text="${comment.commentCount==0?'':comment.commentCount}"></span>
</span>
</div>
<div class="collapse childCol col-lg-12 col-md-12 col-sm-12 col-xs-12" th:id="${'comment-'+comment.getId()}">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 comment-comment" th:if="${session.user!=null}">
<input type="text" class="form-control" th:id="${'childComment-'+comment.getId()}" name="childComment" placeholder="说说你的看法...">
<button type="button" class="btn btn-success btn-comment" th:data-parentId="${comment.getId()}" onclick="subComment(this)">评论</button>
</div>
</div>
</div>
</div>
</div>
<!-- 评论 -->
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 ">
<div class="comment" th:if="${session.user!=null}">
<input type="hidden" id="parentId" th:value="${ArticleDetail.getId()}">
<div class="d-flex">
<div class="media">
<div class="media-comment">
<a href="#">
<img class="img-rounded img-comment" th:src="${ArticleDetail.user.getAvatarUrl()}" >
</a>
</div>
</div>
<textarea id="commentArea" name="description" class="comment-content open" maxlength="1000"></textarea>
</div>
<button type="button" class="btn btn-success btn-comment" onclick="postComment();">回复</button>
</div>
<div class="notLogin" th:if="${session.user==null}">
要回复问题请先<a onclick="toLogin()">登录</a>
</div>
</div>
</div>
<div class="col-lg-3 col-md-12 col-sm-12 col-xs-12">
<div>
<p>发起人</p>
<div class="media">
<div class="media-left">
<a href="#">
<img class="media-object img-rounded" th:src="${ArticleDetail.user.getAvatarUrl()}" >
</a>
</div>
<div class="media-body">
<span class="media-heading" th:text="${ArticleDetail.user.name}"></span>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
2.2 一级/二级评论回复功能
点击回复按钮事件
function postComment() {
var parentId = $("#parentId").val();
var content = $("#commentArea").val();
var data = {"parentId":parentId,"content":content,"type":1};
if(content==undefined||content==""){
alert("评论内容不能为空");
return;
}
$.ajax({
type:"POST",
url:"/comment",
data:JSON.stringify(data),
dataType:"json",
contentType:"application/json",
success:function (result) {
if(result.code==200){
//$("#commentArea").val("");
window.location.reload();
alert(result.message);
}else {
alert(result.message)
}
}
});
}
通过ajax请求到后端“/comment”路径(CommentController.java)
//控制层处理前端提交过来的回复评论请求
@PostMapping("/comment")
@ResponseBody
public RespDTO doComment(@RequestBody Comment comment,
HttpServletRequest request){
User user = (User) request.getSession().getAttribute("user");
if(user==null){
return RespDTO.error(CustomizeErrorCode.NOT_LOGIN.getMessage());
}
comment.setCreateTime(System.currentTimeMillis());
comment.setModifiedTime(System.currentTimeMillis());
comment.setCommentor(user.getId());
comment.setLikeCount(0);
comment.setCommentCount(0);
//做提交操作
commentService.insert(comment);
return RespDTO.ok(CustomizeErrorCode.COMMENT_SUCCESS.getMessage(),user);
}
Service层进行提交逻辑处理(CommentService.java)
/**
通用的评论回复处理,可以回复一级评论和二级评论
*/
//使用事务
@Transactional
public void insert(Comment comment){
Long parentId = null;
Article article1 = articleMapper.selectByPrimaryKey(comment.getParentId());
if(comment.getType()==null || !CommentType.isExist(comment.getType())){
throw new CustomizeException(CustomizeErrorCode.COMMENT_TYPE_WRONG);
}
//类型为ARTICLE,对文章进行评论
if(comment.getType()==CommentType.ARTICLE.getType()){
if(comment.getParentId()==null || articleMapper.selectByPrimaryKey(comment.getParentId())==null){
throw new CustomizeException(CustomizeErrorCode.COMMENT_ARTICLE_ID_NOT_FOUND);
}
parentId = comment.getParentId();
}else {
//否则处理二级评论
Comment parentComment = commentMapper.selectByPrimaryKey(comment.getParentId());
if(parentComment!=null){
parentId = parentComment.getParentId();
}else {
throw new CustomizeException(CustomizeErrorCode.COMMENT_ARTICLE_ID_NOT_FOUND);
}
Comment updateComment = new Comment();
updateComment.setCommentCount(1);
updateComment.setId(comment.getParentId());
commentExtMapper.incSubCommentCount(updateComment);
if(comment.getParentId()==null || commentMapper.selectByPrimaryKey(comment.getParentId())==null){
throw new CustomizeException(CustomizeErrorCode.COMMENT_NOT_FOUND);
}
}
//添加评论内容
commentMapper.insert(comment);
Article article = new Article();
//文章总评论数递增
article.setId(parentId);
article.setAnswerCount(1);
articleExtMapper.incComment(article);
}
2.3 二级评论显示功能
点击查看二级评论,通过ajax向后台请求数据:
function showChildComment(ele) {
var parentId =$(ele).attr("data-id");
$(ele).toggleClass("operateBgColor");
var childComment = $("#comment-"+parentId);
//控制二级评论div显示还是隐藏
childComment.toggleClass("in ");
var data = {childId:parentId};
//如果二级评论框显示,开始请求数据
if(childComment.hasClass("in")){
$.ajax({
type:"get",
url:"/childComment",
data:data,
dataType:"json",
contentType:"application/json",
success:function (result) {
var obj = result.obj;
$.each(obj,function (i, o) {
console.log(o.createTime);
var time = new Date(o.createTime).format("yyyy-MM-dd");
pushCommentHtml(parentId,o.user.avatarUrl,o.user.name,o.content,time);
});
}
});
}else{
//防止评论框关闭,再次点击内容重复叠加,每次都清空二级评论
$("#comment-"+parentId +" .comment-detail").remove()
}
}
二级评论内容拼接到html:
function pushCommentHtml(id,imgurl,uname,content,time) {
var html = "<div class='comment-detail col-lg-12 col-md-12 col-sm-12 col-xs-12'>" +
" <div class='comment-head'>" +
" <div class='media-left'>" +
" <a href='#'>" +
" <img class='img-rounded img-comment' src='" + imgurl + "'>" +
" </a>" +
" </div>" +
" <div class='media-body'>" +
" <span class='media-heading'>" + uname + "</span>" +
" <span class='float-right'>" + time + "</span>"+
" </div>" +
" </div>" +
" <div class='comment-style comment-body'>" + content + "</div>" +
" </div>"
var ele = $("#comment-" + id);
ele.prepend(html);
}
前端请求“/childComment”路径开始获取二级评论内容:
@GetMapping("/childComment")
@ResponseBody
public RespDTO getChildComments(@Param("childId") Long childId, HttpServletRequest request){
User user = (User) request.getSession().getAttribute("user");
if(user==null){
return RespDTO.error(CustomizeErrorCode.NOT_LOGIN.getMessage());
}
if(StringUtils.isEmpty(childId)){
return RespDTO.error(CustomizeErrorCode.COMMENT_NOT_FOUND.getMessage());
}
List<CommentDTO> commentDTOS = commentService.selectCommentsById(childId, CommentType.COMMENT.getType());
return RespDTO.ok("获取二级评论成功",commentDTOS);
}
Service层处理获取评论功能(包括一级评论和二级评论),一级评论是在进入详情页就开始获取,二级评论则是用户操作获取:
public List<CommentDTO> selectCommentsById(long id,int type) {
List<CommentDTO> commentDTOS = new ArrayList<>();
/** 查询出当前文章的所有一级评论 **/
CommentExample commentExample = new CommentExample();
commentExample.createCriteria().andParentIdEqualTo(id).andTypeEqualTo(type);
if(type==CommentType.ARTICLE.getType()){
commentExample.setOrderByClause("create_time desc");
}else if(type == CommentType.COMMENT.getType()){
commentExample.setOrderByClause("create_time asc");
}
List<Comment> comments = commentMapper.selectByExample(commentExample);
if(comments.size()<=0){
return new ArrayList<>();
}
/** 使用lambda表达式获取用户id **/
Set<Long> ids = comments.stream().map(c -> c.getCommentor()).collect(Collectors.toSet());
UserExample user = new UserExample();
user.createCriteria().andIdIn(new ArrayList<>(ids));
List<User> users = userMapper.selectByExample(user);
//将获取到的用户List转为map
Map<Long, User> userMap = users.stream().collect(Collectors.toMap(User::getId, u -> u, (k1, k2) -> k1));
//遍历并拷贝赋值
comments.stream().forEach(comment -> {
CommentDTO commentDTO = new CommentDTO();
BeanUtils.copyProperties(comment,commentDTO);
commentDTO.setUser(userMap.get(comment.getCommentor()));
commentDTOS.add(commentDTO);
});
return commentDTOS;
}