面向切面编程实现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
电商平台的后端接口。
面向切面编程实现Nestjs接口Api数据缓存相关推荐
- AOP面向切面编程之全局日志打印/统计接口耗时
目录 一.什么是AOP 二.AOP使用场景 三.使用AOP的好处 四.先举个例子理解AOP面向切面编程 五.Spring5.X的AOP切入点表达式有这些种写法 六.实战基于Spring的AOP快速实现 ...
- 大数据WEB阶段Spring框架 AOP面向切面编程(一)
Spring - AOP面向切面编程(一) 一.代理模式概述 代理的特点:(目标对象即被代理者) 实现和目标对象相同的接口 具备和目标对象的方法 代理者不仅要做目标对象的方法 , 还要做一些额外的操作 ...
- 大数据WEB阶段Spring框架 AOP面向切面编程(二)
Spring AOP面向切面编程(二) 一.切入点的execution表达式 execution的表达形式: execution(修饰符? 返回值类型 所在包类? 方法名(参数列表) 异常?) ?表示 ...
- Spring→面向切面编程AOP、相关概念、通知Advice类型、配置切面切入点通知、AOP相关API、AOP代理类ProxyFactoryBean、AOP注解@AspectJ
面向切面编程AOP CGLib AOP相关概念 Advice类型 Spring实现AOP Spring配置切面aspect 配置切入点pointcut 配置通知advice 配置通知参数 调用新的父类 ...
- Spring面向切面编程
JAVA就业套餐课:https://edu.csdn.net/combo/detail/1230 第1章主要介绍了Spring管理实体对象的应用,通过ApplicationContext ...
- 面向切面编程AspectJ在Android埋点的实践
在项目开发中,对 App 客户端重构后,发现用于统计用户行为的友盟统计代码和用户行为日志记录代码分散在各业务模块中,比如在某个模块,要想实现对用户的行为一和行为二进行统计,因此按照OOP面向对象编程思 ...
- AOP (面向切面编程)
AOP (面向切面编程) 编辑 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AO ...
- spring中AOP(面向切面编程)
spring中AOP(面向切面编程) 面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是spring框架中的一个重要内容 ...
- 什么是AOP?AOP面向切面编程
面向切面编程 AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引 ...
最新文章
- 库(静态库和动态库)
- wireshark 抓包再利用TCP socket发送包里的payload是可以实现登陆的
- doker zookeeper kafka单机搭建
- lnmp基于fastcgi实现nginx_php_mysql的分离_LNMP基于FastCGI实现Nginx,PHP,MySQL的架构分离...
- windows 仍在设置此设备的类配置。 (代码 56)_谷歌发布Flutter Alpha:支持Windows
- Spring MVC注释
- 熊猫数据集_对熊猫数据框使用逻辑比较
- Labview-隧道 移位寄存器
- Vue模板语法---vue工作笔记0003
- 2引擎帮助文档_ANSA快速入门指南中文帮助文档浅析(上)
- 江苏技术师范学院大学机房管理系统[.NET项目]
- 发布阿里云OSS for phpcmsV9整合教程
- 三角诱导公式 两角和与差 二倍角公式 降幂公式 半角公式 万能公式 积化和差公式 和差化积公式
- movsw 汇编_汇编指令之ADC、SBB、XCHG、MOVS指令
- vue resource的应用
- [Mysql] MOD函数
- 开源项目推荐 | 面向智慧城市的计算机视觉算法基准测试 Benchmark for Smart City上线
- 正交试验设计例题及答案_【正交试验设计法是】试验设计与分析试题及答案
- 按键精灵制作自用游戏脚本所需的简单操作
- CSS进阶(3)- 布局