个人博客项目笔记_09

1. 归档文章列表

1.1 接口说明

接口url:/articles

请求方式:POST

请求参数:

参数名称 参数类型 说明
year string
month string

返回数据:

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

1.2 文章列表参数

新增参数:

​ private String year;

​ private String month;

月份特殊处理:6月——>06月

public String getMonth(){
if (this.month != null && this.month.length() == 1){
return "0"+this.month;
}
return this.month;
}

package com.cherriesovo.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;
    }
}

1.3 使用自定义sql 实现文章列表

ArticleMapper:

listArticle():根据给定的条件查询文章,并且支持分页功能:

  • page: 这是一个分页对象,用于指定查询的页码和每页显示的数据量。
  • categoryId: 要查询的文章所属的分类 ID。
  • tagId: 要查询的文章关联的标签 ID。
  • year: 要查询的文章的发布年份。
  • month: 要查询的文章的发布月份。

这个方法会返回一个分页后的文章列表。

IPage<Article> listArticle(Page<Article> page,
                               Long categoryId,
                               Long tagId,
                               String year,
                               String month);

ArticleServiceImpl:重写方法listArticlesPage()

IPage<Article> 是 MyBatis-Plus 框架中用于分页查询结果的接口。它表示了一个分页后的文章列表,包括了查询结果的分页信息和实际的文章数据列表。

在这个接口中,可以通过以下方法获取分页信息和文章列表:

  • getRecords(): 获取当前页的文章列表。
  • getTotal(): 获取符合查询条件的总文章数。
  • getCurrent(): 获取当前页码。
  • getPages(): 获取总页数。
  • getSize(): 获取当前页的文章数量。
  • hasNext(): 是否有下一页。
  • hasPrevious(): 是否有上一页。
  @Override
    public List<ArticleVo> listArticlesPage(PageParams pageParams) {
        //创建了一个用于分页查询的 Page 对象
        Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());
        IPage<Article> articleIPage = this.articleMapper.listArticle(//调用 listArticle 方法来查询文章
                page,
                pageParams.getCategoryId(),
                pageParams.getTagId(),
                pageParams.getYear(),
                pageParams.getMonth());
        List<Article> records = articleIPage.getRecords();
        List<ArticleVo> articleVoList = copyList(records,true,false,true);
        return articleVoList;
    }

