异常捕获&统一异常格式
全局捕获:捕获自定义业务异常、捕获 http 异常、捕获其他异常
统一格式:捕获后统一异常格式 :::
1、自定义业务异常
ts
// src/common/exceptions/business.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';
// 业务异常接口(规范返回字段)
export interface BusinessExceptionOptions {
code: number; // 业务码(非HTTP状态码)
message: string; // 错误消息
httpStatus?: HttpStatus; // HTTP状态码,默认500
data?: any; // 附加数据
}
/**
* 业务异常基类
* 所有业务相关异常都继承此类
*/
export class BusinessException extends HttpException {
readonly code: number;
readonly data?: any;
constructor(options: BusinessExceptionOptions) {
// 调用父类构造器,传递响应体和HTTP状态码
super(
{
code: options.code,
message: options.message,
data: options.data,
},
options.httpStatus ?? HttpStatus.INTERNAL_SERVER_ERROR,
);
this.code = options.code;
this.data = options.data;
}
// 快捷方法:创建参数错误异常(HTTP 400)
static badRequest(code: number, message: string, data?: any) {
return new BusinessException({
code,
message,
httpStatus: HttpStatus.BAD_REQUEST,
data,
});
}
// 快捷方法:创建未授权异常(HTTP 401)
static unauthorized(code: number, message: string, data?: any) {
return new BusinessException({
code,
message,
httpStatus: HttpStatus.UNAUTHORIZED,
data,
});
}
}2、定义异常过滤器
ts
// src/common/filters/global-exception.filter.ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { BusinessException } from '../exceptions/business.exception';
/**
* 全局异常过滤器
* 捕获所有异常,统一响应格式
*/
@Catch() // 不指定异常类型 = 捕获所有异常
export class GlobalExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(GlobalExceptionFilter.name);
catch(exception: unknown, host: ArgumentsHost) {
// 获取HTTP上下文(req/res)
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
// 1. 初始化默认响应参数
let status = HttpStatus.INTERNAL_SERVER_ERROR; // 默认500
let code = 50000; // 默认业务码
let message = '服务器内部错误';
let error = undefined;
let data: null;
let stack: string | undefined = undefined;
// 2. 区分异常类型处理
if (exception instanceof BusinessException) {
// 处理自定义业务异常
status = exception.getStatus();
code = exception.code;
message = exception.message;
data = exception.data;
stack = (exception.cause as Error)?.stack; // 根因栈信息
this.logger.warn(
`[业务异常] ${request.method} ${request.url} | 业务码: ${code} | 消息: ${message}`,
);
} else if (exception instanceof HttpException) {
// 处理内置HTTP异常(非业务异常)
status = exception.getStatus();
const responseBody = exception.getResponse();
// 兼容Nest内置响应格式(字符串或对象)
if (typeof responseBody === 'string') {
message = responseBody;
} else {
message = (responseBody as any).message || message;
code = (responseBody as any).code || status * 100; // 适配默认格式
error = (responseBody as any).error;
}
this.logger.warn(
`[HTTP异常] ${request.method} ${request.url} | HTTP状态码: ${status} | 消息: ${message}`,
);
} else {
// 处理未知异常(如原生Error、字符串等)
message =
exception instanceof Error ? exception.message : String(exception);
stack = exception instanceof Error ? exception.stack : undefined;
this.logger.error(
`[未知异常] ${request.method} ${request.url} | 消息: ${message}`,
stack,
);
}
// 3. 构建标准化响应体
const responseBody = {
code,
message,
data: data || null,
path: request.url,
method: request.method,
timestamp: new Date().toISOString(),
// 开发环境返回栈信息,生产环境隐藏
...(process.env.NODE_ENV === 'development' && { stack, error }),
};
// 4. 发送响应
response.status(status).json(responseBody);
}
}3、定义全局异常过滤器
ts
// src/main.ts
import { GlobalExceptionFilter } from './common/filters/global-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 注册全局异常过滤器
app.useGlobalFilters(new GlobalExceptionFilter());
app.setGlobalPrefix('api/v1');
await app.listen(3000);
}
bootstrap();4、测试
- http 异常测试
shell
# 请求一个不存在的路由
http://localhost:3000/api/v1/user123456
# 结果
{
"code": 40400,
"message": "Cannot POST /api/v1/user123465",
"data": null,
"path": "/api/v1/user123465",
"method": "POST",
"timestamp": "2025-12-09T08:07:41.751Z",
"error": "Not Found"
}- 自定义业务异常测试
shell
# 在 src/user/user.controller.ts 中,使用自定义异常
import { BusinessException } from '../common/exceptions/business.exception';
@Post()
async create(@Body() CreateUserDto: CreateUserDto) {
throw BusinessException.badRequest(40001, '使用自定义异常');
}
# 进入此路由并触发自定义异常
http://localhost:3000/api/v1/user # 方法: POST 、参数: { "username": "alias", "password": "1212343" }
# 结果
{
"code": 40001,
"message": "使用自定义异常",
"data": null,
"path": "/api/v1/user",
"method": "POST",
"timestamp": "2025-12-09T08:11:42.664Z"
}- 其他系统异常测试
shell
# 在 src/user/user.controller.ts 中,使用自定义异常
@Post()
async create(@Body() CreateUserDto: CreateUserDto) {
throw new Error('报错了哦!')
}
# 进入此路由并触发异常
http://localhost:3000/api/v1/user # 方法: POST 、参数: { "username": "alias", "password": "1212343" }
# 结果
{
"code": 50000,
"message": "报错了哦!",
"data": null,
"path": "/api/v1/user",
"method": "POST",
"timestamp": "2025-12-09T08:13:39.811Z",
"stack": "Error: 报错了哦!\n at UserController.create (/Users/macbookpro/Desktop/nest 学习模板/3.1、nest-Mysql-TypeORM-Repository/src/user/user.controller.ts:23:11)\n at /Users/macbookpro/Desktop/nest 学习模板/3.1、nest-Mysql-TypeORM-Repository/node_modules/.pnpm/@nestjs+core@10.4.17_@nestjs+common@10.4.17_class-transformer@0.5.1_class-validator@0.14.3_re_42tscuezzqw2ny42cjwxmnswou/node_modules/@nestjs/core/router/router-execution-context.js:38:29\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)"
}