SpringBoot学习项目-个人博客系统-part7

文章详情

  1. 根据文档可知请求地址、请求方式、请求参数。

ArticleController

  • 因为是文章的,所以还是使用关于文章的ArticleController
@PostMapping("/view/{id}")
    public Result findArticleById(@PathVariable("id") Long id){
        return articleService.findArticleById(id);
    }
  1. 根据返回数据形式,返回的是查到的文章详情。

ArticleService

/**
     * 查看文章详情
     * @param articleId
     * @return
     */
    Result findArticleById(Long articleId);

ArticleServiceImpl

  • 因为在根据Id查找文章的时候,不仅需要显示文章信息,同时也需要标签信息,作者信息、内容信息以及类别信息,所以涉及到的表:文章表、标签表、用户表、内容表、以及类别表
  1. 将需要的Service进行注入
  2. 因为返回的是一个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;
    }

TagService

List<TagVo> findTagsByArticleId(Long articleId);

TagServiceImpl

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);
    }
  • 有几个注意的点:
    1. 线程池配置的时候开使用注解@EnableAsync 开启多线程
    2. 给线程池取名@Bean(value = "taskExecutor")
    3. 在ThreadService中要注意标识接下来的操作是在线程池中进行,不会影响原有的主线程,@Async("taskExecutor")。确定在线程池中进行
    4. 在多线程环境下,要保证线程安全,wrapper.eq(Article::getViewCounts, viewCounts);,这句话表明,先检查原先阅读数和当前阅读是是否一致,只有一致的时候才进行+1,类似于乐观锁
  • 思考:为什么这里更新阅读数要使用一个线程进行?
    1. 因为如果在主方法中进行更新,因为更新是一个写锁的操作,会阻塞其他的读操作,那么同步进行的话,就会造成更新的时候无法读取到文章内容。
    2. 同时一旦更新出问题,使用一个线程去执行更新操作,那么也不会影响查看文章这个操作。
上一篇:Android 修改按键映射


下一篇:Latex