如何设计异常返回结构
设计异常返回结构(Error / Exception Response)时,目标是:统一、可读、可追踪、可扩展。下面从设计原则 → 常见结构 → 示例 → 实践建议逐步说明。
一、设计原则(非常重要)
- 统一格式
- 所有接口成功 / 失败返回结构一致
- 可定位问题
- 包含错误码、错误信息、请求标识
- 对前端友好
- 前端能直接展示或做逻辑判断
- 对开发友好
- 包含堆栈、内部信息(仅开发/测试环境)
- 不泄露敏感信息
- 生产环境不返回 SQL、路径、系统信息
二、推荐的通用异常返回结构
✅ 最常用(推荐)
{
"code": 400,
"message": "参数校验失败",
"detail": "用户名不能为空",
"requestId": "a1b2c3d4",
"timestamp": "2026-01-15T10:30:00Z"
}
字段说明
| 字段 | 说明 |
|---|---|
code |
业务错误码(非 HTTP 状态码) |
message |
简短错误描述 |
detail |
详细原因(可选) |
requestId |
请求唯一 ID,用于排错 |
timestamp |
错误时间 |
三、HTTP 状态码 vs 业务错误码
✅ 建议做法
- HTTP 状态码:表示请求是否成功
- 业务 code:表示具体业务错误
示例
| 场景 | HTTP Status | code |
|---|---|---|
| 参数错误 | 400 | 1001 |
| 未登录 | 401 | 2001 |
| 无权限 | 403 | 2002 |
| 资源不存在 | 404 | 3001 |
| 系统异常 | 500 | 9999 |
四、不同场景的异常结构示例
1️⃣ 参数校验异常
{
"code": 1001,
"message": "参数校验失败",
"errors": [
{
"field": "username",
"message": "用户名不能为空"
}
]
}
2️⃣ 业务异常(如余额不足)
{
"code": 4001,
"message": "余额不足",
"detail": "当前余额 10,无法支付 50"
}
3️⃣ 系统异常(生产环境)
{
"code": 9999,
"message": "系统异常,请稍后重试",
"requestId": "req-123456"
}
4️⃣ 系统异常(开发环境)
{
"code": 9999,
"message": "NullPointerException",
"stack": "com.xxx.service.UserService.getUser(UserService.java:23)",
"requestId": "req-123456"
}
五、成功与失败结构对比
✅ 成功返回
{
"code": 0,
"data": {
"id": 1,
"name": "Tom"
}
}
❌ 失败返回
{
"code": 1001,
"message": "参数错误"
}
六、错误码设计规范(推荐)
错误码结构
A-BB-CC
- A:错误级别(1=系统,2=业务,3=权限)
- BB:模块
- CC:具体错误
示例
| 错误码 | 含义 |
|---|---|
| 1001 | 系统-通用-参数错误 |
| 2001 | 业务-用户-未登录 |
| 3001 | 权限-无访问权限 |
七、不同语言实践建议
Java(Spring Boot)
@ControllerAdvice- 自定义异常 + 统一返回体
Go
- 定义
AppErrorstruct - 统一中间件处理
Node.js
- 自定义
AppError - Express 错误处理中间件
八、常见坑
❌ 直接把异常抛给前端
❌ 返回 SQL 错误
❌ 成功和失败结构不一致
❌ 前端需要解析字符串才能判断错误
九、总结一句话
异常返回结构 = 统一格式 + 明确错误码 + 可追踪 + 不泄露敏感信息
如果你告诉我:
- 使用语言(Java / Go / Node / Python)
- 是否前后端分离
- 是否微服务
我可以给你可直接用的完整代码模板。