9.归档文章列表、统一缓存处理(优化)

归档文章列表

1.1 接口说明

接口url:/articles

请求方式:POST

请求参数:

参数名称参数类型说明
yearstring
monthstring

返回数据:

{
    "success": true, 
    "code": 200, 
    "msg": "success", 
    "data": [文章列表,数据同之前的文章列表接口]
}


由于需要统计年月份,mybatisPlus的queryWrapper不好统计,只能用sql语句
所以文章详情接口需要全部重新用xml的方式实现

前端参数也需要多加两个成员,year和month

package com.example.blog.vo.params;


import lombok.Data;

@Data
public class PageParams
{
    //当前的页数
    private int page = 1;

    //每页查询的数量
    private int pageSize = 10;

    private Long categoryId;

    private Long tagId;

    private String year;

    private String month;

    public String getMonth()
    {
        if(this.month != null && this.month.length() ==1)
        {
            return  "0"+this.month;
        }
        return this.month;
    }
}
ArticleController:
    /**
     * 首页 文章列表
     * @param pageParams
     * @return
     */
    @PostMapping
    public Result listArticle(@RequestBody PageParams pageParams)
    {
        return articleService.listArticle(pageParams);
    }

 ArticleServiceImpl:

 /**
     * 分页查询 article数据库表
     * @param pageParams
     * @return
     */
    //加上此注解 代表要对此接口记录日志
    @LogAnnotation(module="文章",operator="获取文章列表")
    @Override
    public Result listArticle(PageParams pageParams)
    {
        Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());
        IPage<Article> articleIPage = articleMapper.listArticle(
                                                    page,
                                                    pageParams.getCategoryId(),
                                                    pageParams.getTagId(),
                                                    pageParams.getYear(),
                                                    pageParams.getMonth());
        List<Article> records = articleIPage.getRecords();
        return Result.success(copyList(records,true,true));
    }
