当前位置: 首页 > news >正文

nestjs 缓存配置及防抖拦截器

1、编写全局拦截器,

2、配置缓存服务,以便于依赖注入

3、编写添加元数据方法,后面防抖拦截器是否需要拦截做准备

4、编写全局拦截器,依赖注入缓存service,在拦截器中每次进入的时候从缓存中读取,如果从在,则抛异常,否则存储在缓存中

5、将拦截器全局引入

1、下载安装

pnpm i keyv @keyv/redis cache-manager cacheable

2、配置缓存服务,以便于依赖注入

providers: [{provide: 'CACHE_MANAGER',inject: [ConfigService],useFactory: (configService: ConfigService) => {return createCache({nonBlocking: true,stores: [// 内存中存储new Keyv({store: new CacheableMemory({ ttl: 60000, lruSize: 5000 }),namespace:'',}),// redis中存储new Keyv({store: new KeyvRedis(configService.get('redis.url')),namespace: ''})]})}}
]
exports: ['CACHE_MANAGER'
],

完整全局配置

import { Global, Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { HttpModule } from '@nestjs/axios';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import configuration from '../../config/index';
import { JwtModule } from '@nestjs/jwt';
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { JwtGuard } from 'src/utils/jwt/jwt-guard';
import { JwtStrategy } from 'src/utils/jwt/jwt-strategy';
import { WinstonService } from 'src/utils/logger/winston-service';
import { CatchLoggerFilter } from 'src/utils/logger/catch-logger-filter';
import { ResponseLoggerInterceptor } from 'src/utils/logger/response-logger-interceptor';
import { RedisModule } from '@nestjs-modules/ioredis';
import { RequirePermissionGuard } from 'src/utils/premission/require-premission.guard';
import { DebounceInterceptor } from 'src/utils/debounce/debounce.interceptor';
import { Keyv } from 'keyv';
import KeyvRedis from '@keyv/redis'
import { createCache } from 'cache-manager';
import { CacheableMemory } from 'cacheable'@Global()
@Module({imports: [ConfigModule.forRoot({isGlobal: true,load: [configuration],}),TypeOrmModule.forRootAsync({name: "default",inject: [ConfigService],useFactory: (configService: ConfigService) => {return {type: 'mysql',...configService.get('db.mysql'),timezone: '+08:00',// logger: 'advanced-console',entities: [__dirname + '/../**/*.entity.{js,ts}'],} as TypeOrmModuleOptions;},}),// TypeOrmModule.forRootAsync({//     name: "oracle",//     inject: [ConfigService],//     useFactory: async (configService: ConfigService) => {//         return {//             type: 'oracle',//             ...configService.get('db.oracle'),//             // logger: 'advanced-console',//             timezone: '+08:00',//             entities: [__dirname + '/../**/*.entity.{js,ts}'],//         } as TypeOrmModuleOptions;//     },// }),HttpModule.registerAsync({inject: [ConfigService],useFactory: async (configService: ConfigService) => {return {timeout: configService.get('http.timeout'),maxRedirects: configService.get('http.maxRedirects'),};},}),RedisModule.forRootAsync({inject: [ConfigService],useFactory: (configService: ConfigService) => {return {type: "single",url: configService.get('redis.url'),};},}),JwtModule.registerAsync({inject: [ConfigService],global: true,useFactory: (configService: ConfigService) => {return {secret: configService.get('jwt.secretkey'),// signOptions: { expiresIn: configService.get('jwt.expiresin') },};},}),],providers: [JwtStrategy,{provide: APP_GUARD,useFactory: (configService: ConfigService) => {return new JwtGuard(configService);},inject: [ConfigService],},{provide: APP_GUARD,useClass: RequirePermissionGuard},{provide: WinstonService,inject: [ConfigService],useFactory: (configService: ConfigService) => {return new WinstonService(configService);}},{provide: APP_FILTER,useClass: CatchLoggerFilter},{provide: APP_INTERCEPTOR,useClass: ResponseLoggerInterceptor},{provide: APP_INTERCEPTOR,useClass: DebounceInterceptor},{provide: 'CACHE_MANAGER',inject: [ConfigService],useFactory: (configService: ConfigService) => {return createCache({nonBlocking: true,stores: [// 内存中存储new Keyv({store: new CacheableMemory({ ttl: 60000, lruSize: 5000 }),namespace:'',}),// redis中存储new Keyv({store: new KeyvRedis(configService.get('redis.url')),namespace: ''})]})}},],exports: [WinstonService,HttpModule,'CACHE_MANAGER'],
})
export class ShareModule { }

3、编写添加元数据方法,为后面防抖拦截器是否需要拦截做准备

import { SetMetadata } from '@nestjs/common';
export const Debounce = (keyPattern: string, ttl: number = 5) => SetMetadata('debounce', { keyPattern, ttl });

4、编写防抖拦截器

import { CallHandler, ExecutionContext, HttpException, Inject, Injectable, NestInterceptor } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { Observable } from "rxjs";
import type { Cache } from 'cache-manager'
import { CacheEnum } from "../base-enum";
@Injectable()
export class DebounceInterceptor implements NestInterceptor {constructor(private reflector: Reflector,@Inject('CACHE_MANAGER') private cache: Cache,) {}async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<any>> {// 判断是否有元数据const debounce = this.reflector.getAllAndOverride('debounce', [context.getClass(),context.getHandler()]);// 如果没有 说明不需要控制if (!debounce) {return next.handle();}const { keyPattern, ttl } = debounce;const request = context.switchToHttp().getRequest();const cacheKey = CacheEnum.DEBOUNCE_KEY + this.resolveKey(keyPattern, request);const isBlocked = await this.cache.get(cacheKey);if (isBlocked) {throw new HttpException('操作过于频繁,请稍后再试', 429);}const data = await this.cache.set(cacheKey, true, ttl);return next.handle();}private resolveKey(pattern: string, request: any): string {return pattern.replace(/\{(\w+)\}/g, (match, paramName) => {// 优先从 params、body、query、user 中查找const sources = [request.params, request.user, request.body, request.query];for (const src of sources) {if (src && src[paramName] !== undefined) {return src[paramName];}}// 支持 user.id 等if (paramName.includes('.')) {const parts = paramName.split('.');let val = request;for (const part of parts) {val = val?.[part];}return val || 'unknown';}return 'unknown';});}
}

5、全局引入

providers: [{provide: APP_INTERCEPTOR,useClass: DebounceInterceptor},
]

6、使用

 需要做防抖的控制器上添加元数据. @Debounce(标识,过期时间-毫秒)

@Put("/update")
@Debounce('userUpdate:{userId}', 5000)
update(@Body() body: UpdateUserDto) {return this.userService.updateUser(body.userId, body)
}

---------------------------------------------------------------------------------------------------------------------------------

既然自己定义了缓存服务,那么全局注册也写一个好了,但是不建议全局化哈

基于上面全局注册的"CACHE_MANAGER_INSTANCE" service,再写一个拦截器,拦截器中只处理get请求,同样的原理,如果是get请求的话 ,从元数据中取值,先去缓存中查找,存在直接返回,不存在走自己的方法,完事儿后再像缓存中保存一份

import {CallHandler,ExecutionContext,Inject,Injectable,NestInterceptor,
} from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { CACHE_KEY_METADATA, CACHE_TTL_METADATA, CacheKey, CacheTTL } from '@nestjs/cache-manager';
import type { Cache } from 'cache-manager';@Injectable()
export class HttpCacheInterceptor implements NestInterceptor {constructor(@Inject("CACHE_MANAGER_INSTANCE") private readonly cacheManager: Cache,) { }async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {const request = context.switchToHttp().getRequest();if (request.method !== 'GET') return next.handle();const key = this.getCacheKey(context);const ttl = this.getTTL(context);const cached = await this.cacheManager.get(key);if (cached) return of(cached);return next.handle().pipe(tap((response) => {this.cacheManager.set(key, response, ttl);}),);}private getCacheKey(context: ExecutionContext): string {const key = Reflect.getMetadata(CACHE_KEY_METADATA, context.getHandler());const request = context.switchToHttp().getRequest();return key || `${request.method}_${request.url}`;}private getTTL(context: ExecutionContext): number {const ttl = Reflect.getMetadata(CACHE_TTL_METADATA, context.getHandler());return ttl || 60;}
}

然后全局化就可以啦

{provide: APP_INTERCEPTOR,useClass: HttpCacheInterceptor
}

使用

/**
* 
* 因为全局拦截器中取的是 import { CACHE_KEY_METADATA, CACHE_TTL_METADATA } from '@nestjs/cache-manager';
* 所以这里直接使用原有的就好了 CacheKey 和 CacheTTL
*/
@Get("/list")
@CacheTTL(10000)    //不指定的话取全局默认是时间
@CacheKey('list')   //不指定的话取路由类型+路径地址
@RequirePermission(['system:user:query'])
findList(@Query() query: ListUserDto) {return this.userService.paginateUser(query)
}

单个方法使用手动写入一下就好了

// 🔍 先查缓存
const cached = await this.cacheManager.get(‘自己定义个key’);
if (cached) {console.log(`🎯 缓存命中: ${cacheKey}`);return cached;
}// 🚀 查询数据库(模拟)
const result = await this.queryFromDatabase(query);// 💾 写入缓存,毫秒为单位
await this.cacheManager.set(cacheKey, result, 10000);return result;


文章转载自:

http://IVUSqAgi.mdnnz.cn
http://igdxD6tL.mdnnz.cn
http://Kp72Pu3l.mdnnz.cn
http://3SfWWMWL.mdnnz.cn
http://BJsE553y.mdnnz.cn
http://3veck7a8.mdnnz.cn
http://VcUtsyJ7.mdnnz.cn
http://0E9XoUKD.mdnnz.cn
http://atjQBjpF.mdnnz.cn
http://T9PNAETA.mdnnz.cn
http://LIDw2MOQ.mdnnz.cn
http://S50qiSlW.mdnnz.cn
http://PcsF3p7G.mdnnz.cn
http://bNqGJkD2.mdnnz.cn
http://1I1yju2m.mdnnz.cn
http://z7TVB6dp.mdnnz.cn
http://RHPLU4bq.mdnnz.cn
http://oJeQJVq4.mdnnz.cn
http://0oSWP134.mdnnz.cn
http://bfiJJmH5.mdnnz.cn
http://sxj9LCxx.mdnnz.cn
http://MdRqIKLN.mdnnz.cn
http://JWPAeAuY.mdnnz.cn
http://P55Caxgi.mdnnz.cn
http://K7X8cFGL.mdnnz.cn
http://xKoY1WWV.mdnnz.cn
http://RbZ4NlQK.mdnnz.cn
http://AfA7PElx.mdnnz.cn
http://mJFyMatL.mdnnz.cn
http://ZYHD6TjN.mdnnz.cn
http://www.dtcms.com/a/368815.html

相关文章:

  • 高等数学知识补充:三角函数
  • 论文Review Registration VGICP | ICRA2021 | 经典VGICP论文
  • 遇到 Git 提示大文件无法上传确实让人头疼
  • 基于单片机雏鸡家禽孵化系统/孵化环境监测设计
  • Docling将pdf转markdown以及与AI生态集成
  • GD32入门到实战35--485实现OTA
  • 别再看人形机器人了!真正干活的机器人还有这些!
  • C++编程——异步处理、事件驱动编程和策略模式
  • 【分享】AgileTC测试用例管理平台使用分享
  • cargs: 一个轻量级跨平台命令行参数解析库
  • 高级 ACL 有多强?一个规则搞定 “IP + 端口 + 协议” 三重过滤
  • 人大金仓:创建数据库分区
  • 【大数据专栏】大数据框架-Apache Druid Overview
  • Java中的多态有什么用?
  • 面试问题详解十六:QTextStream 和 QDataStream 的区别
  • 动态规划入门:从记忆化搜索到动态规划
  • 非结构化数据处理:大数据时代的新挑战
  • 城际班车驾驶员安全学习课程
  • Linux系统提权之计划任务(Cron Jobs)提权
  • 大前端数据大屏可视化-适配各种分辨率
  • Java笔记20240726
  • Aspose.Words for .NET 25.7:支持自建大语言模型(LLM),实现更安全灵活的AI文档处理功能
  • 怎样利用AE统计数据优化安防芯片ISP的图像质量?
  • 基于Python读取多个excel竖向拼接为一个excel
  • 深入解析汇编语言的奥秘
  • C++语言程序设计——06 字符串
  • 十二、软件系统分析与设计
  • flink 伪代码
  • AGENTS.md: AI编码代理的开放标准
  • 代码可读性的详细入门