NestJS生命周期组件:注册方式与最佳实践

338 阅读4分钟

1. 请求生命周期概述

NestJS请求处理的生命周期按顺序包含以下组件:

  1. 中间件 (Middleware)
  2. 守卫 (Guards)
  3. 拦截器 (Interceptors) - 请求阶段
  4. 管道 (Pipes)
  5. 路由处理器 (Controller Method)
  6. 拦截器 (Interceptors) - 响应阶段(逆序执行)
  7. 异常过滤器 (Exception Filters) - 在任何阶段出现异常时触发

NestJS请求生命周期

2. 中间件 (Middleware)

中间件是请求处理的第一道关卡,可以访问请求和响应对象,适合处理日志、认证等通用功能。

全局注册

方法1: 在main.ts中使用app.use()

// main.ts
const app = await NestFactory.create(AppModule);
app.use(loggerMiddleware);

注意: 通过app.use()注册的中间件总是先于在模块中通过configure()注册的中间件执行。

方法2: 在AppModule中实现NestModule接口

// app.module.ts
@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*'); // 应用到所有路由
  }
}

局部注册

// app.module.ts
@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats'); // 特定路由
      // 或者
      // .forRoutes({ path: 'cats', method: RequestMethod.GET });
      // 或者
      // .forRoutes(CatsController);
  }
}

函数式中间件

// 函数式中间件
export function loggerMiddleware(req: Request, res: Response, next: NextFunction) {
  console.log(`请求路径: ${req.path}`);
  next();
}

多个中间件链式应用

consumer
  .apply(cors(), helmet(), logger)
  .forRoutes('*');

排除特定路由

consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'health', method: RequestMethod.GET },
    { path: 'api/docs', method: RequestMethod.ALL }
  )
  .forRoutes('*');

3. 守卫 (Guards)

守卫主要负责授权决策,决定请求是否可以继续处理。

全局注册

方法1: 在main.ts中使用useGlobalGuards()

// main.ts
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new AuthGuard());

方法2: 使用APP_GUARD令牌(支持依赖注入)

// 可以在任何模块中注册,不必只在AppModule
import { APP_GUARD } from '@nestjs/core';
​
@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: AuthGuard,
    },
  ],
})
export class AppModule {}

局部注册

控制器级别:

@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}

方法级别:

@Post()
@UseGuards(RolesGuard)
create(@Body() createCatDto: CreateCatDto) {}

使用反射器传递元数据

// roles.decorator.ts
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
​
// 控制器中使用
@Post()
@Roles('admin')
create() {}
​
// 守卫中获取元数据
@Injectable()
export class RolesGuard {
  constructor(private reflector: Reflector) {}
​
  canActivate(context: ExecutionContext): boolean {
    // 从方法获取元数据
    const methodRoles = this.reflector.get<string[]>('roles', context.getHandler());
    
    // 从控制器获取元数据
    const controllerRoles = this.reflector.get<string[]>('roles', context.getClass());
    
    // 合并控制器和方法的元数据(优先使用方法的元数据)
    const roles = this.reflector.getAllAndOverride<string[]>('roles', [
      context.getHandler(),
      context.getClass(),
    ]);
    
    if (!roles) {
      return true;
    }
    
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return roles.some((role) => user.roles?.includes(role));
  }
}

不同上下文中的守卫

// 支持不同类型的应用(HTTP、微服务、WebSocket)
@Injectable()
export class AuthGuard {
  canActivate(context: ExecutionContext): boolean {
    const contextType = context.getType();
    
    if (contextType === 'http') {
      // HTTP处理逻辑
      const request = context.switchToHttp().getRequest();
      return this.validateRequest(request);
    } 
    else if (contextType === 'rpc') {
      // 微服务处理逻辑
      const data = context.switchToRpc().getData();
      return this.validateRpcData(data);
    }
    else if (contextType === 'ws') {
      // WebSocket处理逻辑
      const client = context.switchToWs().getClient();
      return this.validateWsConnection(client);
    }
  }
}

4. 拦截器 (Interceptors)

拦截器可以在请求和响应的处理前后添加逻辑,适合用于日志、转换响应等场景。

全局注册

方法1: 在main.ts中使用useGlobalInterceptors()

// main.ts
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());

方法2: 使用APP_INTERCEPTOR令牌(支持依赖注入)

// 可以在任何模块中注册,不必只在AppModule
import { APP_INTERCEPTOR } from '@nestjs/core';
​
@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
  ],
})
export class AppModule {}

使用APP_INTERCEPTOR令牌注册的拦截器只需要在一个模块中注册一次即可。可以在主模块或专门的拦截器模块中注册:

