跳转到内容

Api错误码与异常返回规范

错误码体系与异常处理约定


[Requirement] 错误码为 6 位数字,结构为 {模块位 2 位}{类型位 1 位}{序号 3 位},便于定位来源。

40 0 001
│ │ └── 序号(001-999)
│ └──── 类型:0=通用 1=参数 2=业务 3=权限 4=系统 5=第三方
└─────── 模块:10=用户 20=订单 30=支付 40=通用 ...

[Standard] 错误码以 int 传输,不用字符串;成功固定为 0。

模块范围示例
通用400000-409999400001 参数错误
认证鉴权410000-419999410001 Token 无效
权限430000-439999430001 无操作权限
用户100000-109999100001 用户不存在
订单200000-209999200001 订单不存在
支付300000-309999300001 支付失败
系统500000-509999500001 内部错误
错误码含义HTTP
0成功200
400001参数错误(校验失败)400
400002缺少必填参数400
404001资源不存在404
409001资源冲突409
410001未认证(Token 缺失/失效)401
410002Token 已过期401
430001无权限403
429001请求过于频繁429
500001系统内部错误500
503001服务不可用503

[Requirement] 错误码一旦发布不可复用、不可变更含义;废弃时保留占位并标注 deprecated。


[Requirement] 错误响应与成功响应共用信封结构,code 为非 0,data 为 null,并增加 errors 字段承载字段级错误。

{
  "code": 400001,
  "message": "参数校验失败",
  "data": null,
  "errors": [
    { "field": "email", "message": "邮箱格式不正确" },
    { "field": "age", "message": "年龄不能小于 0" }
  ],
  "trace_id": "7f3a9c2e1b4d..."
}

[Standard] message 为面向用户/调用方的可读提示,不包含堆栈、SQL、内部类名等敏感信息。

[Standard] 字段级错误通过 errors 数组返回,便于前端定位表单字段。

[Standard] HTTP 状态码表达错误类别,code 表达具体业务原因;二者配套使用。

错误类别HTTPcode 范围
成功2000
参数错误400400xxx
未认证401410xxx
无权限403430xxx
资源不存在404404xxx
冲突409409xxx
限流429429xxx
服务端错误500500xxx
服务不可用503503xxx
// 反模式 1:message 泄漏内部信息
{ "code": 500001, "message": "NullPointerException at UserService.java:42" }
 
// 反模式 2:用 200 传输业务错误
HTTP/1.1 200 OK
{ "code": 400001, "message": "参数错误" }   // 应用 HTTP 400
 
// 反模式 3:无 errors 字段,前端无法定位
{ "code": 400001, "message": "参数错误" }

[Standard] 后端异常分为三级,不同级别处理策略不同。

级别类型示例处理方式告警
业务异常可预期、用户可恢复余额不足、参数错误返回业务码,不告警否
系统异常依赖故障、可重试DB 超时、第三方超时重试/降级,记录日志警告
未知异常代码 Bug、不可预期NPE、数组越界记录堆栈,告警紧急

[Requirement] 全局异常处理器(@ControllerAdvice / FastAPI exception_handler)统一捕获并转换,禁止在 Controller 层手写 try-catch 包装。

@app.exception_handler(BusinessError)
async def business_error_handler(request, exc: BusinessError):
    return JSONResponse(
        status_code=exc.http_status,
        content={"code": exc.code, "message": exc.message, "data": None, "trace_id": ctx.trace_id},
    )

[Caution] 禁止在异常消息中拼接用户输入(防止日志注入与信息泄漏),敏感字段需脱敏。


[Requirement] 每个请求必须生成或透传 trace_id(优先从请求头 X-Trace-Id 读取,无则生成 UUID),并写入响应头与响应体。

[Standard] 日志、错误响应、下游调用必须携带同一 trace_id,实现全链路串联。

# 请求
X-Trace-Id: 7f3a9c2e1b4d...
 
# 响应头
X-Trace-Id: 7f3a9c2e1b4d...

[Standard] 系统异常与未知异常必须记录完整堆栈(logger.error(..., exc_info=True)),业务异常仅记录 warning 级别。

[Caution] 日志中禁止打印密码、Token、身份证、手机号原文,需脱敏(手机号 138****1234)。

[Default] 错误码作为 metrics 标签上报,按 code、path、method 维度统计错误率,用于告警与趋势分析。


HTTP/1.1 400 Bad Request
X-Trace-Id: a1b2c3d4
 
{
  "code": 400001,
  "message": "参数校验失败",
  "data": null,
  "errors": [
    { "field": "email", "message": "邮箱格式不正确" }
  ],
  "trace_id": "a1b2c3d4"
}
HTTP/1.1 409 Conflict
X-Trace-Id: e5f6g7h8
 
{
  "code": 200003,
  "message": "订单已支付,无法重复支付",
  "data": null,
  "errors": null,
  "trace_id": "e5f6g7h8"
}
HTTP/1.1 500 Internal Server Error
X-Trace-Id: i9j0k1l2
 
{
  "code": 500001,
  "message": "系统内部错误,请稍后重试",
  "data": null,
  "errors": null,
  "trace_id": "i9j0k1l2"
}
class AppError(Exception):
    code: int = 500001
    http_status: int = 500
    message: str = "系统内部错误"
 
    def __init__(self, message: str | None = None):
        if message:
            self.message = message
        super().__init__(self.message)
 
 
class BusinessError(AppError):
    http_status = 400
 
 
class ParamError(BusinessError):
    code = 400001
    message = "参数校验失败"
 
 
class NotFoundError(BusinessError):
    code = 404001
    http_status = 404
    message = "资源不存在"
 
 
class UnauthorizedError(AppError):
    code = 410001
    http_status = 401
    message = "未认证"