一个请求进入你的 NestJS API,RolesGuard 检查 request.body.age >= 18,结果它拒绝了每一个请求——包括发送 { "age": 25 } 的请求。DTO 上标着 @Type(() => Number),验证管道已经注册,代码也能编译通过,但它仍然失败。
问题不在守卫的逻辑里,而在一个关于运行顺序的假设上:守卫相对于管道,到底什么时候运行?那个管道本应把字符串转换成数字。
![]()
你能学到什么
到文章结尾,你将能够:
- 准确说出请求在 Nest 中经过的顺序——中间件、守卫、拦截器、管道、处理器和过滤器——并解释为什么是这个顺序
- 预测守卫、拦截器或管道在运行的那一刻能看到什么、不能看到什么
- 追踪当守卫拒绝或管道抛出异常时,管道其余部分会发生什么
- 把授权、验证和横切逻辑放进真正为每个任务构建的组件里
- 在多个守卫或拦截器堆叠时,理清全局、控制器和路由级别的顺序
适合谁读
你至少构建过一个带 @Controller()、一个 @Get() 处理器、也许还有一个 ValidationPipe 的 NestJS 控制器。你不需要已经写过自定义守卫或拦截器——我们会从零开始构建这两者。
目录
- 问题:一个永远赢不了的守卫
- 心智模型:带两个回环的单向管道
- 阶段 1:中间件
- 阶段 2:守卫
- 阶段 3:拦截器,“之前”那一半
- 阶段 4:管道
- 阶段 5:处理器
- 阶段 6:拦截器,“之后”那一半
- 阶段 7:异常过滤器
- 边界情况与坑
- 最佳实践:哪个组件,做什么
- 常见问题
- 速查表
问题:一个永远赢不了的守卫
下面是控制器和那个据称在保护它的守卫:
发送 { "age": 25 } 作为 JSON。此时 req.body.age 是字符串 "25",因为 body 解析器只产生 JSON 形状的值——字符串、数字、布尔值、对象和数组,而不会根据 DTO 元数据做类型转换。
守卫在管道之前运行,所以它看到的是原始请求体,而不是转换后的 DTO。这就是为什么 req.body.age >= 18 在 age 为字符串 "25" 时,JavaScript 会把它与数字 18 比较,字符串 "25" 会被强制转换为数字 25,比较结果本应为真。但这里有一个更隐蔽的问题:如果 age 是 "18" 这样的字符串,比较行为取决于具体值和类型强制转换的细节,而真正的问题在于守卫根本不应该承担验证类型的职责。
这个例子的核心矛盾是:开发者假设 DTO 上的 @Type(() => Number) 会在守卫运行前把请求体里的字符串转换成数字,但 NestJS 的实际执行顺序让守
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.