Nestjs框架: 请求生命周期与应用生命周期

概述

  • 在 NestJS 框架中,中间件(Middleware)、管道(Pipes)、过滤器(Filters)、拦截器(Interceptors) 均属于请求处理流程的核心组件,它们共同构成了 NestJS 的 请求生命周期(Request Lifecycle)

  • 中间件、管道、过滤器、拦截器属于 请求生命周期,用于管理单次请求的处理流程;而 onModuleInit 等钩子属于应用生命周期,用于管理应用状态

  • 两者的区别

    类型 请求生命周期组件 应用程序生命周期钩子
    作用对象 单次 HTTP 请求 整个应用启动/关闭过程
    典型方法 middleware.use(), @UseInterceptors() onModuleInit(), onApplicationShutdown()
    关注点 请求处理流程控制 资源初始化、数据库连接释放等
  • 中间件、管道、过滤器、拦截器属于 请求生命周期 ,用于管理单次请求的处理流程;而 onModuleInit 等钩子属于 应用生命周期,用于管理应用状态

请求生命周期

1 )请求生命周期中的角色与执行顺序

当客户端发起请求时,NestJS 会按以下顺序处理

  1. 中间件(Middleware)

    • 定位:请求的第一道关卡,处理全局或路由级别的预处理(如日志、CORS)。
    • 特点:可访问请求/响应对象,需调用 next() 传递控制权。
    • 示例:日志记录、请求头校验
  2. 守卫(Guards)

    • 定位:第二道关卡,负责权限验证(如角色校验、JWT 认证)
    • 特点:决定请求是否进入控制器(Controller)
    • :用户问题未提及守卫,但因其在流程中紧接中间件,需补充说明
  3. 拦截器(Interceptors)

    • 定位:第三道关卡,包裹控制器逻辑,处理请求前后逻辑。
    • 功能:
      • 请求前:修改请求数据;
      • 响应后:统一封装响应格式、记录执行时间
    • 示例 :全局响应包装({ code: 200, data: ... }
  4. 管道(Pipes)

    • 定位:第四道关卡,聚焦数据转换与验证。
    • 功能:
      • 转换:将输入数据转为目标类型(如字符串转数字);
      • 验证:校验参数有效性,失败时抛出异常
    • 示例@Param('id', ParseIntPipe) 自动转换路径参数
  5. 控制器(Controller)

    • 定位:执行业务逻辑,返回响应。
  6. 拦截器 (Post)

    • 响应前的程序拦截
  7. 异常过滤器(Exception Filters)

    • 定位:请求流程的"安全网",捕获全局或局部异常
    • 功能:统一处理错误响应(如转换技术异常为友好提示)

2 ) 代码示例:实现请求周期钩子

基于您提供的NestJS生命周期结构图,我已全面补充了每个组件的完整代码实现,确保所有子类(全局/控制器/路由级别)均有完整示例。以下是按执行顺序重新生成的代码:

ts 复制代码
src/
├── middleware/
│    └── global.middleware.ts
├── guards/
│    ├── global.guard.ts
│    ├── controller.guard.ts
│    └── route.guard.ts
├──interceptors/
│    ├── global.interceptor.ts
│    ├── controller.interceptor.ts
│    └── route.interceptor.ts
├──pipes/
│    ├── global.pipe.ts
│    ├── controller.pipe.ts
│    └── route.pipe.ts
├── controllers/
│    └── example.controller.ts
├── filters/
│    ├── global.filter.ts
│    └── route.filter.ts

2.1 中间件(Middleware)

typescript 复制代码
// 全局中间件
@Injectable()
export class GlobalMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('全局中间件执行');
    next();
  }
}
 
// 模块中间件(在模块内注册)
@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(ModuleMiddleware).forRoutes('*');
  }
}

2.2 守卫(Guards)

typescript 复制代码
// 全局守卫
@Injectable()
export class GlobalGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    console.log('全局守卫验证');
    return true;
  }
}
 
// 控制器守卫 
@Controller('users')
@UseGuards(ControllerGuard)
export class UserController {}
 
// 路由守卫 
@Get('profile')
@UseGuards(RouteGuard)
getProfile() { /*...*/ }

2.3 拦截器(Interceptors) PRE处理阶段(请求到达控制器前):

