在 NestJS 项目中,最推荐的做法是组合使用全局异常过滤器(Exception Filter)捕获错误,配合拦截器(Interceptor)统一成功响应格式。这样既能保证异步错误不泄露堆栈,又能让前后端接口结构一致,同时保留正确的 HTTP 状态码语义。
先说结论:通过全局过滤器拦截所有未处理异常并转换为标准 JSON,再用拦截器包裹正常返回值,实现全链路响应统一。
- 适合:需要统一前端错误提示码、隐藏服务端内部错误详情的中大型项目。
- 关键点:过滤器必须区分 HttpException 与普通 Error,避免将 404/400 等客户端错误统一转为 500。
- 建议:自定义业务异常类继承 HttpException,以便在过滤器中区分业务错误与系统错误。
核心代码实现
以下是经过修正的完整代码结构,包含必要的 import 语句、状态码判断逻辑以及响应拦截器实现。
1. 自定义业务异常类
为了方便抛出业务错误,建议封装一个基础异常类。
// common/exceptions/business.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';
export class BusinessException extends HttpException {
constructor(message: string, status: HttpStatus = HttpStatus.BAD_REQUEST) {
super(message, status);
}
}2. 全局异常过滤器
修复了强制返回 500 的问题,增加了对 HttpException 实例的判断,保留原始状态码。
// common/filters/all-exceptions.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = '系统繁忙,请稍后重试';
// 区分 HttpException 与其他错误,避免掩盖 404/400 等状态
if (exception instanceof HttpException) {
status = exception.getStatus();
message = exception.getMessage();
}
response.status(status).json({
code: status,
message,
data: null,
path: request.url,
});
}
} 3. 统一响应拦截器
使用 RxJS 的 map 操作符包裹正常返回数据,确保成功响应的结构统一。
// common/interceptors/transform-response.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response {
code: number;
message: string;
data: T;
}
@Injectable()
export class TransformResponseInterceptor implements NestInterceptor> {
intercept(context: ExecutionContext, next: CallHandler): Observable> {
return next.handle().pipe(
map((data) => ({
code: 200,
message: 'success',
data,
})),
);
}
} 4. 全局注册
在 main.ts 中完成注册,确保覆盖所有模块。
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
import { TransformResponseInterceptor } from './common/interceptors/transform-response.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 注册全局异常过滤器
app.useGlobalFilters(new AllExceptionsFilter());
// 注册全局响应拦截器
app.useGlobalInterceptors(new TransformResponseInterceptor());
await app.listen(3000);
}
bootstrap();验证方法
完成代码部署后,通过以下命令验证异常处理是否生效且状态码正确。
1. 验证系统异常(500)
在 Controller 中故意抛出普通 Error,检查是否被捕获且不带堆栈信息。
curl -v http://localhost:3000/api/test-error预期结果:HTTP 状态码 500,响应体包含统一 code 和 message,无 stack 字段。
2. 验证业务异常(400/404)
抛出 BusinessException 或 HttpException,检查状态码是否保持原样。
curl -v http://localhost:3000/api/test-not-found预期结果:HTTP 状态码 404 或 400,响应体结构与其他错误一致。
3. 验证正常响应
请求正常接口,检查是否被拦截器包裹。
curl -v http://localhost:3000/api/test-success预期结果:HTTP 状态码 200,响应体结构为 { code: 200, message: 'success', data: ... }。
常见坑与排查
- 过滤器顺序问题:如果同时使用了局部过滤器和全局过滤器,局部优先于全局。确保全局过滤器能兜底未捕获的异常。
- 异步错误未捕获:在 Service 层调用异步方法时,如果没有 await 或者没有 try-catch,错误可能会 bypass 某些逻辑。确保所有异步操作都被正确 awaited,或者让错误自然抛出由全局过滤器捕获。
- RxJS 错误流:拦截器中如果使用 RxJS 操作符,注意错误流不会被 map 捕获。如果需要拦截器处理错误,需使用 catchError 操作符,但通常建议错误交给过滤器处理。
- 响应体被多次修改:过滤器只负责错误,拦截器只负责成功响应的格式转换。不要在过滤器中修改成功响应,也不要在拦截器中处理异常逻辑,避免冲突。