From 1ebc5bf3f1b2a5ea202625c84d5d9cc48bb9c05a Mon Sep 17 00:00:00 2001 From: magiccaptain Date: Wed, 22 Jan 2025 10:12:49 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20[=E7=BC=93=E5=AD=98]=20=E7=BB=99=20?= =?UTF-8?q?auth=20=E6=B7=BB=E5=8A=A0=20redis=20=E7=BC=93=E5=AD=98=20#44?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.module.ts | 3 ++- src/common/cache.interceptor.ts | 8 +++++++ src/namespace/namespace.controller.ts | 10 +++++++++ src/user/user.controller.ts | 30 ++++++++++++++++++++++++++- 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 67f7bad..21306aa 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,6 +4,7 @@ import { Inject, MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { MongooseModule } from '@nestjs/mongoose/dist/mongoose.module'; +import { Cache } from 'cache-manager'; import { redisClusterInsStore, redisInsStore } from 'cache-manager-redis-yet'; import * as config from 'src/constants'; @@ -70,7 +71,7 @@ import { UserModule } from './user'; ], }) export class AppModule implements NestModule { - constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: any) {} + constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: Cache) {} configure(consumer: MiddlewareConsumer): void { consumer.apply(RouteLoggerMiddleware).exclude('/hello').forRoutes('*'); diff --git a/src/common/cache.interceptor.ts b/src/common/cache.interceptor.ts index 43d87f3..ed3a5ce 100644 --- a/src/common/cache.interceptor.ts +++ b/src/common/cache.interceptor.ts @@ -9,9 +9,12 @@ import { NestInterceptor, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; +import Debug from 'debug'; import { isArray } from 'lodash'; import { Observable, tap } from 'rxjs'; +const debug = Debug('commom:cache-interceptor'); + function compileKey(keyStr: string, data: any) { if (!keyStr) return keyStr; if (!data) return keyStr; @@ -56,9 +59,12 @@ export class UnsetCacheInterceptor implements NestInterceptor { tap((data) => { // 从返回结果构造 key cacheKey = compileKey(cacheKey, data?.toJSON ? data.toJSON() : data); + // 清除缓存 typeof cacheKey === 'string' && // cacheKey 可能用逗号分隔 多个 key cacheKey.split(',').forEach((key) => this.cacheService.del(key)); + + debug(`unset cache: ${cacheKey}`); }) ); } @@ -100,6 +106,8 @@ export class SetCacheInterceptor extends CacheInterceptor { cacheKey = compileKey(cacheKey, { payload, ...payload }); } + debug(`set cache: ${cacheKey}`); + return cacheKey || super.trackBy(context); } } diff --git a/src/namespace/namespace.controller.ts b/src/namespace/namespace.controller.ts index ca44de8..ae27837 100644 --- a/src/namespace/namespace.controller.ts +++ b/src/namespace/namespace.controller.ts @@ -1,5 +1,7 @@ +import { CacheKey } from '@nestjs/cache-manager'; import { Body, + CacheTTL, ConflictException, Controller, Delete, @@ -12,6 +14,7 @@ import { Post, Query, Res, + UseInterceptors, } from '@nestjs/common'; import { ApiCreatedResponse, @@ -24,6 +27,7 @@ import { } from '@nestjs/swagger'; import { Response } from 'express'; +import { SetCacheInterceptor, UnsetCacheInterceptor } from 'src/common'; import { ErrorCodes } from 'src/constants'; import { CreateNamespaceDto } from './dto/create-namespace.dto'; @@ -117,6 +121,8 @@ export class NamespaceController { type: 'string', description: 'Namespace id or key, if key should encodeURIComponent', }) + @UseInterceptors(SetCacheInterceptor) + @CacheTTL(24 * 3600) @Get(':namespaceIdOrKey') async get(@Param('namespaceIdOrKey') namespaceIdOrKey: string): Promise { const namespace = await this.namespaceService.get(namespaceIdOrKey); @@ -137,6 +143,8 @@ export class NamespaceController { description: 'The namespace updated.', type: Namespace, }) + @UseInterceptors(UnsetCacheInterceptor) + @CacheKey('/namespaces/:id,/namespaces/:key') @Patch(':namespaceIdOrKey') async update( @Param('namespaceIdOrKey') namespaceIdOrKey: string, @@ -158,6 +166,8 @@ export class NamespaceController { @ApiOperation({ operationId: 'deleteNamespace' }) @ApiNoContentResponse({ description: 'No content.' }) @HttpCode(HttpStatus.NO_CONTENT) + @UseInterceptors(UnsetCacheInterceptor) + @CacheKey('/namespaces/:id,/namespaces/:key') @Delete(':namespaceId') async delete(@Param('namespaceId') namespaceId: string): Promise { await this.namespaceService.delete(namespaceId); diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index fcb8f56..fc82c94 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,18 +1,24 @@ +import { CLIENT_RENEG_LIMIT } from 'tls'; + +import { CacheKey } from '@nestjs/cache-manager'; import { BadRequestException, Body, + CacheTTL, ConflictException, Controller, Delete, Get, HttpCode, HttpStatus, + Inject, NotFoundException, Param, Patch, Post, Query, Res, + UseInterceptors, } from '@nestjs/common'; import { ApiCreatedResponse, @@ -23,7 +29,9 @@ import { ApiTags, } from '@nestjs/swagger'; import { Response } from 'express'; +import { RedisClientType } from 'redis'; +import { SetCacheInterceptor, UnsetCacheInterceptor } from 'src/common'; import * as config from 'src/constants'; import { NamespaceService } from 'src/namespace'; @@ -41,7 +49,8 @@ import { verifyIdentity } from './verify-identity'; export class UserController { constructor( private readonly userService: UserService, - private readonly namespaceService: NamespaceService + private readonly namespaceService: NamespaceService, + @Inject('REDIS_CLIENT') private readonly redis: RedisClientType ) {} /** @@ -153,6 +162,7 @@ export class UserController { const count = await this.userService.count(query); const data = await this.userService.list(query); res.set({ 'X-Total-Count': count.toString() }).json(data); + return data; } @@ -169,6 +179,8 @@ export class UserController { type: 'string', description: 'User id', }) + @UseInterceptors(SetCacheInterceptor) + @CacheTTL(24 * 3600) @Get(':userId') async get(@Param('userId') userId: string): Promise { const user = await this.userService.get(userId); @@ -190,6 +202,8 @@ export class UserController { type: User, }) @Patch(':userId') + @UseInterceptors(UnsetCacheInterceptor) + @CacheKey('/users/:id') async update( @Param('userId') userId: string, @Body() updateDto: UpdateUserDto @@ -288,6 +302,8 @@ export class UserController { description: 'The user upserted.', type: User, }) + @UseInterceptors(UnsetCacheInterceptor) + @CacheKey('/users/:id') @Post(':employeeId/@upsertUserByEmployeeId') async upsertByEmployeeId( @Param('employeeId') employeeId: string, @@ -387,6 +403,8 @@ export class UserController { description: 'The user upserted.', type: User, }) + @UseInterceptors(UnsetCacheInterceptor) + @CacheKey('/users/:id') @Post(':username/@upsertUserByUsername') async upsertByUsername( @Param('username') username: string, @@ -404,6 +422,8 @@ export class UserController { description: 'The user upserted.', type: User, }) + @UseInterceptors(UnsetCacheInterceptor) + @CacheKey('/users/:id') @Post(':email/@upsertUserByEmail') async upsertByEmail( @Param('email') email: string, @@ -420,6 +440,8 @@ export class UserController { description: 'The user upserted.', type: User, }) + @UseInterceptors(UnsetCacheInterceptor) + @CacheKey('/users/:id') @Post(':phone/@upsertUserByPhone') async upsertByPhone( @Param('phone') phone: string, @@ -434,6 +456,8 @@ export class UserController { @ApiOperation({ operationId: 'deleteUser' }) @ApiNoContentResponse({ description: 'No content.' }) @HttpCode(HttpStatus.NO_CONTENT) + @UseInterceptors(UnsetCacheInterceptor) + @CacheKey('/users/:id') @Delete(':userId') async delete(@Param('userId') userId: string): Promise { await this.userService.delete(userId); @@ -448,6 +472,8 @@ export class UserController { type: User, }) @HttpCode(HttpStatus.OK) + @UseInterceptors(UnsetCacheInterceptor) + @CacheKey('/users/:id') @Post(':userId/@verifyIdentity') async verifyIdentity(@Param('userId') userId: string): Promise { const user = await this.userService.get(userId); @@ -473,6 +499,8 @@ export class UserController { */ @ApiOperation({ operationId: 'updatePassword' }) @HttpCode(HttpStatus.NO_CONTENT) + @UseInterceptors(UnsetCacheInterceptor) + @CacheKey('/users/:id') @Post(':userId/@updatePassword') async updatePassword( @Param('userId') userId: string, From 02ab1601aa8bd501a72c4f8155ee981f57e1075c Mon Sep 17 00:00:00 2001 From: magiccaptain Date: Wed, 22 Jan 2025 10:17:36 +0800 Subject: [PATCH 2/4] chore: no cache for update pwd --- src/user/user.controller.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index fc82c94..0381249 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -499,8 +499,6 @@ export class UserController { */ @ApiOperation({ operationId: 'updatePassword' }) @HttpCode(HttpStatus.NO_CONTENT) - @UseInterceptors(UnsetCacheInterceptor) - @CacheKey('/users/:id') @Post(':userId/@updatePassword') async updatePassword( @Param('userId') userId: string, From 07910645193052fca52c05543e67c00611207ed7 Mon Sep 17 00:00:00 2001 From: magiccaptain Date: Wed, 22 Jan 2025 10:47:54 +0800 Subject: [PATCH 3/4] feat: unset key when delete --- src/namespace/namespace.controller.ts | 1 - src/user/user.controller.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/namespace/namespace.controller.ts b/src/namespace/namespace.controller.ts index ae27837..bf4105b 100644 --- a/src/namespace/namespace.controller.ts +++ b/src/namespace/namespace.controller.ts @@ -167,7 +167,6 @@ export class NamespaceController { @ApiNoContentResponse({ description: 'No content.' }) @HttpCode(HttpStatus.NO_CONTENT) @UseInterceptors(UnsetCacheInterceptor) - @CacheKey('/namespaces/:id,/namespaces/:key') @Delete(':namespaceId') async delete(@Param('namespaceId') namespaceId: string): Promise { await this.namespaceService.delete(namespaceId); diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 0381249..3ca9e4b 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -457,7 +457,6 @@ export class UserController { @ApiNoContentResponse({ description: 'No content.' }) @HttpCode(HttpStatus.NO_CONTENT) @UseInterceptors(UnsetCacheInterceptor) - @CacheKey('/users/:id') @Delete(':userId') async delete(@Param('userId') userId: string): Promise { await this.userService.delete(userId); From c4cad54fb710b9e37f97f6ae1265a4041f9d78dc Mon Sep 17 00:00:00 2001 From: magiccaptain Date: Wed, 22 Jan 2025 11:02:03 +0800 Subject: [PATCH 4/4] fix: eslint --- src/user/user.controller.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 3ca9e4b..c479cef 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,5 +1,3 @@ -import { CLIENT_RENEG_LIMIT } from 'tls'; - import { CacheKey } from '@nestjs/cache-manager'; import { BadRequestException,