ArticleMapper:
package com.example.blog.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.blog.dos.Archives;
import com.example.blog.entity.Article;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ArticleMapper extends BaseMapper<Article>
{
    public List<Archives> listArchives();

    IPage<Article> listArticle(Page<Article> page,
                               Long categoryId,
                               Long tagId,
                               String year,
                               String month);
}
ArticleMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis配置文件-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.blog.dao.mapper.ArticleMapper">

    <resultMap id="articleMap" type="com.example.blog.entity.Article">
        <id column="id" property="id"></id>
        <result column="comment_counts" property="commentCounts"></result>
        <result column="create_date" property="createDate"></result>
        <result column="summary" property="summary"></result>
        <result column="title" property="title"></result>
        <result column="view_counts" property="viewCounts"></result>
        <result column="weight" property="weight"></result>
        <result column="author_id" property="authorId"></result>
        <result column="body_id" property="bodyId"></result>
        <result column="category_id" property="categoryId"></result>
    </resultMap>

    <select id="listArchives" resultType="com.example.blog.dos.Archives">
       select FROM_UNIXTIME(create_date/1000,'%Y') as year, FROM_UNIXTIME(create_date/1000,'%m') as month,count(*) as count from ms_article group by year,month
    </select>
    <!--参数:
        page,
        pageParams.getCategoryId(),
        pageParams.getTagId(),
        pageParams.getYear(),
        pageParams.getMonth()-->
    <!--这里FROM_UNIXTIME(create_date/1000,'%Y')查出来是0X月份,所以month参数那里需要加 public String getMonth()-->
    <select id="listArticle" resultMap="articleMap">
        select * from ms_article
        <where>
            <if test="categoryId != null">
                and category_id=#{category_id}
            </if>
            <if test="tagId != null">
                and id in (select article_id from ms_article_tag where tag_id =#{tagId})
            </if>
            <if test="year != null and year.length>0 and month!=null and month.length>0">
                and (FROM_UNIXTIME(create_date/1000,'%Y')=#{year} and FROM_UNIXTIME(create_date/1000,'%m')=#{month})
            </if>
        </where>
        order by weight,create_date DESC
    </select>


</mapper>

统一缓存处理(优化)

1.首先写一个缓存注解,这个注解加在哪个地方,哪里就进行一个缓存。切点为添加了该注解的方法。

package com.example.blog.common.aop;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cache
{
    //设置过期时间
    long expire() default 1 * 60 * 1000;
    //缓存表示 key
    String name() default "";
}

2.再设置环绕通知,看redis有没有存入该返回值

有的话直接返回,就不用调用切点方法了,没有的话再调用切点方法,将返回值存入redis中

CacheAspect:
package com.example.blog.common.aop;

import com.alibaba.fastjson.JSON;
import com.example.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.Duration;

@Aspect
@Component
@Slf4j
//缓存的切面(切点为添加了该注解的方法)
public class CacheAspect
{
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Pointcut("@annotation(com.example.blog.common.aop.Cache)")
    public void pt(){}

    @Around("pt()")//环绕通知
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            Signature signature = joinPoint.getSignature();
            //获取切点的类名
            String className = joinPoint.getTarget().getClass().getSimpleName();
            //获取切点的方法名
            String methodName = signature.getName();

            //获取切点的参数
            Class[] parameterTypes = new Class[joinPoint.getArgs().length];//存储参数类型的数组
            Object[] args = joinPoint.getArgs();//存储参数的数组
            String params = "";
            for(int i=0;i<args.length;i++)
            {
                if(args[i] != null)
                {
                    params += JSON.toJSONString(args[i]);
                    parameterTypes[i] = args[i].getClass();
                }else
                {
                    parameterTypes[i] = null;
                }
            }
            if(StringUtils.isNotEmpty(params))
            {
                //MD5加密 以防出现key过长以及字符串转义获取不到的情况
                params = DigestUtils.md5Hex(params);
            }
            //通过parameterTypes获得切点
            Method method = joinPoint.getSignature().getDeclaringType().getMethod(methodName,parameterTypes);
            //获得cache注解
            Cache annotation = method.getAnnotation(Cache.class);
            //拿到切点设置的缓存过期时间
            long expire = annotation.expire();
            //拿到切点设置的缓存名称
            String name = annotation.name();
            //先从redis获得,看是否该缓存存入了redis
            String redisKey = name + "::" + className + "::"+  methodName + "::" +params;
            String redisValue = redisTemplate.opsForValue().get(redisKey);
            if(StringUtils.isNotEmpty(redisValue))
            {
                log.info("走了缓存~~~,{},{}",className,methodName);
                return JSON.parseObject(redisValue, Result.class);
            }
            //如果为空,代表需要调用切点方法,再把返回值存入redis里面
            Object proceed = joinPoint.proceed();
            redisTemplate.opsForValue().set(redisKey,JSON.toJSONString(proceed), Duration.ofMillis(expire));
            log.info("存入缓存~~~ {},{}",className,methodName);
            return proceed;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return Result.fail(-999,"系统错误");
    }
}

3.使用该注解

package com.example.blog.controller;

import com.example.blog.common.aop.Cache;
import com.example.blog.service.ArticleService;
import com.example.blog.vo.Result;
import com.example.blog.vo.params.ArticleParam;
import com.example.blog.vo.params.PageParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RequestMapping("/articles")
@RestController
public class ArticleController
{

    @Autowired
    private ArticleService articleService;
    /*如果参数时放在请求体中,application/json传入后台的话,那么后台要用@RequestBody才能接收到;
     如果不是放在请求体中的话,那么后台接收前台传过来的参数时,要用@RequestParam来接收。
     或者形参前 什么也不写也能接收。*/

    @PostMapping("/publish")
    public Result publish(@RequestBody ArticleParam articleParam)
    {
        return articleService.publish(articleParam);
    }

    /**
     * 首页 文章列表
     * @param pageParams
     * @return
     */
    @PostMapping
    @Cache(expire = 5 *60 * 1000,name = "list_article")
    public Result listArticle(@RequestBody PageParams pageParams)
    {
        return articleService.listArticle(pageParams);
    }

    /**
     * 首页最热文章
     * @return
     */
    @Cache(expire = 5 *60 * 1000,name = "hot_article")
    @PostMapping("hot")
    public Result hotArticles()
    {
        int limit = 6;
        return articleService.hotArticle(limit);
    }

    /**
     * 首页最新文章
     */
    @PostMapping("new")
    @Cache(expire = 5 *60 * 1000,name = "new_article")
    public Result newArticles()
    {
        int limit = 5;
        return articleService.newArticle(limit);
    }

    /**
     * 文章归档
     */
    @PostMapping("listArchives")
    @Cache(expire = 5 *60 * 1000,name = "list_archives")
    public Result listArchives()
    {
        return articleService.listArchives();
    }

    /**
     * 文章详情
     */
    @PostMapping("/view/{id}")
    public Result findArticleById(@PathVariable("id") Long articleId)
    {
        return articleService.findArticleById(articleId);
    }
}

不过这里有一个bug,加了缓存之后,点击文章详情出现文章加载失败 

原因:

if(StringUtils.isNotEmpty(redisValue))
            {
                log.info("走了缓存~~~,{},{}",className,methodName);
                return JSON.parseObject(redisValue, Result.class);
            }

如果该切点方法以及把返回值存入缓存,那么JSON在解析类型为Long的id时,又会出现精度不够的情况

解决:把以下的id都改为String类型

 ArticleServiceImpl:

copy方法,加入

articleVo.setId(String.valueOf(article.getId()));

对应impl的所有copy方法都需要修改

前端也有一个bug,当评论成功之后,后端返回给前端的数据应该是null,所以前端that.comments.unshift(data.data)是把Null加入进去,应该是直接重新获取评论列表

this.getCommentsByArticle()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值