typescript 复制代码
// 全局拦截器pre 
@Injectable()
export class GlobalPreInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler) {
    console.log('全局拦截器pre');
    return next.handle();
  }
}
 
// 控制器拦截器pre
@UseInterceptors(ControllerPreInterceptor)
@Controller('posts')
export class PostController {}
 
// 路由拦截器pre
@Get(':id')
@UseInterceptors(RoutePreInterceptor)
getPost() { /*...*/ }

2.4 管道(Pipes)

typescript 复制代码
// 全局管道(main.ts注册)
app.useGlobalPipes(new ValidationPipe());
 
// 控制器管道
@UsePipes(ControllerPipe)
@Controller('products')
export class ProductController {}
 
// 路由参数管道
@Get(':id')
getProduct(@Param('id', ParseIntPipe) id: number) { /*...*/ }

2.5 控制器(Controller) & 服务(Service)

typescript 复制代码
@Controller('orders')
export class OrderController {
  constructor(private orderService: OrderService) {}
 
  @Post()
  createOrder(@Body() data: CreateOrderDto) {
    return this.orderService.process(data); // 服务处理业务逻辑 
  }
}

2.6 拦截器(Post 处理阶段) 响应返回客户端前:

typescript 复制代码
// 路由拦截器post
@Injectable()
export class RoutePostInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler) {
    return next.handle().pipe(
      map(data => ({ status: 'success', data })) // 统一响应格式 
    );
  }
}
// 全局/控制器级post拦截器同理

2.7 过滤器(Exception Filters)

typescript 复制代码
// 全局过滤器
@Catch(HttpException)
export class GlobalFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    ctx.getResponse().status(500).json({ error: exception.message });
  }
}
 
// 控制器过滤器
@UseFilters(ControllerFilter)
@Controller('payments')
export class PaymentController {}
 
// 路由过滤器 
@Post()
@UseFilters(RouteFilter)
makePayment() { /*...*/ }

3 ) 各组件核心功能总结

组件 核心职责 典型场景 注册方式
中间件 预处理请求/响应 日志记录、请求头设置 全局 app.use() 或模块绑定
守卫 访问控制 角色验证、API 权限 控制器类或方法装饰器
拦截器 拦截请求/响应 响应包装、性能监控 全局或控制器级 @UseInterceptors()
管道 数据转换与验证 参数类型转换、DTO 校验 控制器方法参数装饰器
过滤器 统一异常处理 捕获 HttpException 返回友好错误 全局 app.useGlobalFilters()

4 ) 关键注册方式对比

组件类型 全局注册 控制器注册 路由注册
中间件 app.use(GlobalMiddleware) Module.configure() ❌ 不支持
守卫 app.useGlobalGuards() @UseGuards() @UseGuards()
拦截器 app.useGlobalInterceptors() @UseInterceptors() @UseInterceptors()
管道 app.useGlobalPipes() @UsePipes() @Param(key, Pipe)
过滤器 app.useGlobalFilters() @UseFilters() @UseFilters()

5 ) 请求生命周期执行顺序总结

阶段 组件类型 执行顺序
请求进入 中间件(pre) 1
认证/授权 守卫 2
数据预处理 拦截器(pre) 3
数据验证 管道 4
控制器处理 控制器 5
数据处理 服务 6
数据后处理 拦截器(post) 7
异常处理 过滤器 8
响应发送 响应体 9

6 )可扩展性建议

  • 日志追踪:可在中间件或拦截器中加入请求 ID,实现全链路日志追踪。
  • 权限统一管理:将守卫和拦截器结合 JWT 实现 RBAC 权限系统。
  • 响应结构统一:通过拦截器 post 统一返回格式。
  • 自定义管道:实现自定义数据校验逻辑,如手机号格式校验。
  • 异常处理增强:区分客户端错误与服务器错误,返回不同响应。

7 )技术建议

  1. 链路追踪:在全局中间件中添加请求ID,贯穿所有生命周期组件 [1]
  2. 响应标准化:通过POST拦截器统一返回格式 { code: 200, data: T }
  3. 管道进阶:自定义验证管道(如手机号格式校验)[9]
  4. 错误分层:使用过滤器区分客户端错误(4xx)和服务端错误(5xx) [5]

