You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS中如何动态为非公开路由添加Swagger授权响应装饰器?

问题描述

我正在学习并开发一个NestJS个人项目,目前正在集成Swagger。若要标识某路由可能返回UnauthorizedException,通常需要手动添加@ApiUnauthorizedResponse装饰器,示例代码如下:

@ApiUnauthorizedResponse({ description: 'Unauthorized' })
@Get()
findAll() {
  return this.usersService.findAll();
}

但我希望为所有非公开路由自动添加该装饰器。我设想通过Interceptor获取当前路由处理器及isPublic元数据,判断是否为非公开路由后为其添加装饰器,Interceptor的大致实现如下:

@Injectable()
export class UnauthSwaggerInterceptor implements NestInterceptor {
  constructor(private readonly reflector: Reflector) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const handler = context.getHandler();

    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (!isPublic) {
      //  Apply Swagger decorator to handler
    }

    return next.handle();
  }
}

其中,我通过Public装饰器标记公开路由:

export const IS_PUBLIC = 'isPublic';

export const Public = () => SetMetadata(IS_PUBLIC, true);

请问能否在运行时通过路由处理器引用动态添加该Swagger装饰器?若可行,正确的实现方式是什么?

解决方案

直接在Interceptor里动态添加Swagger装饰器不可行,因为Swagger的装饰器是在应用启动时(编译阶段)解析元数据的,而Interceptor是运行时执行的,这时候Swagger已经完成了文档生成的扫描工作,动态添加的装饰器不会被识别。

正确的做法是在应用启动前,通过自定义装饰器或者扩展Swagger的扫描逻辑来批量添加@ApiUnauthorizedResponse,具体有两种实现方式:

方式一:自定义全局装饰器,批量处理非公开路由

创建一个自定义装饰器,结合Reflector判断路由是否为公开路由,自动为非公开路由添加Swagger响应装饰器:

import { applyDecorators, SetMetadata } from '@nestjs/common';
import { ApiUnauthorizedResponse } from '@nestjs/swagger';
import { Reflector } from '@nestjs/core';

export const IS_PUBLIC = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC, true);

// 自定义装饰器:自动为非公开路由添加Unauthorized响应
export const ApiProtectRoute = () => {
  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    const reflector = new Reflector();
    // 检查当前路由或控制器是否标记为Public
    const isPublic = reflector.getAllAndOverride<boolean>(IS_PUBLIC, [
      descriptor.value,
      target,
    ]);

    if (!isPublic) {
      // 应用Swagger装饰器
      ApiUnauthorizedResponse({ description: 'Unauthorized' })(target, propertyKey, descriptor);
    }
  };
};

在控制器上全局使用该装饰器,所有路由默认非公开,标记@Public()的路由则不会添加Unauthorized响应:

@Controller('users')
@ApiProtectRoute() // 控制器级别全局应用
export class UsersController {
  @Get()
  findAll() {
    return this.usersService.findAll();
  }

  @Public() // 标记为公开路由
  @Get('public')
  getPublicData() {
    return 'public content';
  }
}

方式二:扩展Swagger文档生成逻辑,启动时批量处理

通过SwaggerModule的自定义配置,在生成文档前遍历所有路由元数据,为非公开路由添加响应定义:

import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { INestApplication } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC } from './public.decorator';

export function setupSwagger(app: INestApplication) {
  const config = new DocumentBuilder()
    .setTitle('API Docs')
    .setDescription('API documentation')
    .setVersion('1.0')
    .build();

  const reflector = app.get(Reflector);
  const document = SwaggerModule.createDocument(app, config, {
    // 自定义操作处理器,批量修改路由元数据
    operationFactory: (controllerKey: string, methodKey: string) => {
      const operation = SwaggerModule.createOperation(controllerKey, methodKey);
      const controller = app.get(controllerKey);
      const handler = controller[methodKey];

      // 检查是否为公开路由
      const isPublic = reflector.getAllAndOverride<boolean>(IS_PUBLIC, [
        handler,
        controller.constructor,
      ]);

      if (!isPublic) {
        // 添加Unauthorized响应到Swagger操作定义
        operation.responses['401'] = {
          description: 'Unauthorized',
          schema: { type: 'object', properties: { message: { type: 'string' } } },
        };
      }

      return operation;
    },
  });

  SwaggerModule.setup('api', app, document);
}

在main.ts中调用该方法:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  setupSwagger(app);
  await app.listen(3000);
}
bootstrap();

为什么Interceptor不可行?

Interceptor的intercept方法是在每次请求运行时执行的,而Swagger的元数据解析是在应用启动阶段完成的——也就是SwaggerModule.createDocument执行时就已经扫描完所有装饰器信息了。运行时动态添加的装饰器无法被Swagger的扫描逻辑捕获,所以不会出现在文档中。

内容的提问来源于stack exchange,提问作者Daniel Maranhao

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 02:05:24