面向切面编程实现Nestjs接口Api数据缓存

一、业务场景

在后端接口开发过程中,我们经常会谈论的话题,提高接口响应速度,前端接口调用后端接口响应时间的缩短,我们抛开数据库设计及后端代码的业务逻辑等问题。我们经常会听到说用redis做数据缓存,直接从内存中获取数据返回给客户端,减少后端程序对数据库的操作,提高接口的性能。使用方式

  • 1、侵入式的方式,在需要缓存的接口代码里面不停的复制黏贴,将数据缓存到redis中,哪天如果不需要缓存了,又要去代码里面查找,删除
  • 2、采用面向切面编程的思想,使用装饰器(注解)的方式,在需要缓存的接口上加上注解的方式,redis就会对该接口的数据进行缓存。

本文章使用的后端技术栈是nestjs结合该技术栈的拦截器自定义装饰器反射来实现无侵入式的方式来对后端接口缓存,前提是要你先安装redis数据库和会使用nestjs开发接口。

二、自定义装饰器

  • 1、关于自定义装饰器参考官网

  • 2、我们创建一个自定义装饰器加到路由上,然后在拦截器上通过反射原理来获取该装饰器中的内容

    // redis.cache.api.ts文件内容
    
    import { applyDecorators, SetMetadata } from '@nestjs/common';
    import { REDIS_CACHE_KEY, REDIS_CACHE_EX_SECOND_KEY } from '@src/constants';
    import redisCacheConfig from '@src/config/redisCache.config';
    
    // 是否缓存
    const isCache = true;
    
    /**
     * @Author: 水痕
     * @Date: 2021-03-09 17:05:19
     * @LastEditors: 水痕
     * @Description: 自定义装饰器,用于路由上装饰需要缓存的接口
     * @param {number} exSecond redis缓存过期时间,时间为妙
     * @return {*}
     */
    export function RedisCacheApi(exSecond: number = redisCacheConfig.redisEXSecond): any {
      return applyDecorators(
        SetMetadata(REDIS_CACHE_KEY, isCache),
        SetMetadata(REDIS_CACHE_EX_SECOND_KEY, exSecond)
      );
    }
    

    在自定义装饰器中传递一个默认redis缓存时间,也同样接收自定义传递进来的时间,const isCache = true;是告诉拦截器,这个接口是要走redis缓存的。以下两个常量是随便定义的,仅仅可以区分不重名就可以。

    /** 自定义redis缓存key,用于反射 */
    export const REDIS_CACHE_KEY = '@@redis_cache_key';
    /** 自定义redis缓存过期key,用于反射 */
    export const REDIS_CACHE_EX_SECOND_KEY = '@@redis_cache_ex_second_key';
    