// interceptors.module.ts
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { LoggingInterceptor } from './logging.interceptor';
import { TimeoutInterceptor } from './timeout.interceptor';
​
@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
    {
      provide: APP_INTERCEPTOR,
      useClass: TimeoutInterceptor, // 可以注册多个拦截器
    },
  ],
})
export class InterceptorsModule {}

注意: 当注册多个拦截器时,请求阶段按注册顺序执行,响应阶段按相反顺序执行。这是NestJS的特殊设计,以便拦截器可以按"洋葱模型"工作。

局部注册

控制器级别:

@Controller('cats')
@UseInterceptors(LoggingInterceptor)
export class CatsController {}

方法级别:

@Get()
@UseInterceptors(LoggingInterceptor)
findAll() {}

响应映射与转换

@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map(data => ({
        statusCode: context.switchToHttp().getResponse().statusCode,
        data,
        timestamp: new Date().toISOString(),
      })),
    );
  }
}

缓存实现

@Injectable()
export class CacheInterceptor implements NestInterceptor {
  private readonly cacheMap = new Map();

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const key = this.buildKey(context);
    
    if (this.cacheMap.has(key)) {
      return of(this.cacheMap.get(key));
    }
    
    return next.handle().pipe(
      tap(response => this.cacheMap.set(key, response)),
    );
  }

  private buildKey(context: ExecutionContext): string {
    const request = context.switchToHttp().getRequest();
    return `${request.method}-${request.url}`;
  }
}

超时拦截器

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      timeout(5000),
      catchError(err => {
        if (err instanceof TimeoutError) {
          return throwError(() => new RequestTimeoutException());
        }
        return throwError(() => err);
      }),
    );
  }
}

5. 管道 (Pipes)

管道用于数据转换和验证,在控制器方法执行前处理参数。

全局注册

方法1: 在main.ts中使用useGlobalPipes()

// main.ts
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());

方法2: 使用APP_PIPE令牌(支持依赖注入)

// 可以在任何模块中注册,不必只在AppModule
import { APP_PIPE } from '@nestjs/core';
​
@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
  ],
})
export class AppModule {}

局部注册

控制器级别:

@Controller('cats')
@UsePipes(ValidationPipe)
export class CatsController {}

方法级别:

@Post()
@UsePipes(ValidationPipe)
create(@Body() createCatDto: CreateCatDto) {}

参数级别:

@Post()
create(@Body(ValidationPipe) createCatDto: CreateCatDto) {}

自定义验证管道(支持异步)

@Injectable()
export class CustomValidationPipe implements PipeTransform {
  // 支持同步方式
  transform(value: any, metadata: ArgumentMetadata) {
    const { metatype } = metadata;
    
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    
    // 自定义验证逻辑
    if (!value || Object.keys(value).length === 0) {
      throw new BadRequestException('值不能为空');
    }
    return value;
  }
  
  // 也支持异步方式
  async transformAsync(value: any, metadata: ArgumentMetadata) {
    const { metatype } = metadata;
    
    // 异步验证
    const result = await this.validator.validate(value);
    if (result.errors.length > 0) {
      throw new BadRequestException('验证失败');
    }
    return value;
  }
​
  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

全局启用验证器转换

// main.ts
app.useGlobalPipes(
  new ValidationPipe({
    transform: true, // 自动转换类型
    whitelist: true, // 自动移除非DTO中定义的属性
    forbidNonWhitelisted: true, // 当存在非白名单属性时抛出错误
    transformOptions: {
      enableImplicitConversion: true, // 启用隐式类型转换
    },
  }),
);

模式验证管道

import Joi from 'joi';
​
@Injectable()
export class JoiValidationPipe implements PipeTransform {
  constructor(private schema: Joi.Schema) {}
​
  transform(value: any) {
    const { error, value: validatedValue } = this.schema.validate(value);
    if (error) {
      throw new BadRequestException('验证失败');
    }
    return validatedValue;
  }
}
​
// 使用
@Post()
create(
  @Body(new JoiValidationPipe(createCatSchema)) createCatDto: CreateCatDto,
) {}

6. 异常过滤器 (Exception Filters)

异常过滤器捕获应用程序中抛出的异常,并返回自定义响应。

全局注册

方法1: 在main.ts中使用useGlobalFilters()

// main.ts
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());

方法2: 使用APP_FILTER令牌(支持依赖注入)

// 可以在任何模块中注册,不必只在AppModule
import { APP_FILTER } from '@nestjs/core';
​
@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

局部注册

控制器级别:

@Controller('cats')
@UseFilters(HttpExceptionFilter)
export class CatsController {}

方法级别:

@Post()
@UseFilters(HttpExceptionFilter)
create(@Body() createCatDto: CreateCatDto) {}

注意: 异常过滤器的执行顺序与其他组件不同。首先执行方法级过滤器,然后是控制器级,最后是全局过滤器。这样设计是为了让更具体的过滤器有机会先处理异常。

