目录
1. 文章详情
1.1 接口说明
接口url:/articles/view/{id}
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
id | long | 文章id(路径参数) |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": {
"id": 1,
"title": "springboot介绍以及入门案例",
"summary": "通过Spring Boot实现的服务,只需要依靠一个Java类,把它打包成jar,
并通过`java -jar`命令就可以运行起来。\r\n\r\n这一切相较于传统Spring应用来说,已经变得非常的轻便、简单。",
"commentCounts": 1,
"viewCounts": 125,
"weight": 0,
"createDate": "1970-01-20 21:28",
"author": "pjp",
"body": {
"content": "# 1. Spring Boot\r\n\r\n........"
},
"tags": [
{
"id": 5,
"tagName": "Mybatis-Plus",
"avatar": null
},
{
"id": 7,
"tagName": "表达式",
"avatar": null
},
{
"id": 8,
"tagName": "Java",
"avatar": null
}
],
"category": {
"id": 2,
"avatar": "/static/category/back.png",
"categoryName": "后端"
}
}
}
1.2 涉及到的表及pojo
CREATE TABLE `blog`.`ms_article_body` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
`content_html` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
`article_id` bigint(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `article_id`(`article_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 38 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
@Data
public class ArticleBody {
private Long id;
private String content;
private String contentHtml;
private Long articleId;
}
CREATE TABLE `blog`.`ms_category` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
`category_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
@Data
public class Category {
private Long id;
private String avatar;
private String categoryName;
private String description;
}
1.3 Controller
package com.pjp.blog.controller;
import com.pjp.blog.service.ArticleService;
import com.pjp.blog.vo.ArticleVo;
import com.pjp.blog.vo.Result;
import com.pjp.blog.vo.params.PageParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.sql.PreparedStatement;
//json数据进行交互
@RestController
@RequestMapping("/articles")
public class ArticleController {
@Autowired
private ArticleService articleService;
/**
* 查询文章详情
* @param articleId
* @return
*/
@PostMapping("view/{id}")
public Result findArticleById(@PathVariable("id") Long articleId) {
return articleService.findArticleById(articleId);
}
}
1.4 Service
package com.pjp.blog.vo;
import lombok.Data;
import java.util.List;
/**
* 和前端数据进行交互
*/
@Data
public class ArticleVo {
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<TagVo> tags;
private CategoryVo category;
}
ArticleService
package com.pjp.blog.service;
import com.pjp.blog.vo.Result;
import com.pjp.blog.vo.params.PageParams;
public interface ArticleService {
/**
* 查询文章详情
* @param articleId
* @return
*/
Result findArticleById(Long articleId);
}
ArticleServiceImpl
package com.pjp.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.pjp.blog.dao.dos.Archives;
import com.pjp.blog.dao.mapper.ArticleBodyMapper;
import com.pjp.blog.dao.mapper.ArticleMapper;
import com.pjp.blog.dao.mapper.TagMapper;
import com.pjp.blog.dao.pojo.Article;
import com.pjp.blog.dao.pojo.ArticleBody;
import com.pjp.blog.service.ArticleService;
import com.pjp.blog.service.CategoryService;
import com.pjp.blog.service.SysUserService;
import com.pjp.blog.service.TagService;
import com.pjp.blog.vo.ArticleBodyVo;
import com.pjp.blog.vo.ArticleVo;
import com.pjp.blog.vo.Result;
import com.pjp.blog.vo.TagVo;
import com.pjp.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.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 CategoryService categoryService;
@Override
public Result findArticleById(Long articleId) {
/**
* 1.根据id查询 文章信息
* 2.根据bodyId和categoryId 去做关联查询
*/
Article article = articleMapper.selectById(articleId);
ArticleVo articleVo = cope(article, true, true, true, true);
return Result.success(articleVo);
}
private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article article : records) {
articleVoList.add(cope(article, isTag, isAuthor, false, false));
}
return articleVoList;
}
//方法重载
private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor, boolean isBody, boolean isCategory) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article article : records) {
articleVoList.add(cope(article, isTag, isAuthor, isBody, isCategory));
}
return articleVoList;
}
private ArticleVo cope(Article article, boolean isTag, boolean isAuthor, boolean isBody, boolean isCategory) {
ArticleVo articleVo = new ArticleVo();
BeanUtils.copyProperties(article, articleVo);
// vo中createDate为String Article中为long copy不了
articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyy-MM-dd HH:ss"));
// 不是所有的接口 都需要标签 作者信息
if (isTag) {
// 根据ArticleId 查询 TagId
Long articleId = article.getId();
articleVo.setTags(tagService.findTagByArticleId(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;
}
@Autowired
private ArticleBodyMapper articleBodyMapper;
private ArticleBodyVo findArticleBodyById(Long bodyId) {
ArticleBody articleBody = articleBodyMapper.selectById(bodyId);
ArticleBodyVo articleBodyVo = new ArticleBodyVo();
articleBodyVo.setContent(articleBody.getContent());
return articleBodyVo;
}
}
CategoryService
package com.pjp.blog.service;
import com.pjp.blog.vo.CategoryVo;
public interface CategoryService {
/**
* 查询分类信息
*
* @return
*/
CategoryVo findCategoryById(Long categoryId);
}
CategoryServiceImpl
package com.pjp.blog.service.impl;
import com.pjp.blog.dao.mapper.CategoryMapper;
import com.pjp.blog.dao.pojo.Category;
import com.pjp.blog.service.CategoryService;
import com.pjp.blog.vo.CategoryVo;
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 CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Override
public CategoryVo findCategoryById(Long categoryId) {
Category category = categoryMapper.selectById(categoryId);
CategoryVo categoryVo = new CategoryVo();
BeanUtils.copyProperties(category, categoryVo);
return categoryVo;
}
}
1.5 测试
2. 使用线程池 更新阅读次数
2.1 为什么要使用线程池来更新阅读次数:
查看文章的时候,我们要将文章的阅读数量+1,这个时候进行了一次更新操作,更新的时候会加写锁,阻塞其他的操作,那么整体查看文章的性能就会比较低,它必须等待新增加观看数量完成后才给前端返回文章详情。
2.2 解决办法:
利用线程池实现,将更新操作放入到线程池中执行,那么它就和主线程没有关系,也就不会互相影响速度了(这样前端看到的就是更新之前的阅读数量)
2.3 线程池配置
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;
@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("PJPBlog");
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//执行初始化
executor.initialize();
return executor;
}
}
2.4 使用
package com.pjp.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.pjp.blog.dao.dos.Archives;
import com.pjp.blog.dao.mapper.ArticleBodyMapper;
import com.pjp.blog.dao.mapper.ArticleMapper;
import com.pjp.blog.dao.mapper.TagMapper;
import com.pjp.blog.dao.pojo.Article;
import com.pjp.blog.dao.pojo.ArticleBody;
import com.pjp.blog.service.*;
import com.pjp.blog.vo.ArticleBodyVo;
import com.pjp.blog.vo.ArticleVo;
import com.pjp.blog.vo.Result;
import com.pjp.blog.vo.TagVo;
import com.pjp.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.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 CategoryService categoryService;
@Autowired
private ThreadService threadService;
@Override
public Result findArticleById(Long articleId) {
/**
* 1.根据id查询 文章信息
* 2.根据bodyId和categoryId 去做关联查询
*/
Article article = articleMapper.selectById(articleId);
ArticleVo articleVo = cope(article, true, true, true, true);
//查看文章后 新增阅读数
//查看文章之后 本应该直接返回数据 这时候做一个更新操作 更新时会加写锁 阻塞其他的读操作 性能会比较底
//更新操作 增加了此次接口的耗时 如果一旦更新出现问题,不能影响 查看文章的操作
//使用线程池 可以把更新操作扔到线程池中去执行 和主线程就不相关了
threadService.updateViewCount(articleMapper,article);
return Result.success(articleVo);
}
private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article article : records) {
articleVoList.add(cope(article, isTag, isAuthor, false, false));
}
return articleVoList;
}
//方法重载
private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor, boolean isBody, boolean isCategory) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article article : records) {
articleVoList.add(cope(article, isTag, isAuthor, isBody, isCategory));
}
return articleVoList;
}
private ArticleVo cope(Article article, boolean isTag, boolean isAuthor, boolean isBody, boolean isCategory) {
ArticleVo articleVo = new ArticleVo();
BeanUtils.copyProperties(article, articleVo);
// vo中createDate为String Article中为long copy不了
articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyy-MM-dd HH:ss"));
// 不是所有的接口 都需要标签 作者信息
if (isTag) {
// 根据ArticleId 查询 TagId
Long articleId = article.getId();
articleVo.setTags(tagService.findTagByArticleId(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;
}
@Autowired
private ArticleBodyMapper articleBodyMapper;
private ArticleBodyVo findArticleBodyById(Long bodyId) {
ArticleBody articleBody = articleBodyMapper.selectById(bodyId);
ArticleBodyVo articleBodyVo = new ArticleBodyVo();
articleBodyVo.setContent(articleBody.getContent());
return articleBodyVo;
}
}
package com.pjp.blog.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.pjp.blog.dao.mapper.ArticleMapper;
import com.pjp.blog.dao.pojo.Article;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@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);
}
}
2.5 测试
睡眠 ThredService中的方法 5秒,不会影响主线程的使用,即文章详情会很快的显示出来,不受影响
@Async("taskExecutor")
public void updateViewCount(ArticleMapper articleMapper,Article article){
try {
//睡眠5秒 证明不会影响主线程的使用
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}