外观
异常过滤器
AllExceptionsFilter 全局捕获所有异常,统一错误响应格式,并自动记录错误日志。
注册
typescript
import { AllExceptionsFilter } from '@maxtan/nest-core'
// main.ts
const app = await NestFactory.create(AppModule)
app.useGlobalFilters(new AllExceptionsFilter())统一错误响应
所有异常均被转换为如下格式:
json
{
"code": 400,
"data": null,
"message": "用户名已存在",
"success": false,
"timestamp": 1700000000000,
"traceId": "1234567890123456789"
}code— HTTP 状态码或自定义业务错误码message— 错误描述success— 始终为falsetimestamp— 错误发生时间戳
AppException — 自定义业务异常
typescript
import { AppException } from '@maxtan/nest-core'
// 基本用法
throw new AppException('用户名已存在')
// 自定义业务错误码
throw new AppException('用户名已存在', 1002)
// 自定义 HTTP 状态码
throw new AppException('余额不足', 2001, HttpStatus.PAYMENT_REQUIRED)
// 附加错误详情
throw new AppException('验证失败', 400, HttpStatus.BAD_REQUEST, {
field: 'email',
reason: '格式错误'
})构造参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
message | string | — | 错误消息 |
businessCode | number | httpStatus | 业务错误码 |
httpStatus | HttpStatus | 400 | HTTP 状态码 |
details | unknown | — | 附加错误详情 |
异常处理优先级
过滤器按以下顺序处理异常:
- Fastify 特有错误 — 文件过大、请求体过大、JSON 解析错误等
- AppException — 自定义业务异常(带业务错误码)
- HttpException — NestJS 标准 HTTP 异常
- 未知异常 — 统一返回 500 + 通用错误消息
Fastify 错误映射
| 错误代码 | HTTP 状态码 | 错误消息 |
|---|---|---|
FST_REQ_FILE_TOO_LARGE | 400 | 文件超过大小限制 |
FST_ERR_CTP_BODY_TOO_LARGE | 413 | 请求体过大 |
FST_ERR_CTP_INVALID_JSON_BODY | 400 | JSON 格式错误 |
FST_ERR_CTP_INVALID_TYPE | 415 | 不支持的媒体类型 |
FST_ERR_HOOK_TIMEOUT | 408 | 请求处理超时 |
| Body cannot be empty | 400 | 请求体不能为空 |
链路追踪
每个错误响应自动包含 traceId(雪花 ID),可用于关联请求日志:
json
{
"code": 500,
"data": null,
"message": "程序错误",
"success": false,
"timestamp": 1700000000000,
"traceId": "1234567890123456789"
}对于 500+ 错误,过滤器会自动记录详细堆栈信息到日志,包含请求方法、URL 和 IP。
安全特性
- 不暴露内部错误细节给客户端
- 未知 Fastify 错误只返回通用消息
- 防止重复发送响应(
response.sent检测) - 响应发送失败时静默记录日志
最佳实践
- 全局注册:通过
APP_FILTER注册AllExceptionsFilter,确保所有异常都被捕获 - 使用 AppException:业务异常统一抛出
AppException,携带业务错误码,便于前端区分处理 - 业务码规范:建议分段管理业务错误码,如
1xxx用户模块、2xxx订单模块 - 不要后置处理:避免在 Controller 中 try/catch 全部异常,让过滤器统一处理
typescript
// 推荐:业务错误码常量
enum BizCode {
USER_NOT_FOUND = 1001,
USER_DISABLED = 1002,
ORDER_EXPIRED = 2001,
}
// 使用
throw new AppException('用户不存在', BizCode.USER_NOT_FOUND)
throw new AppException('用户已禁用', BizCode.USER_DISABLED, HttpStatus.FORBIDDEN)