全局异常处理并记录日志

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  constructor(private logger: LoggerService) {}
​
  catch(exception: any, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    
    const status = 
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;
    
    const errorResponse = {
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      method: request.method,
      message: exception.message || '服务器内部错误',
    };
    
    this.logger.error(
      `${request.method} ${request.url}`,
      exception.stack,
      'ExceptionFilter',
    );
    
    response.status(status).json(errorResponse);
  }
}

特定异常过滤器

@Catch(QueryFailedError, TypeORMError)
export class DatabaseExceptionFilter implements ExceptionFilter {
  catch(exception: QueryFailedError | TypeORMError, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    
    response.status(HttpStatus.BAD_REQUEST).json({
      statusCode: HttpStatus.BAD_REQUEST,
      message: '数据库操作错误',
      error: exception.message,
    });
  }
}

WebSocket异常过滤器

@Catch()
export class WsExceptionFilter implements ExceptionFilter {
  catch(exception: any, host: ArgumentsHost) {
    const client = host.switchToWs().getClient();
    const data = host.switchToWs().getData();
    
    const error = {
      statusCode: HttpStatus.BAD_REQUEST,
      message: exception.message || '错误',
      data,
    };
    
    client.emit('exception', error);
  }
}

7. 执行顺序与性能考虑

执行顺序

NestJS请求生命周期的执行顺序:

  1. 中间件

    • 先执行main.ts中通过app.use()注册的全局中间件
    • 然后执行模块中通过configure()注册的中间件
  2. 守卫

    • 全局守卫
    • 控制器级守卫
    • 路由级守卫
  3. 拦截器(请求阶段)

    • 全局拦截器(按注册顺序)
    • 控制器级拦截器
    • 路由级拦截器
  4. 管道

    • 全局管道
    • 控制器级管道
    • 路由级管道
    • 参数级管道
  5. 路由处理器(控制器方法)

  6. 拦截器(响应阶段,以相反顺序)

    • 路由级拦截器
    • 控制器级拦截器
    • 全局拦截器(按注册的相反顺序)
  7. 异常过滤器(如果有异常抛出)

    • 方法级过滤器
    • 控制器级过滤器
    • 全局过滤器

注意:在同一类别中(如全局守卫、全局拦截器等),组件的执行顺序通常按照它们的注册顺序。

性能考虑

对于性能敏感的应用,应该考虑在哪个生命周期阶段执行特定逻辑:

  • 中间件适合处理认证、日志等所有请求通用的操作
  • 守卫适合处理授权逻辑
  • 拦截器适合处理响应转换和日志记录
  • 管道适合处理输入验证和转换

依赖注入注意事项

在main.ts中使用useGlobal*方法注册的组件不支持依赖注入,因为它们在NestJS IoC容器外部实例化。

要启用依赖注入,必须使用以下方法之一:

  1. 使用APP_*令牌在模块的providers中注册(推荐方式)
  2. 在main.ts中使用应用程序上下文手动获取实例:
const app = await NestFactory.create(AppModule);
const authGuard = app.get(AuthGuard);
app.useGlobalGuards(authGuard);

特别说明:使用APP_*令牌注册时,如果同一类型的令牌注册了多个实现(如多个APP_INTERCEPTOR),它们都会被注册为全局组件。这是NestJS的特殊设计,方便模块化开发。

8. 综合案例

下面是一个综合使用各种生命周期组件的案例:

// app.module.ts
@Module({
  imports: [],
  providers: [
    // 配置全局验证管道
    {
      provide: APP_PIPE,
      useValue: new ValidationPipe({
        transform: true,
        whitelist: true,
      }),
    },
    // 配置全局异常过滤器
    {
      provide: APP_FILTER,
      useClass: GlobalExceptionFilter,
    },
    // 配置全局守卫
    {
      provide: APP_GUARD,
      useClass: JwtAuthGuard,
    },
    // 配置全局拦截器
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
    {
      provide: APP_INTERCEPTOR,
      useClass: TransformInterceptor,
    },
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    // 配置全局中间件
    consumer
      .apply(LoggerMiddleware, HelmetMiddleware)
      .forRoutes('*');
      
    // 为特定路由配置中间件
    consumer
      .apply(AuthMiddleware)
      .exclude('auth/login', 'auth/register')
      .forRoutes('*');
  }
}
​
// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 配置全局中间件
  app.use(cors());
  app.use(helmet());
  
  await app.listen(3000);
}
bootstrap();

在这个例子中,我们:

  1. 使用全局中间件处理跨域和安全头
  2. 添加全局JWT守卫进行认证
  3. 添加全局验证管道处理输入验证
  4. 添加全局异常过滤器统一处理错误
  5. 添加全局日志拦截器和响应转换拦截器