完整可运行项目代码可通过此模板快速初始化

应用生命周期

NestJS的生命周期钩子允许开发者在应用启动、运行和终止的关键阶段注入逻辑,是实现初始化、资源清理和异常管理的核心机制,下面看下生命周期全流程图示
初始化阶段 onModuleInit onApplicationBootstrap 运行阶段 请求处理 终止阶段 onModuleDestroy beforeApplicationShutdown onApplicationShutdown

1 )生命周期核心阶段概览

NestJS生命周期分为三大阶段,共6个核心钩子

  1. 初始化阶段
    • onModuleInit():模块依赖初始化后触发,适合服务预热(如数据库连接池初始化)
    • onApplicationBootstrap():所有模块和服务就绪后触发,HTTP服务器已启动
  2. 运行阶段
    • 无专用生命周期钩子,由请求级控制器和服务处理业务逻辑
  3. 终止阶段(需手动启用enableShutdownHooks()
    • onModuleDestroy():收到终止信号(如SIGTERM)时触发
    • beforeApplicationShutdown():关闭前执行异步清理任务(如保存状态)
    • onApplicationShutdown():资源释放完成后触发(如关闭数据库连接)
  4. 钩子函数特性与约束
    • 异步支持:所有钩子可返回 Promise,Nest 将等待其解决后再继续生命周期
    • 作用域限制:钩子仅作用于 模块/提供者/控制器,不适用于请求作用域实例
    • 平台差异:终止钩子在Windows系统对SIGTERM信号支持有限,建议优先使用SIGINTSIGBREAK

2 ) 核心钩子对比表

钩子函数 触发时机 典型用途
onModuleInit 模块依赖解析完成 初始化模块级全局服务
onApplicationBootstrap HTTP 服务器启动前 加载动态配置/预热缓存
beforeApplicationShutdown 连接关闭前 持久化状态/发送终止通知
onApplicationShutdown 进程退出前最后时机 强制释放占用的硬件资源

3 )最佳代码实践与常见问题

初始化顺序管理

typescript 复制代码
// 方案1:模块拆分(强依赖场景)  
@Module({ imports: [DatabaseModule] }) // 确保 DatabaseModule 先初始化  
export class PublisherModule implements OnModuleInit { /* ... */ }

// 方案2:协调器模式(复杂依赖)  
@Injectable()  
class InitializationCoordinator {  
  async init() {  
    await db.connect();  
    await mq.start();  
  }  
}  

说明:同一模块内提供者的 onModuleInit 并行执行,需通过依赖注入手动控制时序

优雅终止实现示例

typescript 复制代码
@Injectable()  
class TaskService implements OnApplicationShutdown {  
  async onApplicationShutdown() {  
    await this.flushPendingTasks(); // 清理未完成任务  
    await this.releaseResources();  // 释放文件句柄/网络连接  
  }  
}  

4 )实际应用场景

  • 启动时:预加载配置、建立长连接(如WebSocket)
  • 终止时:
    • 关闭数据库连接(避免数据损坏)
    • 结束未完成的任务队列
    • 发送系统停机通知

5 )常见陷阱

  1. 依赖顺序问题

    • 若服务A依赖服务B,需将两者拆分到不同模块,利用模块初始化顺序保证依赖可用性
    • 错误示例:同一模块内,消息发布者可能在Kafka客户端未就绪时发送消息
  2. 异步操作处理

    钩子返回Promise时,NestJS会等待其完成再进入下一阶段

    typescript 复制代码
    async onApplicationBootstrap() {
      await this.loadCache(); // 阻塞直至缓存加载完成
    }
  3. 终止信号管理 ,钩子未触发

    main.ts中显式启用关闭钩子

    typescript 复制代码
    const app = await NestFactory.create(AppModule);
    app.enableShutdownHooks(); // 启用终止信号监听
  4. 资源泄露:在 beforeApplicationShutdown 中关闭数据库连接池、停止消息队列消费者

  5. 竞态条件:避免在并行钩子中访问共享资源,优先使用协调器同步

6 )参考资料

7 )总结

理解NestJS生命周期是构建健壮应用的基础。通过合理使用钩子,开发者能

  • 确保资源按需初始化
  • 实现应用优雅关闭
  • 避免竞态条件和资源泄漏