文章详情
- 根据文档可知请求地址、请求方式、请求参数。
ArticleController
- 因为是文章的,所以还是使用关于文章的ArticleController
@PostMapping("/view/{id}")
public Result findArticleById(@PathVariable("id") Long id){
return articleService.findArticleById(id);
}
- 根据返回数据形式,返回的是查到的文章详情。
ArticleService
/**
* 查看文章详情
* @param articleId
* @return
*/
Result findArticleById(Long articleId);
ArticleServiceImpl
- 因为在根据Id查找文章的时候,不仅需要显示文章信息,同时也需要标签信息,作者信息、内容信息以及类别信息,所以涉及到的表:文章表、标签表、用户表、内容表、以及类别表
- 将需要的Service进行注入
- 因为返回的是一个ArticleVo类,因为在文章详情涉及的信息很多,封装一个类专门用来表示
ArticleVo
@Data
public class ArticleVo {
// @JsonSerialize(using = ToStringSerializer.class)
private String 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<TagVo> tags;
private CategoryVo category;
}
CategoryVo
@Data
public class CategoryVo {
private String id;
private String avatar;
private String categoryName;
// private String description;
}
@Override
public Result findArticleById(Long articleId) {
// 1. 根据id查询文章信息
// 2. 根据bodyId和categoryId去做关联查询
Article article = articleMapper.selectById(articleId);
ArticleVo articleVo = copy(article,true,true,true,true);
return Result.success(articleVo);
//用来将传入的Article对象的属性复制给articleVo对象中去
private ArticleVo copy(Article article, boolean isTag, boolean isAuthor, boolean isBody,boolean isCategory){
ArticleVo articleVo = new ArticleVo();
articleVo.setId(String.valueOf(article.getId()));
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.findTagsByArticleId(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;
}
List<TagVo> findTagsByArticleId(Long articleId);
SysUserService
SysUser findUserById(Long id);
CategoryService
CategoryVo findCategoryById(Long categoryId);
CategoryServiceImpl
@Override
public SysUser findUserById(Long id) {
SysUser sysUser = sysUserMapper.selectById(id);
if(sysUser == null){
sysUser = new SysUser();
sysUser.setNickname("lbj");
}
return sysUser;
}
CategoryServiceImpl
@Override
public CategoryVo findCategoryById(Long categoryId) {
Category category = categoryMapper.selectById(categoryId);
CategoryVo categoryVo = new CategoryVo();
BeanUtils.copyProperties(category,categoryVo);
return categoryVo;
}
文章阅读次数更新
线程池配置
@Configuration
@EnableAsync // 开启多线程
public class ThreadPoolConfig {
@Bean("taskExecutor")
public Executor asyncServiceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(5);
// 设置最大线程数
executor.setMaxPoolSize(20);
//配置队列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("lbj博客项目");
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//执行初始化
executor.initialize();
return executor;
}
}
使用
@Component
public class ThreadService {
//此操作在线程池中执行,不会影响原有的主线程
@Async("taskExecutor")
public void updateArticleViewCount(ArticleMapper articleMapper, Article article) {
Integer viewCounts = article.getViewCounts();
Article articleUpdate = new Article();
articleUpdate.setViewCounts(viewCounts + 1);
LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Article::getId,article.getId());
// 多线程环境下保证线程安全,类似于乐观锁,检查是否相等,相等了才进行+1操作
wrapper.eq(Article::getViewCounts, viewCounts);
// update article set view_count=100 where view_count=99 and id=11
articleMapper.update(articleUpdate,wrapper);
// try {
// Thread.sleep(5000);
// System.out.println("更新完成");
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}
ArticleServiceImpl
@Override
public Result findArticleById(Long articleId) {
// 1. 根据id查询文章信息
// 2. 根据bodyId和categoryId去做关联查询
Article article = articleMapper.selectById(articleId);
ArticleVo articleVo = copy(article,true,true,true,true);
// 查看完文章,新增阅读数,有没有问题?
// 查看完文章之后,本应直接返回数据,这时候做一个更新操作,更新是加写锁的,会阻塞其他的读操作,性能就会比较低
// 更新增加了此次接口的耗时,如果一旦更新出问题,不能影响查看文章的操作。
// 使用线程池,可以把更新操作扔到线程池中去执行,和主线程就不相关
threadService.updateArticleViewCount(articleMapper,article);
return Result.success(articleVo);
}
- 有几个注意的点:
- 线程池配置的时候开使用注解
@EnableAsync
开启多线程
- 给线程池取名
@Bean(value = "taskExecutor")
。
- 在ThreadService中要注意标识接下来的操作是在线程池中进行,不会影响原有的主线程,
@Async("taskExecutor")
。确定在线程池中进行
- 在多线程环境下,要保证线程安全,
wrapper.eq(Article::getViewCounts, viewCounts);
,这句话表明,先检查原先阅读数和当前阅读是是否一致,只有一致的时候才进行+1,类似于乐观锁
- 思考:为什么这里更新阅读数要使用一个线程进行?
- 因为如果在主方法中进行更新,因为更新是一个写锁的操作,会阻塞其他的读操作,那么同步进行的话,就会造成更新的时候无法读取到文章内容。
- 同时一旦更新出问题,使用一个线程去执行更新操作,那么也不会影响查看文章这个操作。