ArticleMapper.xml:

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

    <select id="listArticle"  resultMap="articleMap">
        select * from ms_article
        <where>
            1 = 1
            <if test="categoryId != null">
                and  category_id = #{categoryId}
            </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>
            <if test="tagId != null">
                and id in (select article_id from ms_article_tag where tag_id=#{tagId})
            </if>
        </where>
        order by weight desc,create_date desc
    </select>

1.4 测试

2. 统一缓存处理(优化)

内存的访问速度 远远大于 磁盘的访问速度 (1000倍起)

使用AOP开始优化

切点:

package com.cherriesovo.blog.common.cache;

import java.lang.annotation.*;

@Target({ElementType.METHOD})	//表明这个 Cache 注解只能用于方法上
@Retention(RetentionPolicy.RUNTIME)	//表示 Cache 注解的生命周期为运行时
@Documented	//Cache 注解应该被 javadoc 工具记录
public @interface Cache {

    long expire() default 1 * 60 * 1000;    //缓存的过期时间,单位为毫秒,默认值为 1 分钟

    String name() default "";       //缓存标识,用于区分不同的缓存数据。默认为空字符串

}

Method method = pjp.getSignature().getDeclaringType().getMethod(methodName, parameterTypes);

通过反射获取切点方法对应的 Method 对象。

  • pjp.getSignature().getDeclaringType(): 首先通过 pjp.getSignature() 获取切点方法的签名信息,然后调用 getDeclaringType() 方法获取声明该方法的类的 Class 对象。
  • getMethod(methodName, parameterTypes): 在获取到声明该方法的类的 Class 对象后,调用 getMethod() 方法获取指定方法名和参数类型的 Method 对象。这个方法需要传入两个参数,第一个是方法名 methodName,第二个是参数类型数组 parameterTypes

这行代码的作用是获取切点方法对应的 Method 对象,可以通过该对象进行一些反射操作,比如调用方法、获取方法的修饰符等。

package com.cherriesovo.blog.common.cache;

import com.alibaba.fastjson.JSON;
import com.cherriesovo.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.core.annotation.AliasFor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.Duration;
//AOP定义一个切面,切面定义了切点和通知的关系
@Aspect
@Component
@Slf4j
public class CacheAspect {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    //切点,匹配带有 @Cache 注解的方法
    @Pointcut("@annotation(com.cherriesovo.blog.common.cache.Cache)")
    public void pt(){}	//切点的实际定义,名称为 pt。它没有参数和实现,因为它只是用来定义切点,而不执行任何实际的逻辑。

    //通知,通知关联了切点
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp){
        try {
            Signature signature = pjp.getSignature();	//获取了切点方法的签名信息
            //获取了切点方法所属类的简单类名
            String className = pjp.getTarget().getClass().getSimpleName();
            //获取了切点方法的方法名
            String methodName = signature.getName();

			//定义了一个数组用于存储切点方法的参数类型。pjp.getArgs() 返回的是切点方法的参数列表,通过遍历参数列表,可以逐个获取参数的类型,并存储到 parameterTypes 数组中。
            Class[] parameterTypes = new Class[pjp.getArgs().length];
            Object[] args = pjp.getArgs();	//获取了切点方法的参数值列表
            
            //将切点方法的参数值转换为字符串,并且将参数的类型存储到 parameterTypes 数组中。
            String params = "";	//参数
            for(int i=0; i<args.length; i++) {
                if(args[i] != null) {
                    params += JSON.toJSONString(args[i]);	//将参数值追加到 params 字符串后面
                    parameterTypes[i] = args[i].getClass();
                }else {
                    parameterTypes[i] = null;
                }
            }
            if (StringUtils.isNotEmpty(params)) {
                //加密 以防出现key过长以及字符转义获取不到的情况
                params = DigestUtils.md5Hex(params);
            }
            Method method = pjp.getSignature().getDeclaringType().getMethod(methodName, parameterTypes);
            //获取方法上的Cache注解
            Cache annotation = method.getAnnotation(Cache.class);
            //从注解中获取缓存过期时间
            long expire = annotation.expire();
            //从注解中获取缓存名称
            String name = annotation.name();
            //先从redis获取数据
            String redisKey = name + "::" + className+"::"+methodName+"::"+params;	//唯一标识缓存中的数据
            //通过 Redis 模板的 opsForValue() 方法获取了一个操作字符串的操作对象,并调用了 get(redisKey) 方法,尝试从 Redis 中			根据 redisKey 获取对应的缓存数据。
            String redisValue = redisTemplate.opsForValue().get(redisKey);
            if (StringUtils.isNotEmpty(redisValue)){
                log.info("走了缓存~~~,{},{}",className,methodName);
                //将获取到的 JSON 格式的字符串 redisValue 解析为 Result 类型的对象返回
                return JSON.parseObject(redisValue, Result.class);
            }
            
            //在缓存未命中时,执行方法的实际逻辑并将结果存入 Redis 缓存中
            Object proceed = pjp.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,"系统错误");
    }

}

使用(对最热文章进行缓存处理):

在想要添加缓存的接口上添加:@Cache(expire = 5 * 60 * 1000,name = "hot_article")

   @PostMapping("hot")
    @Cache(expire = 5 * 60 * 1000,name = "hot_article")	//指定了缓存的过期时间 5 分钟
    public Result hotArticle(){
        int limit = 5;
        return articleService.hotArticle(limit);
    }
posted @ 2024-04-12 17:46  CherriesOvO  阅读(15)  评论(0编辑  收藏  举报