跳转到内容

异常过滤器

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 — 始终为 false
  • timestamp — 错误发生时间戳

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: '格式错误'
})

构造参数

参数类型默认值说明
messagestring错误消息
businessCodenumberhttpStatus业务错误码
httpStatusHttpStatus400HTTP 状态码
detailsunknown附加错误详情

异常处理优先级

过滤器按以下顺序处理异常:

  1. Fastify 特有错误 — 文件过大、请求体过大、JSON 解析错误等
  2. AppException — 自定义业务异常(带业务错误码)
  3. HttpException — NestJS 标准 HTTP 异常
  4. 未知异常 — 统一返回 500 + 通用错误消息

Fastify 错误映射

错误代码HTTP 状态码错误消息
FST_REQ_FILE_TOO_LARGE400文件超过大小限制
FST_ERR_CTP_BODY_TOO_LARGE413请求体过大
FST_ERR_CTP_INVALID_JSON_BODY400JSON 格式错误
FST_ERR_CTP_INVALID_TYPE415不支持的媒体类型
FST_ERR_HOOK_TIMEOUT408请求处理超时
Body cannot be empty400请求体不能为空

链路追踪

每个错误响应自动包含 traceId(雪花 ID),可用于关联请求日志:

json
{
  "code": 500,
  "data": null,
  "message": "程序错误",
  "success": false,
  "timestamp": 1700000000000,
  "traceId": "1234567890123456789"
}

对于 500+ 错误,过滤器会自动记录详细堆栈信息到日志,包含请求方法、URL 和 IP。

安全特性

  • 不暴露内部错误细节给客户端
  • 未知 Fastify 错误只返回通用消息
  • 防止重复发送响应(response.sent 检测)
  • 响应发送失败时静默记录日志

最佳实践

  1. 全局注册:通过 APP_FILTER 注册 AllExceptionsFilter,确保所有异常都被捕获
  2. 使用 AppException:业务异常统一抛出 AppException,携带业务错误码,便于前端区分处理
  3. 业务码规范:建议分段管理业务错误码,如 1xxx 用户模块、2xxx 订单模块
  4. 不要后置处理:避免在 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)

相关文档