commentator
字段)的值为0,头像为默认头像,用户登录之后可以进行实名评论type
字段的不同,一级评论为1,二级评论为2com.qianyucc.blog.model.dto.CommentDTO
类,用于封装前端向后端传输的评论信息package com.qianyucc.blog.model.dto;
import lombok.*;
/**
* @author lijing
* @date 2019-10-13 10:53
* @description 前端向后端传输的评论信息
*/
@Data
public class CommentDTO {
private Long id;
private Long parentId;
private Long commentator;
private String content;
private Integer type;
}
com.qianyucc.blog.model.vo.CommentVO
类,用于封装后端返回到前端的评论信息package com.qianyucc.blog.model.vo;
import lombok.*;
import java.util.*;
/**
* @author lijing
* @date 2019-10-13 11:18
* @description 封装返回到前端的评论信息
*/
@Data
public class CommentVO {
private Long id;
private Long parentId;
private Integer type;
private Long commentator;
private String content;
private String gmtCreate;
private String gmtUpdate;
private Long likes;
private Long comments;
private String commentatorAvatarUrl;
private String commentatorName;
private List<CommentVO> secondLevelComments;
}
CommentService
编写业务层代码,需要注意的是在插入评论的时候要将父级(这里的父级评论数是文章的评论数或者一级评论的评论数)的评论数加一package com.qianyucc.blog.service;
import cn.hutool.core.bean.*;
import com.qianyucc.blog.model.dto.*;
import com.qianyucc.blog.model.entity.*;
import com.qianyucc.blog.repository.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;
import java.util.*;
/**
* @author lijing
* @date 2019-10-13 10:55
* @description 与评论相关的业务
*/
@Service
public class CommentService {
@Autowired
private ArticleRepository articleRepository;
@Autowired
private CommentRepository commentRepository;
@Autowired
private UserRepository userRepository;
/**
* 插入评论
*
* @param commentDTO
* @return
*/
public void insComment(CommentDTO commentDTO) {
// 先将父级评论数或者文章评论数加一
if (commentDTO.getType().equals(1)) {
Optional<ArticleDO> byId = articleRepository.findById(commentDTO.getParentId());
byId.ifPresent(articleDO -> {
articleDO.setComments(articleDO.getComments()+1);
articleRepository.save(articleDO);
});
} else if (commentDTO.getType().equals(2)) {
Optional<CommentDO> byId = commentRepository.findById(commentDTO.getParentId());
byId.ifPresent(commentDO -> {
commentDO.setComments(commentDO.getComments() + 1);
commentRepository.save(commentDO);
});
}
CommentDO commentDO = new CommentDO();
BeanUtil.copyProperties(commentDTO, commentDO);
commentDO.setComments(0L);
commentDO.setLikes(0L);
commentDO.setGmtCreate(System.currentTimeMillis());
commentDO.setGmtUpdate(commentDO.getGmtCreate());
commentRepository.save(commentDO);
}
/**
* 根据文章的id查询该文章的所有评论
*
* @param articleId
* @return
*/
public List<CommentVO> findCommentByArticleId(Long articleId) {
List<CommentDO> commentDOS = commentRepository.findByParentIdAndType(articleId, 1);
List<CommentVO> commentVOS = CommentUtil.jpaDosToVos(commentDOS, userRepository);
// 查找所有一级评论对应的二级评论
commentVOS.forEach(commentVO -> {
List<CommentDO> secondLevelCommentDOS = commentRepository.findByParentIdAndType(commentVO.getId(), 2);
List<CommentVO> secondLevelCommentAOS = CommentUtil.jpaDosToVos(secondLevelCommentDOS, userRepository);
commentVO.setSecondLevelComments(secondLevelCommentAOS);
});
return commentVOS;
}
}
上面的业务层代码用到了自定义工具类
CommentUtil
,并且在CommentRepository
里面定义根据parentId
和type
字段查询的方法
package com.qianyucc.blog.repository;
import com.qianyucc.blog.model.entity.*;
import org.springframework.data.jpa.repository.*;
import java.util.*;
/**
* @author lijing
* @date 2019-10-11 10:40
* @description 访问数据库中评论
*/
public interface CommentRepository extends JpaRepository<CommentDO, Long>, JpaSpecificationExecutor<CommentDO> {
List<CommentDO> findByParentIdAndType(Long articleId, Integer type);
}
package com.qianyucc.blog.utils;
import cn.hutool.core.bean.*;
import com.qianyucc.blog.model.entity.*;
import com.qianyucc.blog.model.vo.*;
import com.qianyucc.blog.repository.*;
import java.util.*;
/**
* @author lijing
* @date 2019-10-13 11:19
* @description 与评论相关的工具方法
*/
public class CommentUtil {
private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm";
/**
* 将model转换为ao,并格式化日期
*
* @param commentDOS
* @return
*/
public static List<CommentVO> jpaDosToVos(List<CommentDO> commentDOS, UserRepository userRepository) {
ArrayList<CommentVO> commentVOS = new ArrayList<>();
commentDOS.forEach(commentDO -> {
CommentVO commentVO = new CommentVO();
BeanUtil.copyProperties(commentDO, commentVO);
Optional<UserDO> byId = userRepository.findById(commentDO.getCommentator());
byId.ifPresent(userDO -> {
commentVO.setCommentatorAvatarUrl(userDO.getAvatarUrl());
commentVO.setCommentatorName(userDO.getName());
});
commentVO.setGmtCreate(BlogUtil.formatDate(commentDO.getGmtCreate(), DATE_PATTERN));
commentVO.setGmtUpdate(BlogUtil.formatDate(commentDO.getGmtUpdate(), DATE_PATTERN));
commentVOS.add(commentVO);
});
return commentVOS;
}
}
package com.qianyucc.blog.controller.comm;
import com.qianyucc.blog.model.dto.*;
import com.qianyucc.blog.model.vo.*;
import com.qianyucc.blog.service.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* @author lijing
* @date 2019-10-13 12:19
* @description 与评论相关的api
*/
@RestController
@RequestMapping("/api/comm/comment")
public class CommentController {
@Autowired
private CommentService commentService;
@PostMapping("/insComment")
public RespDataVO comment(@RequestBody CommentDTO commentDTO) {
commentService.insComment(commentDTO);
return RespDataVO.ok("评论成功!");
}
@GetMapping("/getComments")
public List<CommentVO> getAllComments(Long articleId) {
List<CommentVO> commentVOS = commentService.findCommentByArticleId(articleId);
return commentVOS;
}
}
/src/request/api/url.js
中添加与评论相关的url/src/request/api/comment.js
封装请求apiimport url from '@/request/api/url'
// 导入axios实例
import axios from '@/request/http'
export default {
submitComment(commentInfo, callback) {
axios
.post(url.doCommentUrl, commentInfo)
.then(callback)
.catch(err => {
console.log('submitComment Error');
})
},
getAllComments(articleId, callback) {
axios
.get(url.getCommentsUrl, {
params: {
articleId: articleId
}
})
.then(callback)
.catch(err => {
console.log("getAllComments Error");
})
}
}
别忘了在/src/request/api/index.js
中导出
在articleDetails.vue
中定义数据存储评论信息,并导入store
中的isLogin
和userInfo
data() {
return {
// 文章信息
article: {},
// 所有评论
comments: [],
// 文章内容
content: null,
// 与二级评论内容绑定
slcContent: null,
// 与一级评论内容绑定
commentContent: null
};
},
computed: {
...mapState({
isLogin: state => state.app.isLogin,
userInfo: state => state.user.userInfo
})
},
getAllComments() {
this.$api.comment.getAllComments(this.article.id, resp => {
this.comments = resp.data;
});
}
submitSlComment(id) {
if (this.slcContent == null || this.slcContent == "") {
return;
}
this.$api.comment.submitComment(
{
content: this.slcContent,
type: 2,
parentId: id,
commentator: this.isLogin ? this.userInfo.id : 0
},
resp => {
this.slcContent = "";
// 重新获取所有评论
this.getAllComments();
}
);
},
submitComment() {
if (!this.commentContent || this.commentContent == "") {
return;
}
this.$api.comment.submitComment(
{
content: this.commentContent,
type: 1,
parentId: this.article.id,
commentator: this.isLogin ? this.userInfo.id : 0
},
resp => {
this.commentContent = "";
// 重新获取所有评论
this.getAllComments();
}
);
}
}
created()
方法中获取文章信息之后再获取该文章的所有评论created() {
this.$api.article.getArticleById(this.$route.params.articleId, resp => {
this.article = resp.data;
let converter = new showdown.Converter({
// 使代码高亮显示
extensions: [showdownHighlight],
// 启用后可以为图片设置尺寸
parseImgDimensions: true
});
// markdown 转 html
this.content = converter.makeHtml(this.article.content);
// 获取所有评论,因为axios为异步操作,下面的操作不能写在该函数的外面
this.getAllComments();
});
}
<template>
<b-container class="main">
<!-- 文章标题 -->
<h2 class="title">{{article.title}}</h2>
<!-- 文章描述 -->
<h6 class="description">
<b-badge variant="info">{{article.type==1 ? '原创' : '转载'}}</b-badge>
<i class="icon iconfont icon-riqi"></i>
{{article.gmtUpdate}}
<i
class="icon iconfont icon-gaojian-zuozhe"
></i>
{{article.author}}
<i class="icon iconfont icon-yuedu"></i>
{{article.views}}
<i class="icon iconfont icon-fenlei"></i>
{{article.category}}
</h6>
<!-- 文章内容 -->
<div v-html="content"></div>
<!-- 标签 -->
<div class="tag-box">
<b-link class="tag" variant="info" v-for="(tag,index) in article.tags" :key="index">
<i class="icon iconfont icon-tag"></i>
{{tag}}
</b-link>
</div>
<!-- 评论回复 -->
<hr />
<h5>总共有{{comments.length}}条评论</h5>
<hr />
<!-- 一级评论列表 start -->
<b-media v-for="comment in comments" :key="comment.id">
<template v-slot:aside>
<b-img
width="60"
height="60"
:src="comment.commentator==0 ? '/static/images/no-name.png' : comment.commentatorAvatarUrl"
></b-img>
</template>
<h6
thumbnail
class="commentator-name"
v-text="comment.commentator==0 ? '匿名用户' : comment.commentatorName"
></h6>
<p>{{comment.content}}</p>
<p>
<i class="icon iconfont icon-dianzan1 link-icon"></i>
<i
class="icon iconfont icon-pinglun link-icon"
@click="showOrHideSecondLevelComments(comment.id)"
></i>
{{comment.secondLevelComments.length}}
</p>
<div :id="'second-level-comment-'+comment.id" style="display:none;">
<!-- 二级评论列表 start -->
<b-media v-for="cll in comment.secondLevelComments" :key="cll.id">
<template v-slot:aside>
<b-img
width="60"
height="60"
:src="cll.commentator==0 ? '/static/images/no-name.png' : cll.commentatorAvatarUrl"
></b-img>
</template>
<h6
thumbnail
class="commentator-name"
v-text="cll.commentator==0 ? '匿名用户' : cll.commentatorName"
></h6>
<p>{{cll.content}}</p>
</b-media>
<!-- 二级评论 end -->
<hr />
<b-textarea placeholder="请输入评论内容......" v-model="slcContent"></b-textarea>
<b-button class="pull-right" variant="success" @click="submitSlComment(comment.id)">提交</b-button>
</div>
</b-media>
<!-- 一级评论列表 end -->
<hr />
<b-media>
<template v-slot:aside>
<b-img
width="60"
height="60"
:src="isLogin ? userInfo.avatarUrl : '/static/images/no-name.png'"
></b-img>
</template>
<h6 thumbnail class="commentator-name" v-text="isLogin ? userInfo.name : '匿名用户'"></h6>
</b-media>
<b-textarea placeholder="请输入评论内容......" v-model="commentContent"></b-textarea>
<b-button variant="success" class="pull-right" @click="submitComment()">提交</b-button>
<!-- 回顶部按钮 -->
<i class="icon iconfont icon-huidingbu" @click="backTop()"></i>
</b-container>
</template>