接口url:/articles/view/{id}
请求方式:POST
请求参数:
| 参数名称 | 参数类型 | 说明 |
|---|---|---|
| id | long | 文章id(路径参数) |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": "token"
}
ArticleController:
package com.example.blog.controller;
import com.example.blog.service.ArticleService;
import com.example.blog.vo.Result;
import com.example.blog.vo.params.PageParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RequestMapping("/articles")
@RestController
public class ArticleController
{
@Autowired
private ArticleService articleService;
@PostMapping("/view/{id}")
public Result findArticleById(@PathVariable("id") Long articleId)
{
return articleService.findArticleById(articleId);
}
}
ArticleServiceImpl:
package com.example.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.blog.dao.mapper.ArticleBodyMapper;
import com.example.blog.dao.mapper.ArticleMapper;
import com.example.blog.dao.mapper.CategoryMapper;
import com.example.blog.dos.Archives;
import com.example.blog.entity.Article;
import com.example.blog.entity.ArticleBody;
import com.example.blog.entity.Category;
import com.example.blog.service.ArticleService;
import com.example.blog.service.CategoryService;
import com.example.blog.service.SysUserService;
import com.example.blog.service.TagService;
import com.example.blog.vo.ArticleBodyVo;
import com.example.blog.vo.ArticleVo;
import com.example.blog.vo.CategoryVo;
import com.example.blog.vo.Result;
import com.example.blog.vo.params.PageParams;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.Date;
import java.util.ArrayList;
import java.util.List;
@Service
public class ArticleServiceImpl implements ArticleService
{
@Autowired
private ArticleMapper articleMapper;
@Autowired
private TagService tagService;
@Autowired
private SysUserService sysUserService;
@Autowired
private ArticleBodyMapper articleBodyMapper;
@Autowired
private CategoryService categoryService;
@Override
public Result listArticle(PageParams pageParams)
{
Page page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByDesc(Article::getWeight);
queryWrapper.orderByDesc(Article::getCreateDate);
Page articlePage = articleMapper.selectPage(page, queryWrapper);
List records = articlePage.getRecords();
List articleVoList = copyList(records,true,true);
return Result.success(articleVoList);
}
private List copyList(List records,boolean isTag,boolean isAuthor)
{
ArrayList articleVoList = new ArrayList<>();
for(Article record:records)
{
articleVoList.add(copy(record,isTag,isAuthor,false,false));
}
return articleVoList;
}
private ArticleVo copy(Article article,boolean isTag,boolean isAuthor,boolean isBody,boolean isCategory)
{
ArticleVo articleVo = new ArticleVo();
BeanUtils.copyProperties(article,articleVo);
articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
if(isTag)
{
Long articleId = article.getId();
articleVo.setTags(tagService.findTagsById(articleId));
}
if(isAuthor)
{
Long authorId = article.getAuthorId();
articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname());
}
if(isBody)
{
Long bodyId = article.getBodyId();
articleVo.setBody(findArticleBodyById(bodyId));
}
if(isCategory)
{
Long categoryId = article.getCategoryId();
articleVo.setCategory(categoryService.findCategoryById(categoryId));
}
return articleVo;
}
private ArticleBodyVo findArticleBodyById(Long bodyId)
{
ArticleBody articleBody = articleBodyMapper.selectById(bodyId);
ArticleBodyVo articleBodyVo = new ArticleBodyVo();
articleBodyVo.setContent(articleBody.getContent());
return articleBodyVo;
}
@Override
public Result findArticleById(Long articleId)
{
Article article = articleMapper.selectById(articleId);
ArticleVo articleVo = copy(article, true, true,true,true);
return Result.success(articleVo);
}
}
这里有一点复杂,理一下逻辑
1.把ArticleVo对应的ArticleBodyVo 和category注释去掉,也就是现在需要使用这两个成员(文章详情和文章分类),并添加对应的Vo类
1.1ArticleVo:
package com.example.blog.vo;
import com.example.blog.entity.Tag;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.util.List;
@Data
public class ArticleVo {
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String title;
private String summary;
private int commentCounts;
private int viewCounts;
private int weight;
private String createDate;
private String author;
private ArticleBodyVo body;
private List tags;
private CategoryVo category;
}
1.2 ArticleBodyVo
package com.example.blog.vo;
import lombok.Data;
@Data
public class ArticleBodyVo
{
private String content;
}
1.3 CategoryVo
package com.example.blog.vo;
import lombok.Data;
@Data
public class CategoryVo
{
private Long id;
private String avatar;
private String categoryName;
}
2.根据articleId查询文章article,获取对应的bodyId和categoryId
3.根据bodyId 和categoryId 去关联查询对应的文章详情和文章标签
ArticleServiceImpl:
private ArticleBodyVo findArticleBodyById(Long bodyId)
{
ArticleBody articleBody = articleBodyMapper.selectById(bodyId);
ArticleBodyVo articleBodyVo = new ArticleBodyVo();
articleBodyVo.setContent(articleBody.getContent());
return articleBodyVo;
}
CategoryServiceImpl:
package com.example.blog.service.impl;
import com.example.blog.dao.mapper.CategoryMapper;
import com.example.blog.entity.Category;
import com.example.blog.service.CategoryService;
import com.example.blog.vo.CategoryVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CategoryServiceImpl implements CategoryService
{
@Autowired
private CategoryMapper categoryMapper;
@Override
public CategoryVo findCategoryById(Long categoryId)
{
Category category = categoryMapper.selectById(categoryId);
return copy(category);
}
private CategoryVo copy(Category category)
{
CategoryVo categoryVo = new CategoryVo();
BeanUtils.copyProperties(category,categoryVo);
return categoryVo;
}
}
4.将查到的文章详情和文章标签注入到articleVo中
ArticleVo articleVo = copy(article, true, true,true,true);
private ArticleVo copy(Article article,boolean isTag,boolean isAuthor,boolean isBody,boolean isCategory)
{
ArticleVo articleVo = new ArticleVo();
BeanUtils.copyProperties(article,articleVo);
articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
if(isTag)
{
Long articleId = article.getId();
articleVo.setTags(tagService.findTagsById(articleId));
}
if(isAuthor)
{
Long authorId = article.getAuthorId();
articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname());
}
if(isBody)
{
Long bodyId = article.getBodyId();
articleVo.setBody(findArticleBodyById(bodyId));
}
if(isCategory)
{
Long categoryId = article.getCategoryId();
articleVo.setCategory(categoryService.findCategoryById(categoryId));
}
return articleVo;
}
5.重点!!
==> Preparing:
SELECT id,title,summary,comment_counts,view_counts,author_id,body_id,category_id,weight,create_date FROM ms_article WHERe id=?
==> Parameters: 1405564731300831200(Long)
<== Total: 0
发现:前端传入的值1405564731300831200丢失了两位精度,所以前端传入的articleId,后端通过该id找不到对应的article!!!
原因:由于前端传入的articleId丢失了精度,java中long数据能表示的范围比js中number大,在跟前端交互时,这样也就意味着部分数值在js中存不下(变成不准确的值)。
解决方案:ArticleVo 中的id字段使用fastjson的ToStringSerializer注解,让系统序列化时,保留相关精度。
package com.example.blog.vo;
import com.example.blog.entity.Tag;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.util.List;
@Data
public class ArticleVo {
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String title;
private String summary;
private int commentCounts;
private int viewCounts;
private int weight;
private String createDate;
private String author;
private ArticleBodyVo body;
private List tags;
private CategoryVo category;
}
这里阅读数应该随着点击增加,所以需要使用线程池,更新阅读次数
查看完该文章之后,本应该直接返回数据,这个时候做了一个更新操作,更新时加写锁,阻塞其他的读操作,性能就会比较低。
更新肯定会增加此次接口的耗时,如果更新一旦除了问题,不能影响查看文章的操作。
这时可以增加线程池,把更新操作扔到线程池中去执行,这样就和主线程不相关了。
创建一个线程池:
package com.example.blog.cofig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync //开启多线程
public class ThreadPoolConfig
{
@Bean("taskExcutor")
public Executor asyncServiceExecutor()
{
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(5);
// 设置最大线程数
executor.setMaxPoolSize(20);
//配置队列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("博客");
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteonShutdown(true);
//执行初始化
executor.initialize();
return executor;
}
}
ArticleServiceImpl中的显示文章详情的方法findArticleById中,添加一个线程池,用于增加阅读量
@Autowired
private ThreadService threadService;
@Override
public Result findArticleById(Long articleId)
{
Article article = articleMapper.selectById(articleId);
ArticleVo articleVo = copy(article, true, true,true,true);
//查看完该文章之后,本应该直接返回数据,这个时候做了一个更新操作,更新时加写锁,阻塞其他的读操作,性能就会比较低
//更新肯定会增加此次接口的耗时,如果更新一旦除了问题,不能影响 查看文章的操作
//这时可以增加线程池,把更新操作扔到线程池中去执行,这样就和主线程不相关了
threadService.updateArticleViewCount(articleMapper,article);
return Result.success(articleVo);
}
ThreadService:
package com.example.blog.service;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.example.blog.dao.mapper.ArticleMapper;
import com.example.blog.entity.Article;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class ThreadService
{
//期望此次操作在线程池执行 不会影响原有的主线程
@Async("taskExcutor")//将该任务丢到线程池中
public void updateArticleViewCount(ArticleMapper articleMapper, Article article)
{
int viewCounts = article.getViewCounts();
Article articleUpdate = new Article();
articleUpdate.setViewCounts(viewCounts+1);
LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Article::getId,article.getId());
//设置一个 为了在多线程的环境下 线程安全
//乐观锁的一个思想 如果操作的时候发现阅读数与期望的阅读数不一致,修改失败
updateWrapper.eq(Article::getViewCounts,viewCounts);
articleMapper.update(articleUpdate,updateWrapper);
try {
//睡眠 ThredService中的方法 5秒,不会影响主线程的使用,即文章详情会很快的显示出来,不受影响
Thread.sleep(5000);
System.out.println("更新完成了~~~");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这里有一个Bug
由于article类中的commentCounts,viewCounts,weight 字段为int
package com.example.blog.entity;
import lombok.Data;
@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 int commentCounts;
private int viewCounts;
private Long authorId;
private Long bodyId;
private Long categoryId;
private int weight = Article_Common;
private Long createDate;
}
发现:会造成更新阅读次数的时候,将commentCounts,viewCounts,weight 字段设为初始值0,也就是这条语句
articleMapper.update(articleUpdate,updateWrapper);
原因:在更新阅读次数的时候,只要该成员值不为null,mybatisPlus都会把该成员赋值进去更新,也就是把commentCounts,viewCounts,weight这三个int类型的赋值进去
解决:如果设置为integer,该成员值就为null,就不会被更新
总结:需要把与数据库对应字段的成员设置为integer,而不是int类型
修改:
package com.example.blog.entity;
import lombok.Data;
@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;
private Long authorId;
private Long bodyId;
private Long categoryId;
private Integer weight;
private Long createDate;
}
以及对应vo
package com.example.blog.vo;
import com.example.blog.entity.Tag;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.util.List;
@Data
public class ArticleVo {
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String title;
private String summary;
private Integer commentCounts;
private Integer viewCounts;
private Integer weight;
private String createDate;
private String author;
private ArticleBodyVo body;
private List tags;
private CategoryVo category;
}



