八、文章详情和阅读数
1、文章详情
1.1 接口说明
接口url:/articles/view/{id}
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
id | long | 文章id(路径参数) |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": "token"
}
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;
package com.ling.blog.dao.pojo;
import lombok.Data;
@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;
package com.ling.blog.dao.pojo;
import lombok.Data;
@Data
public class Category {
private Long id;
private String avatar;
private String categoryName;
private String description;
}
1.3 controller
/**
* 显示文章详情
* @param articleId
* @return
*/
@PostMapping("/view/{id}")
public Result findArticleById(@PathVariable("id") Long articleId){
return articleService.findArticleById(articleId);
}
1.4 vo
package com.ling.blog.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.ling.blog.dao.pojo.SysUser;
import com.ling.blog.dao.pojo.Tag;
import lombok.Data;
import java.util.List;
@Data
public class ArticleVo {
//要在这里加上注解,保证分布式id的精度在转到vo时不变,才能显示出文章体
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String title;
private String summary;
//将所有的int转成Integer,否则在点击阅读文章返回后,评论数会置0,这是由于update语句引起的,int有初值0,而Intege会为空,不进行更新
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;
//private List<CategoryVo> categorys;
}
package com.ling.blog.vo;
import lombok.Data;
@Data
public class ArticleBodyVo {
private String content;
}
package com.ling.blog.vo;
import lombok.Data;
@Data
public class CategoryVo {
private Long id;
private String avatar;
private String categoryName;
}
1.5 service
package com.ling.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ling.blog.dao.dos.Archives;
import com.ling.blog.dao.mapper.ArticleBodyMapper;
import com.ling.blog.dao.mapper.ArticleMapper;
import com.ling.blog.dao.mapper.TagMapper;
import com.ling.blog.dao.pojo.Article;
import com.ling.blog.dao.pojo.ArticleBody;
import com.ling.blog.service.*;
import com.ling.blog.vo.ArticleBodyVo;
import com.ling.blog.vo.ArticleVo;
import com.ling.blog.vo.Result;
import com.ling.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 ArticleBodyMapper articleBodyMapper;
@Autowired
private CategoryService categoryService;
@Autowired
private ThreadService threadService;
/**
* 最热文章
* @param limit
* @return
*/
@Override
public Result hotArticle(int limit) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
//select id,title from ms_article order by view_counts desc limit 5
queryWrapper.orderByDesc(Article::getViewCounts);
queryWrapper.select(Article::getId,Article::getTitle);
queryWrapper.last("limit "+limit);
List<Article> articles = articleMapper.selectList(queryWrapper);
return Result.success(copyList(articles,false,false));
}
/**
* 最新文章
* @param limit
* @return
*/
@Override
public Result newArticle(int limit) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
//select id,title from ms_article order by create_date desc limit 5
queryWrapper.orderByDesc(Article::getCreateDate);
queryWrapper.select(Article::getId,Article::getTitle);
queryWrapper.last("limit "+limit);
List<Article> articles = articleMapper.selectList(queryWrapper);
return Result.success(copyList(articles,false,false));
}
/**
* 文章归档
*
* @return
*/
@Override
public Result listArchives() {
List<Archives> archivesList = articleMapper.listArchives();
return Result.success(archivesList);
}
/**
* 查看文章详情
* @param articleId
* @return
*/
@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);
}
/**
* 文章列表
* @param pageParams
* @return
*/
@Override
public Result listArticle(PageParams pageParams) {
/**
* 1、分页查询article数据库表
*/
Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
//是否置顶排序
//再按创建时间排序
queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate);
Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);
List<Article> records = articlePage.getRecords();
List<ArticleVo> articleVoList = copyList(records,true,true);
return Result.success(articleVoList);
}
private List<ArticleVo> copyList(List<Article> records,boolean isTag,boolean isAuthor) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article article : records) {
articleVoList.add(copy(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(copy(article,isTag,isAuthor,isBody,isCategory));
}
return articleVoList;
}
public 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.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;
}
/**
* 根据文章id查询文章内容
* @param bodyId
* @return
*/
/* @Autowired
private ArticleBodyMapper articleBodyMapper;*/
private ArticleBodyVo findArticleBodyById(Long bodyId) {
ArticleBody articleBody = articleBodyMapper.selectById(bodyId);
ArticleBodyVo articleBodyVo = new ArticleBodyVo();
articleBodyVo.setContent(articleBody.getContent());
return articleBodyVo;
}
}
要查询类别,应该创建对应的service
package com.ling.blog.service;
import com.ling.blog.vo.CategoryVo;
import java.util.List;
public interface CategoryService {
/**
* 根据id查询文章类别
* @param categoryId
* @return
*/
CategoryVo findCategoryById(Long categoryId);
}
package com.ling.blog.service.impl;
import com.ling.blog.dao.mapper.CategoryMapper;
import com.ling.blog.dao.pojo.Category;
import com.ling.blog.service.CategoryService;
import com.ling.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);
CategoryVo categoryVo = new CategoryVo();
BeanUtils.copyProperties(category,categoryVo);
return categoryVo;
}
}
1.6 dao
package com.ling.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ling.blog.dao.pojo.Category;
public interface CategoryMapper extends BaseMapper<Category> {
}
package com.ling.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ling.blog.dao.pojo.ArticleBody;
public interface ArticleBodyMapper extends BaseMapper<ArticleBody> {
}
2、使用线程池更新阅读次数
2.1 线程池配置
package com.ling.blog.config;
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("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;
}
}
2.2 使用
package com.ling.blog.service;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.ling.blog.dao.mapper.ArticleMapper;
import com.ling.blog.dao.pojo.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<Article> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Article::getId,article.getId());
//设置一个为了在多线程环境下 线程安全
updateWrapper.eq(Article::getViewCounts,viewCounts);
articleMapper.update(articleUpdate,updateWrapper);
try {
//睡眠5秒 证明不会影响主线程的使用
Thread.sleep(5000);
System.out.println("更新完成。。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在ArticleServiceImpl中:
@Autowired
private ThreadService threadService;
/**
* 查看文章详情
* @param articleId
* @return
*/
@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);
}