本文目录导读:

标准化API的成功与错误响应是提升系统可维护性、降低前后端协作成本的关键,目前业界最通用且成熟的规范是 JSON:API 或基于 RESTful 风格的自定义规范。
以下是推荐的标准化方案,结合了RFC 7807(Problem Details for HTTP APIs)和常见的最佳实践。
核心原则
- 统一结构:无论是成功还是错误,响应体都遵循相同的顶层字段结构。
- HTTP状态码 + 业务码:HTTP状态码表示传输层状态(如200, 400, 500),业务码(
code)表示业务逻辑状态(如1000表示成功,2001表示用户不存在)。 - 自描述性:错误信息要足够具体,能帮助调用方确定问题所在。
推荐的标准响应结构
采用一个通用的ApiResponse对象,包含四个关键字段:
| 字段名 | 类型 | 是否必须 | 说明 |
|---|---|---|---|
success |
boolean |
是 | 业务处理是否成功。true 或 false。 |
code |
string/int |
是 | 业务状态码,成功时固定(如"OK"或1000),失败时为具体错误码。 |
message |
string |
是 | 对本次响应的简短、人类可读的描述。 |
data |
object/null |
是 | 承载实际数据的容器,成功时必须包含数据;失败时为null或错误详情。 |
errors |
array |
推荐 | (新增字段) 详细错误列表,用于表单验证等场景。 |
成功响应的标准化
HTTP状态码:200 OK、201 Created、204 No Content。
示例1:获取单个资源(200)
{
"success": true,
"code": "OK",
"message": "用户信息查询成功",
"data": {
"id": 123,
"name": "张三",
"email": "zhangsan@example.com",
"created_at": "2024-01-15T10:30:00Z"
},
"errors": null
}
示例2:创建资源成功(201)
{
"success": true,
"code": "CREATED",
"message": "订单创建成功",
"data": {
"order_id": "ORD-20241015-0001",
"status": "pending"
},
"errors": null
}
示例3:列表查询(200)
建议在data中包含分页元信息,而非平铺。
{
"success": true,
"code": "OK",
"message": "查询成功",
"data": {
"items": [
{ "id": 1, "title": "文章1" },
{ "id": 2, "title": "文章2" }
],
"pagination": {
"page": 1,
"page_size": 20,
"total": 58,
"total_pages": 3
}
},
"errors": null
}
错误响应的标准化
核心原则:区分系统级错误(如数据库连接失败)和业务级错误(如余额不足、参数校验失败)。
通用错误结构(遵循 RFC 7807 风格)
{
"success": false,
"code": "PARAM_INVALID",
"message": "请求参数校验失败",
"data": null,
"errors": [
{
"field": "email",
"type": "invalid_format",
"message": "邮箱格式不正确"
},
{
"field": "age",
"type": "range_error",
"message": "年龄必须在18-60之间"
}
]
}
常见HTTP状态码与业务码映射
| 场景 | HTTP状态码 | 业务码 (code) |
示例 message |
|---|---|---|---|
| 请求成功 | 200/201 | OK / CREATED |
处理成功 |
| 参数错误 | 400 | PARAM_INVALID |
请求参数验证失败 |
| 未授权 | 401 | UNAUTHORIZED |
令牌无效或已过期 |
| 无权限 | 403 | FORBIDDEN |
当前用户无操作权限 |
| 资源未找到 | 404 | NOT_FOUND |
用户ID 123 不存在 |
| 请求冲突 | 409 | RESOURCE_CONFLICT |
邮箱已被注册 |
| 依赖失败 | 422 | UNPROCESSABLE_ENTITY |
业务规则不满足(如余额不足) |
| 请求频次超限 | 429 | RATE_LIMIT_EXCEEDED |
请求过于频繁,请稍后重试 |
| 服务器内部错误 | 500 | INTERNAL_ERROR |
服务器内部异常,请联系管理员 |
| 服务不可用 | 503 | SERVICE_UNAVAILABLE |
服务维护中 |
注意:永远不要将服务器内部异常栈(如Java StackTrace)暴露给客户端!使用通用message代替。
特殊场景处理
分页与列表
- 错误案例:把分页信息放在
errors里。 - 正确做法:如上文,分页元数据放在
data.pagination内。
文件上传
- 成功:返回文件URL和元数据。
- 失败:返回具体的字段错误,如
file.too_large、type.unsupported。
异步操作(202 Accepted)
对于耗时操作,可以返回202 Accepted状态码:
{
"success": true,
"code": "ACCEPTED",
"message": "任务已提交,正在处理",
"data": {
"task_id": "task_abc123",
"status_url": "/tasks/task_abc123/status"
},
"errors": null
}
最佳实践总结
- 一致性:整个API的所有端点必须遵循同一结构,不允许成功返回
data,失败返回error这种不一致的设计。 - 拒绝裸数组:永远不要在顶层直接返回一个数组 ,顶层必须是
data对象。 - 统一错误码管理:建立中心化的错误码字典(如枚举或常量类),避免散落在代码各处。
- 国际化:
message字段建议支持占位符,并预留多语言切换的能力(可通过Accept-Language头部或统一管理)。 - 日志链路:在
errors或根层级中,可以附加一个trace_id用于内部排障,但不向外暴露堆栈。 - 框架集成:
- Spring Boot:使用
@ControllerAdvice+ResponseEntityExceptionHandler全局处理。 - Django:使用
django-rest-framework的exception_handler+ 自定义响应格式化。 - Node.js (Express):使用中间件统一包装
res.success()和res.fail()。
一个完整的代码示例(伪代码)
# Python / Flask 示例
def success_response(data=None, message="操作成功", code="OK", http_status=200):
return {
"success": True,
"code": code,
"message": message,
"data": data,
"errors": None
}, http_status
def error_response(message="请求失败", code="ERROR", errors=None, http_status=400):
if errors is None:
errors = []
return {
"success": False,
"code": code,
"message": message,
"data": None,
"errors": errors
}, http_status
# 使用
@app.route('/users', methods=['POST'])
def create_user():
try:
user = create_user_service(request.json)
return success_response(data=user, message="用户创建成功", code="CREATED", http_status=201)
except ValidationError as e:
return error_response(message="参数校验失败", code="PARAM_INVALID", errors=e.errors, http_status=400)
except Exception:
return error_response(message="服务器内部错误", code="INTERNAL_ERROR", http_status=500)
通过以上标准化方案,你的API将具备高度的可预测性、可调试性和兼容性,能够很好地支持前端、移动端和第三方调用的需求。