Nest.js如何动态限制请求速率

  • Nest.js 使用 @nestjs/throttler 时如何动态限制请求速率?

  • 场景:有个 API,平常限制请求速率为 1 分钟 1 次,现在业务有个场景:如果符合某个条件就暂时放开限制,客户端可以立即再次发起请求,服务端再次判断,如果不再符合条件了,就再限制上请求速率。

  • @nestjs/throttler ​默认使用装饰器,没办法完成上面的需求。查看源码,发现装饰器的核心代码如下:

    • // throttler.decorator.ts
      export const Throttle = (limit = 20, ttl = 60): MethodDecorator & ClassDecorator => {
        return (
          target: any,
          propertyKey?: string | symbol,
          descriptor?: TypedPropertyDescriptor<any>,
        ) => {
          if (descriptor) {
            setThrottlerMetadata(descriptor.value, limit, ttl);
            return descriptor;
          }
          setThrottlerMetadata(target, limit, ttl);
          return target;
        };
      };
      
      // throttler.decorator.ts
      export const SkipThrottle = (skip = true): MethodDecorator & ClassDecorator => {
        return (
          target: any,
          propertyKey?: string | symbol,
          descriptor?: TypedPropertyDescriptor<any>,
        ) => {
          if (descriptor) {
            Reflect.defineMetadata(THROTTLER_SKIP, skip, descriptor.value);
            return descriptor;
          }
          Reflect.defineMetadata(THROTTLER_SKIP, skip, target);
          return target;
        };
      };
      
    • 发现是通过定义元数据来实现是否限制速率,知道这些,就可以在业务逻辑中手动设置了。

    • 先把 @nestjs/throttler​​ 引用的常量单独提出来:

      • // throttler.constant.ts
        export const THROTTLER_LIMIT = 'THROTTLER:LIMIT';
        export const THROTTLER_TTL = 'THROTTLER:TTL';
        export const THROTTLER_SKIP = 'THROTTLER:SKIP';
        
    • 再根据业务逻辑单独设置:

      • // auth.controller.ts
        
        @Controller('auths')
        export class AuthController {
          @Throttle(1, 60)
          @Post('confirmation')
          async sendConfirmation(@Body('email') email: string) {
            const self = this.sendConfirmation;
        
            if (userIsExist) {
              // 设置元数据,忽略请求速率的限制
              Reflect.defineMetadata(THROTTLER_SKIP, true, self);
              throw new BadRequestException('用户已存在');
            } else {
              // 重新设置限制
              Reflect.defineMetadata(THROTTLER_SKIP, false, self);
            }
        
            return this.authService.sendConfirmation(email);
          }
        }