目录
Bug修正
之前Article中的commentCounts,viewCounts,weight 字段为int,会造成更新阅读次数的时候,将其余两个字段设为初始值0,
@Data
public class Article {
public static final int Article_TOP = 1;
public static final int Article_Common = 0;
private Long id;
private String title;
private String summary;
private Integer commentCounts;
private Integer viewCounts;
/**
* 作者id
*/
private Long authorId;
/**
* 内容id
*/
private Long bodyId;
/**
*类别id
*/
private Long categoryId;
/**
* 置顶
*/
private Integer weight;
/**
* 创建时间
*/
private Long createDate;
}
MyBatisPlus会修改updateArticle中不为null的值
@Service
public class ThreadService {
/**
* 更新阅读数量
*
* @param articleMapper
* @param article
*/
@Async("taskExecutor")
//期望此操作在线程池执行 不会影响原有的主线程
public void updateViewCount(ArticleMapper articleMapper, Article article) {
Integer viewCounts = article.getViewCounts();
Article updateArticle = new Article();
updateArticle.setViewCounts(viewCounts + 1);
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Article::getId,article.getId());
//多个线程操作同一个变量
//为了线程安全 如果操作的时候发现与期望的阅读数量不一致,修改失败
queryWrapper.eq(Article::getViewCounts,viewCounts);
//UPDATE ms_article SET view_counts=? WHERE (id = ? AND view_counts = ?)
articleMapper.update(updateArticle,queryWrapper);
}
}
1. 评论列表
1.1涉及的表以及pojo
CREATE TABLE `blog`.`ms_comment` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`create_date` bigint(0) NOT NULL,
`article_id` int(0) NOT NULL,
`author_id` bigint(0) NOT NULL,
`parent_id` bigint(0) NOT NULL,
`to_uid` bigint(0) NOT NULL,
`level` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `article_id`(`article_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
package com.pjp.blog.dao.pojo;
import lombok.Data;
@Data
public class Comment {
private Long id;
private String content;
private Long createDate;
private Long articleId;
private Long authorId;
private Long parentId;
private Long toUid;
private Integer level;
}
1.1 接口说明
接口url:/comments/article/{id}
请求方式:GET
请求参数:
参数名称 | 参数类型 | 说明 |
id | long | 文章id(路径参数) |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id": "53",
"author": {
"nickname": "土豆",
"avatar": "/static/user/user_1.png",
"id": 1689624302930862082
},
"content": "good job",
"children": [
{
"id": "54",
"author": {
"nickname": "pjp",
"avatar": "http://localhost:8080/static/user/admin.png",
"id": 1
},
"content": "厉害呀!土豆",
"children": [],
"createDate": "1970-01-20 21:58",
"level": 2,
"toUser": {
"nickname": "土豆",
"avatar": "/static/user/user_1.png",
"id": 1689624302930862082
}
}
],
"createDate": "1970-01-20 21:57",
"level": 1,
"toUser": null
},
{
"id": "55",
"author": {
"nickname": "pjp",
"avatar": "http://localhost:8080/static/user/admin.png",
"id": 1
},
"content": "写的好",
"children": [
{
"id": "56",
"author": {
"nickname": "pjp",
"avatar": "http://localhost:8080/static/user/admin.png",
"id": 1
},
"content": "111",
"children": [],
"createDate": "1973-11-26 08:52",
"level": 2,
"toUser": {
"nickname": "pjp",
"avatar": "http://localhost:8080/static/user/admin.png",
"id": 1
}
}
],
"createDate": "1973-11-27 09:53",
"level": 1,
"toUser": null
}
]
}
1.2 Controller
package com.pjp.blog.controller;
import com.pjp.blog.service.CommentsService;
import com.pjp.blog.vo.Result;
import com.pjp.blog.vo.params.CommentParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("comments")
public class CommentsController {
@Autowired
private CommentsService commentsService;
@GetMapping("article/{id}")
public Result comments(@PathVariable("id") Long articleId) {
return commentsService.findCommentsByArticleId(articleId);
}
}
1.3 Service
package com.pjp.blog.service;
import com.pjp.blog.vo.Result;
import com.pjp.blog.vo.params.CommentParam;
public interface CommentsService {
/**
* 根据文章Id 查找评论列表
* @param articleId
* @return
*/
Result findCommentsByArticleId(Long articleId);
}
package com.pjp.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.pjp.blog.dao.mapper.CommentsMapper;
import com.pjp.blog.dao.pojo.Comment;
import com.pjp.blog.dao.pojo.SysUser;
import com.pjp.blog.service.CommentsService;
import com.pjp.blog.service.SysUserService;
import com.pjp.blog.utils.UserThreadLocal;
import com.pjp.blog.vo.CommentVo;
import com.pjp.blog.vo.Result;
import com.pjp.blog.vo.UserVo;
import com.pjp.blog.vo.params.CommentParam;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class CommentsServiceImpl implements CommentsService {
@Autowired
private CommentsMapper commentsMapper;
@Autowired
private SysUserService sysUserService;
@Override
public Result findCommentsByArticleId(Long articleId) {
/**
* 1. 根据文章id 查询 评论列表 从 comment 表中查询
* 2. 根据作者的id 查询作者的信息
* 3. 判断 如果level = 1 要去查询它有没有子评论
* 4. 如果有 根据评论id 进行查询 (parent_id)
*/
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getArticleId, articleId);
queryWrapper.eq(Comment::getLevel, 1);
List<Comment> comments = commentsMapper.selectList(queryWrapper);
return Result.success(copyList(comments));
}
@Override
public Result comment(CommentParam commentParam) {
SysUser sysUser = UserThreadLocal.get();
Comment comment = new Comment();
comment.setArticleId(commentParam.getArticleId());
comment.setAuthorId(sysUser.getId());
comment.setContent(commentParam.getContent());
comment.setCreateDate(System.currentTimeMillis());
Long parent = commentParam.getParent();
if (parent == null || parent == 0) {
comment.setLevel(1);
} else {
comment.setLevel(2);
}
comment.setParentId(parent == null ? 0 : parent);
Long toUserId = commentParam.getToUserId();
comment.setToUid(toUserId == null ? 0 : toUserId);
this.commentsMapper.insert(comment);
UpdateWrapper<Article> updateWrapper = Wrappers.update();
updateWrapper.eq("id",comment.getArticleId());
updateWrapper.setSql(true,"comment_counts=comment_counts+1");
this.articleMapper.update(null,updateWrapper);
CommentVo commentVo = copy(comment);
return Result.success(commentVo);
}
private List<CommentVo> copyList(List<Comment> comments) {
List<CommentVo> commentVoList = new ArrayList<>();
for (Comment comment : comments) {
commentVoList.add(copy(comment));
}
return commentVoList;
}
private CommentVo copy(Comment comment) {
CommentVo commentVo = new CommentVo();
BeanUtils.copyProperties(comment, commentVo);
//时间格式化
commentVo.setCreateDate(new DateTime(comment.getCreateDate()).toString("YYYY-MM-dd HH:mm"));
//作者信息
Long authorId = comment.getAuthorId();
UserVo userVo = sysUserService.findUserVoById(authorId);
commentVo.setAuthor(userVo);
//查询评论的评论 去comment表中找parent_id等于当前评论的id
List<CommentVo> childrenComlist = findChildrenComById(comment.getId());
commentVo.setChildren(childrenComlist);
//to user 评论的评论 给谁的评论评论
Integer level = comment.getLevel();
if (level > 1) {
Long toUid = comment.getToUid();
UserVo userVoById = sysUserService.findUserVoById(toUid);
commentVo.setToUser(userVoById);
}
return commentVo;
}
private List<CommentVo> findChildrenComById(Long id) {
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getParentId, id);
queryWrapper.eq(Comment::getLevel, 2);
List<Comment> comments = commentsMapper.selectList(queryWrapper);
return copyList(comments);
}
}
返回的数据:
@Data
public class CommentVo {
private Long id;
private UserVo author;
private String content;
private List<CommentVo> childrens;
private String createDate;
private Integer level;
private UserVo toUser;
}
@Data
public class UserVo {
private String nickname;
private String avatar;
private Long id;
}
在SysUserService中提供 查询用户信息的服务:
/**
* 根据 id 查询用户 转换为UserVo
* @param authorId
* @return
*/
UserVo findUserVoById(Long authorId);
package com.pjp.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.pjp.blog.dao.mapper.SysUserMapper;
import com.pjp.blog.dao.pojo.SysUser;
import com.pjp.blog.service.SysUserService;
import com.pjp.blog.service.TokenService;
import com.pjp.blog.utils.JWTUtils;
import com.pjp.blog.vo.ErrorCode;
import com.pjp.blog.vo.LoginUserVo;
import com.pjp.blog.vo.Result;
import com.pjp.blog.vo.UserVo;
import org.apache.catalina.User;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class SysUserServiceIml implements SysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private TokenService tokenService;
@Override
public UserVo findUserVoById(Long authorId) {
SysUser sysUser = sysUserMapper.selectById(authorId);
if (sysUser == null) {
sysUser = new SysUser();
sysUser.setId(1L);
sysUser.setNickname("游客");
sysUser.setAvatar("/static/user/user_6.png");
}
UserVo userVo = new UserVo();
BeanUtils.copyProperties(sysUser, userVo);
return userVo;
}
}
测试 成功显示一级评论和二级评论
2. 评论
2.1 接口说明
接口url:/comments/create/change
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
articleId | long | 文章id |
content | string | 评论内容 |
parent | long | 父评论id |
toUserId | long | 被评论的用户id |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": null
}
2.2 加入到登录拦截器中
package com.pjp.blog.config;
import com.pjp.blog.handler.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(loginInterceptor).addPathPatterns("/**")
// .excludePathPatterns("/login").excludePathPatterns("/register");
// 拦截test接口,后续遇到实际需要拦截的接口时,再配置拦截相应的接口
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/test")
.addPathPatterns("/comments/create/change");
;
}
}
2.3 Controller
构建评论参数对象:
@Data
public class CommentParam {
private Long articleId;
private String content;
private Long parent;
private Long toUserId;
}
@RestController
@RequestMapping("comments")
public class CommentsController {
@Autowired
private CommentsService commentsService;
@PostMapping("create/change")
public Result comment(@RequestBody CommentParam commentParam) {
return commentsService.comment(commentParam);
}
}
//防止前端 精度损失 把id转为string
// 分布式id 比较长,传到前端 会有精度损失,必须转为string类型 进行传输,就不会有问题了
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
输入评论 即可成功评论
点击评论