SpringBoot+Vue博客前后端分离项目 八 文章详情-线程池(springboot + mybatisplus+redis+mysql+jwt+缓存)

目录

1. 文章详情

1.1 接口说明

1.2 涉及到的表及pojo

1.3 Controller

1.4 Service

1.5 测试

2. 使用线程池 更新阅读次数

2.1 为什么要使用线程池来更新阅读次数:

2.2 解决办法:

2.3 线程池配置

2.4 使用

2.5 测试


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();
        }
    }

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PJP__00

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值