NestJS 提供了一系列强大的“增强器”或“拦截器”来处理请求生命周期中的各种横切关注点,而无需将这些逻辑散布在控制器或服务中。这就像在中央厨房中设立了各种专业角色,负责在不同环节进行检查、处理或记录,确保菜品的质量、流程的顺畅以及异常情况的妥善处理。
- Guards (守卫): (
@Guard()
) Guards 决定一个请求是否应该被路由处理程序接受。它们通常用于认证(用户是谁)和授权(用户是否有权限执行此操作)。Guard 在请求到达控制器方法之前执行。就像厨房门口的保安或特定区域的门禁,只允许符合条件的人进入。 - Interceptors (拦截器): (
@Interceptor()
) Interceptors 可以在请求被路由处理程序处理之前或响应发送给客户端之后执行额外的逻辑。它们可以用来:转换响应结果、转换请求体、调用额外的逻辑(比如日志)、处理异常、甚至是完全覆盖响应。就像厨房里的观察员,可以在菜品烹饪前/后做记录、调整、甚至在出问题时介入。 - Pipes (管道): (
@Pipe()
) Pipes 用于对请求参数进行转换或验证。它们在请求到达路由处理程序方法参数之前执行。常见的用途是:将字符串格式的路由参数转换为数字、验证请求体的结构和数据是否有效等。就像厨房里的质检员,检查原材料是否符合标准,或进行初步的加工(如清洗、切块)。 - Exception Filters (异常过滤器): (
@Catch()
) Exception Filters 用于捕获应用程序中未处理的异常,并发送一个适当的响应给客户端。这使得你可以定制错误响应的格式和内容。就像厨房里的善后团队,当有意外发生(比如菜烧坏了),他们负责清理现场并向顾客解释情况。
执行顺序 (默认情况下):
请求 -> Middleware (Express/Connect) -> Guards -> Interceptors (pre-handler) -> Pipes -> Controller/Route Handler -> Service -> Interceptors (post-handler) -> Exception Filters -> Response
小例子:使用 Pipe 进行参数验证
我们将创建一个简单的 Pipe 来验证路由参数是否是数字。
使用 CLI 生成一个 Pipe:
nest generate pipe parse-int --no-spec # 创建一个名为 parse-int 的 pipe,不生成测试文件
src/parse-int.pipe.ts
:
// src/parse-int.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
// PipeTransform 接口有两个泛型参数:Input 和 Output
transform(value: string, metadata: ArgumentMetadata): number {
// value 是传入的参数值 (这里是路由参数,通常是字符串)
// metadata 包含参数的元数据 (如参数名、参数类型等)
const intValue = parseInt(value, 10); // 尝试将字符串转换为整数
if (isNaN(intValue)) {
// 如果转换失败 (不是数字),则抛出 BadRequestException
throw new BadRequestException(`参数 "${metadata.data}" 应该是一个数字`);
}
return intValue; // 转换成功,返回数字
}
}
在控制器中使用这个 Pipe:
// src/products/products.controller.ts (修改)
import { Controller, Get, Post, Put, Delete, Param, Body, ParseIntPipe } from '@nestjs/common';
import { ProductsService } from './products.service';
@Controller('api/products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
// ... findAll, create, update, remove 方法 ...
@Get(':id') // GET /api/products/:id
findOne(@Param('id', ParseIntPipe) id: number) { // 将 ParseIntPipe 应用到 id 参数
// 现在,NestJS 会先运行 ParseIntPipe
// 如果传入的 id 不是数字,Pipe 会抛出异常,请求不会到达这里
// 如果是数字,Pipe 会将其转换为 number 类型并传递给 id 参数
console.log('获取产品 ID (number):', id, typeof id);
// 在实际应用中,会根据这个数字 ID 去获取产品
return `获取产品 ID: ${id}`;
}
}
现在,如果你访问 /api/products/abc
,你会收到 400 Bad Request 错误,错误信息是 “参数 ‘id’ 应该是一个数字”。如果你访问 /api/products/123
,id
参数在控制器方法中会是一个数字 123
。
小例子:使用 Exception Filter 捕获异常
我们将创建一个 Exception Filter 来捕获 NotFoundException
并返回自定义的响应格式。
使用 CLI 生成一个 Filter:
nest generate filter http-exception --no-spec # 创建一个捕获 HttpException 的 filter
src/http-exception.filter.ts
:
// src/http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express'; // 导入 express 的 Request 和 Response 类型
@Catch(HttpException) // @Catch() 装饰器指定这个 filter 捕获 HttpException 及其子类
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp(); // 获取 HTTP 上下文
const response = ctx.getResponse<Response>(); // 获取响应对象
const request = ctx.getRequest<Request>(); // 获取请求对象
const status = exception.getStatus(); // 获取 HTTP 状态码
const exceptionResponse = exception.getResponse(); // 获取异常响应体
// 自定义响应格式
const errorResponse = {
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
message: typeof exceptionResponse === 'string' ? exceptionResponse : (exceptionResponse as any).message, // 提取错误消息
error: typeof exceptionResponse === 'string' ? 'HttpException' : (exceptionResponse as any).error || 'Error' // 提取错误类型
};
response
.status(status)
.json(errorResponse); // 发送自定义格式的 JSON 响应
}
}
在 main.ts
中全局应用这个 Filter:
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './http-exception/http-exception.filter'; // 导入 Filter
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局应用 HttpExceptionFilter
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
现在,当你抛出 HttpException
(比如 new NotFoundException()
或 new BadRequestException()
,Pipe 内部就是抛出的 BadRequestException
)时,这个全局 Filter 会捕获它并按照你定义的格式返回响应。
小结: Guards, Interceptors, Pipes, Exception Filters 是 NestJS 提供的重要工具,用于在请求处理的不同阶段插入逻辑。它们分别负责认证授权、请求/响应拦截、数据转换/验证和异常处理,使得核心业务逻辑更纯粹,非业务逻辑被优雅地分离和管理,提高了代码的模块化和可维护性。
练习:
- 在你之前的
my-backend
项目中,确认已经创建了ParseIntPipe
并在GET /api/products/:id
路由中使用。 - 使用 CLI 生成一个 Exception Filter,命名为
any-exception
。 - 修改
any-exception.filter.ts
,使其捕获所有类型的错误 (@Catch()
),并返回一个包含状态码、消息和时间戳的 JSON 响应。 - 在
main.ts
中全局应用你的AnyExceptionFilter
(注意,如果你已经应用了HttpExceptionFilter
,这里可以选择保留一个或者先注释掉HttpExceptionFilter
以测试AnyExceptionFilter
)。 - 在某个路由处理函数中主动抛出一个普通的 JavaScript 错误 (
throw new Error('Something went wrong!');
),运行应用并访问该路由,观察你的全局错误处理 Filter 是否生效,并返回了你定义的响应格式。