5.文章详情、使用线程池,更新阅读次数
文章详情:
接口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.*;
-
-
-
-
public class ArticleController
-
{
-
-
-
private ArticleService articleService;
-
/*如果参数时放在请求体中,application/json传入后台的话,那么后台要用@RequestBody才能接收到;
-
如果不是放在请求体中的话,那么后台接收前台传过来的参数时,要用@RequestParam来接收。
-
或者形参前 什么也不写也能接收。*/
-
-
/**
-
* 文章详情
-
*/
-
-
public Result findArticleById( 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;
-
-
-
public class ArticleServiceImpl implements ArticleService
-
{
-
-
-
private ArticleMapper articleMapper;
-
-
-
private TagService tagService;
-
-
-
private SysUserService sysUserService;
-
-
-
private ArticleBodyMapper articleBodyMapper;
-
-
-
private CategoryService categoryService;
-
/**
-
* 分页查询 article数据库表
-
* @param pageParams
-
* @return
-
*/
-
-
public Result listArticle(PageParams pageParams)
-
{
-
Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
-
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();/*查询器*/
-
queryWrapper.orderByDesc(Article::getWeight);/*是否置顶进行排序*/
-
queryWrapper.orderByDesc(Article::getCreateDate);/*根据创建时间进行降序排序 order by create_date desc*/
-
Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);/*等同于编写一个普通list查询,mybatis-plus自动替你分页*/
-
List<Article> records = articlePage.getRecords();
-
List<ArticleVo> articleVoList = copyList(records,true,true);
-
return Result.success(articleVoList);
-
}
-
-
/**
-
* 将Article列表封装为ArticleVo列表,以便把数据传给前端
-
* @param records
-
* @param isTag
-
* @param isAuthor
-
* @return
-
*/
-
private List<ArticleVo> copyList(List<Article> records,boolean isTag,boolean isAuthor)
-
{
-
ArrayList<ArticleVo> 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"));
-
/*因为Article中的createDate(Long)与ArticleVo(String)中的createDate类型不同,所以需要进行手动赋值*/
-
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;
-
}
-
-
/**
-
* 通过文章id 查询文章详情表中的文章详情
-
* @param bodyId
-
* @return
-
*/
-
private ArticleBodyVo findArticleBodyById(Long bodyId)
-
{
-
ArticleBody articleBody = articleBodyMapper.selectById(bodyId);
-
ArticleBodyVo articleBodyVo = new ArticleBodyVo();
-
articleBodyVo.setContent(articleBody.getContent());
-
return articleBodyVo;
-
}
-
-
-
/**
-
* 文章详情
-
* @param articleId
-
* @return
-
*/
-
-
public Result findArticleById(Long articleId)
-
{
-
/**
-
* 1.把ArticleVo对应的ArticleBodyVo 和categories注释去掉,也就是现在需要使用这两个成员,并添加对应的Vo类
-
* 2.根据articleId查询文章article
-
* 3.根据bodyId 和categoryId 去关联查询对应的文章详情和文章标签
-
* 4.将查到的文章详情和文章标签注入到articleVo中
-
*/
-
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 lombok.Data;
-
-
import java.util.List;
-
-
-
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 String avatar;
-
-
private String authorId;
-
-
private ArticleBodyVo body;
-
-
private List<TagVo> tags;/*文章标签*/
-
-
private CategoryVo categoryVo;
-
}
1.2 ArticleBodyVo
-
package com.example.blog.vo;
-
-
import lombok.Data;
-
-
-
public class ArticleBodyVo
-
{
-
private String content;
-
}
1.3 CategoryVo
-
package com.example.blog.vo;
-
-
import lombok.Data;
-
-
-
public class CategoryVo
-
{
-
private String id;
-
-
private String avatar;
-
-
private String categoryName;
-
-
}
2.根据articleId查询文章article,获取对应的bodyId和categoryId
3.根据bodyId 和categoryId 去关联查询对应的文章详情和文章标签
ArticleServiceImpl:
-
/**
-
* 通过文章id 查询文章详情表中的文章详情
-
* @param bodyId
-
* @return
-
*/
-
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;
-
-
-
public class CategoryServiceImpl implements CategoryService
-
{
-
-
-
private CategoryMapper categoryMapper;
-
-
-
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);/*可以理解为article相同成员赋值给articleVo*/
-
/*BeanUtils.copyProperties(article, articleVo);
-
articleVo中的存在的属性,article中一定要有,但是article中可以有多余的属性;
-
article中与articleVo中相同的属性都会被替换,不管是否有值;
-
article、articleVo中的属性要名字相同,才能被赋值,不然的话需要手动赋值;
-
Spring的BeanUtils的CopyProperties方法需要对应的属性有getter和setter方法;
-
如果存在属性完全相同的内部类,但是不是同一个内部类,即分别属于各自的内部类,则spring会认为属性不同,不会copy*/
-
articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
-
/*因为Article中的createDate(Long)与ArticleVo(String)中的createDate类型不同,所以需要进行手动赋值*/
-
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 lombok.Data;
-
-
import java.util.List;
-
-
-
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 String avatar;
-
-
private String authorId;
-
-
private ArticleBodyVo body;
-
-
private List<TagVo> tags;/*文章标签*/
-
-
private CategoryVo categoryVo;
-
}
这里阅读数应该随着点击增加,所以需要使用线程池,更新阅读次数
查看完该文章之后,本应该直接返回数据,这个时候做了一个更新操作,更新时加写锁,阻塞其他的读操作,性能就会比较低。
更新肯定会增加此次接口的耗时,如果更新一旦除了问题,不能影响查看文章的操作。
这时可以增加线程池,把更新操作扔到线程池中去执行,这样就和主线程不相关了。
创建一个线程池:
-
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;
-
-
-
//开启多线程
-
public class ThreadPoolConfig
-
{
-
-
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中,添加一个线程池,用于增加阅读量
-
-
private ThreadService threadService;
-
-
public Result findArticleById(Long articleId)
-
{
-
/**
-
* 1.把ArticleVo对应的ArticleBodyVo 和categories注释去掉,也就是现在需要使用这两个成员,并添加对应的Vo类
-
* 2.根据articleId查询文章article
-
* 3.根据bodyId 和categoryId 去关联查询对应的文章详情和文章标签
-
* 4.将查到的文章详情和文章标签注入到articleVo中
-
*/
-
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;
-
-
-
public class ThreadService
-
{
-
//期望此次操作在线程池执行 不会影响原有的主线程
-
//将该任务丢到线程池中
-
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 {
-
//睡眠 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;
-
-
-
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;
-
-
/**
-
* 作者id
-
*/
-
private Long authorId;
-
/**
-
* 内容id
-
*/
-
private Long bodyId;
-
/**
-
*类别id
-
*/
-
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;
-
-
-
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;
-
}
以及对应vo
-
package com.example.blog.vo;
-
-
-
import lombok.Data;
-
-
import java.util.List;
-
-
-
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 String avatar;
-
-
private String authorId;
-
-
private ArticleBodyVo body;
-
-
private List<TagVo> tags;/*文章标签*/
-
-
private CategoryVo categoryVo;
-
}
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgeibbc
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01