三、自定义拦截器

  • 1、为什么我们要选择用拦截器来做数据缓存的拦截呢?

    nestjs中所谓的中间有:中间件、拦截器、守卫、过滤器等,我们从字面意义上来理解,既然是要拦截接口请求,根据redis中是否有数据来响应数据,如果没有也要到控制层,那么我们就需要一个双向通道的中间件,刚好拦截器适用我们使用。

  • 2、关于拦截器官方地址

    在这里插入图片描述

  • 3、使用nest-cli自动生成拦截器代码

    nest g interceptor interceptors/redisCache --no-spec 
    # or
    nest g in interceptors/redisCache --no-spec 
    
  • 4、拦截器基本框架

    @Injectable()
    export class RedisCacheInterceptor implements NestInterceptor {
      // 依赖注入自定义的redis缓存服务
      constructor (
        private readonly redisCacheService: RedisCacheService,
      ) { }
    
      async intercept(context: ExecutionContext, next: CallHandler): Promise<any> {
        // TODO
        // 核心代码区域
      }
    }
    
  • 5、RedisCacheService仅仅是对redis连接和使用redis字符串数据类型,来存储数据,参考代码如下

    import { Injectable } from '@nestjs/common';
    import { RedisService } from 'nestjs-redis';
    import { Redis } from 'ioredis';
    
    @Injectable()
    export class RedisCacheService {
      public client: Redis;
      constructor (
        private redisService: RedisService
      ) { }
    
      onModuleInit() {
        this.getClient();
      }
    
      public getClient() {
        this.client = this.redisService.getClient();
      }
    
      /**
       * @Author: 水痕
       * @Date: 2020-01-17 14:53:37
       * @LastEditors: 水痕
       * @Description: 封装设置redis缓存的方法
       * @param key {String} key值 
       * @param value {String} key的值 
       * @param second {Number} 过期时间秒
       * @return: Promise<any>
       */
      public async set(key: string, value: any, second?: number): Promise<any> {
        value = JSON.stringify(value);
        if (!second) {
          await this.client.set(key, value);
        } else {
          await this.client.set(key, value, 'EX', second);
        }
      }
    
      /**
       * @Author: 水痕
       * @Date: 2020-01-17 14:55:14
       * @LastEditors: 水痕
       * @Description: 设置获取redis缓存中的值
       * @param key {String} 
       */
      public async get(key: string): Promise<any> {
        const data = await this.client.get(key);
        if (data) {
          return JSON.parse(data);
        } else {
          return null;
        }
      }
    }
    
    
  • 6、在拦截器的intercept方法中主要的思路是

    • 通过反射的方式获取当前请求并且判断该接口是否加上了缓存标识(通过自定义装饰器实现的)
    • 判断是否走缓存,如果不走缓存直接往下走,如果要走缓存,继续判断redis中是否已经存在数据
    • 判断了要走缓存的接口,在里面根据当前请求去获取redis中是否存在缓存数据,有的话就直接返回给前端如果有缓存数据,那么直接返回节省响应时间,没有就继续走到控制器,从数据库中查询数据返回给前端
    // 从上下文中获取request
    const request: Request = context.switchToHttp().getRequest();
    // 反射的方法获取接口是否需要缓存
    const isCacheApi = Reflect.getMetadata(REDIS_CACHE_KEY, context.getHandler()) || Reflect.getMetadata(REDIS_CACHE_KEY, context.getClass());
    // 获取缓存时间
    const redisEXSecond = Reflect.getMetadata(REDIS_CACHE_EX_SECOND_KEY, context.getHandler()) || Reflect.getMetadata(REDIS_CACHE_EX_SECOND_KEY, context.getClass());
    
    // 判断是否要走缓存
    if (isCacheApi) {
      console.log('走缓存');
      const redisKey = this.redisCacheKey(request.method, request.url);
      const redisData = await this.redisCacheService.get(redisKey);
      if (redisData) {
        console.log('redis直接返回');
        return of(redisData);
      } else {
        console.log('后端');
        return next.handle().pipe(map(data => {
          // 将后端的返回的数据存储到redis数据库中
          this.redisCacheService.set(redisKey, data, redisEXSecond);
          return data;
        }));
      }
    } else {
      console.log('不走缓存');
      return next.handle();
    }
    
    // 定义一个方法根据当前请求方式和请求url地址作为redis的key
    private redisCacheKey(method: string, url: string): string {
       return `${method}:${url}`;
    }
    

四、使用

  • 1、在main.ts中全局使用拦截器,注意这里的拦截器的构造函数中有参数,我们需要反射来获取该服务

    // 处理拦截器中调用服务层的策略
    const reflectorEn = app.get<RedisCacheService>(RedisCacheService);
    // 全局注册redis缓存接口
    app.useGlobalInterceptors(new RedisCacheInterceptor(reflectorEn));
    
  • 2、在我们需要拦截的接口上加上上面自定义的装饰器

    @Get(':id')
    // 直接在需要缓存的接口上加上就可以
    @RedisCacheApi()
    @HttpCode(HttpStatus.OK)
    async findById(
      @Param('id', new ParseIntPipe()) id: number,
    ): Promise<any> {
      return await this.activityService.findById(id);
    }
    
  • 3、对于一些不变的数据(比如全部省市县、邮编)等数据表,可以在@RedisCacheApi()直接加一个redis过期时间,时间单位为表

五、使用npm包直接在项目中使用

对于这么好用的功能,本人自然会封装成npm包供大家一起简单使用,npm包地址,具体使用参考README.md文件,有不足的地方,可以直接联系我

附上去年开源的nestjs权限系统项目github地址,本人正在重写该项目,将来也会开源一个关于nestjs电商平台的后端接口。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

水痕01

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

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

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

打赏作者

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

抵扣说明:

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

余额充值