11 KiB
11 KiB
API 错误处理
错误响应设计
一致且信息丰富的错误响应对于 API 的可用性至关重要。
标准错误格式
基础错误响应
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "未找到 ID 为 123 的用户",
"details": null
}
}
RFC 7807 问题详情
标准化错误格式 (application/problem+json):
HTTP/1.1 404 Not Found
Content-Type: application/problem+json
{
"type": "https://api.example.com/errors/resource-not-found",
"title": "资源未找到",
"status": 404,
"detail": "ID 为 123 的用户不存在",
"instance": "/users/123"
}
字段说明:
type— 标识错误类型的 URI 引用title— 简短、人类可读的摘要status— HTTP 状态码detail— 针对本次错误的人类可读解释instance— 指向本次错误具体实例的 URI 引用
扩展错误响应
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求验证失败",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "邮箱必须是有效的电子邮件地址"
},
{
"field": "age",
"code": "OUT_OF_RANGE",
"message": "年龄必须在 18 到 120 之间"
}
],
"request_id": "req_123456",
"timestamp": "2024-01-15T10:30:00Z",
"documentation_url": "https://api.example.com/docs/errors#validation-error"
}
}
错误类别
1. 验证错误(400 Bad Request)
客户端发送了无效数据。
POST /users
Content-Type: application/json
{
"name": "",
"email": "invalid-email",
"age": 15
}
Response: 400 Bad Request
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求验证失败",
"details": [
{
"field": "name",
"code": "REQUIRED",
"message": "姓名为必填项"
},
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "邮箱必须是有效的电子邮件地址"
},
{
"field": "age",
"code": "OUT_OF_RANGE",
"message": "年龄必须至少为 18",
"constraints": {
"min": 18,
"max": 120
}
}
]
}
}
2. 身份认证错误(401 Unauthorized)
缺少或提供了无效的身份认证凭据。
GET /users/123
Authorization: Bearer invalid_token
Response: 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token"
{
"error": {
"code": "INVALID_TOKEN",
"message": "访问令牌无效或已过期",
"details": {
"reason": "token_expired",
"expired_at": "2024-01-15T10:00:00Z"
}
}
}
常见认证错误码:
MISSING_TOKEN— 未提供认证令牌INVALID_TOKEN— 令牌格式错误或无效EXPIRED_TOKEN— 令牌已过期REVOKED_TOKEN— 令牌已被撤销
3. 授权错误(403 Forbidden)
已通过身份认证但无权执行操作。
DELETE /users/123
Authorization: Bearer valid_token
Response: 403 Forbidden
{
"error": {
"code": "INSUFFICIENT_PERMISSIONS",
"message": "您没有权限删除此用户",
"details": {
"required_permission": "users:delete",
"your_permissions": ["users:read", "users:update"]
}
}
}
4. 未找到错误(404 Not Found)
资源不存在。
GET /users/99999
Response: 404 Not Found
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "未找到 ID 为 99999 的用户",
"details": {
"resource_type": "User",
"resource_id": "99999"
}
}
}
5. 冲突错误(409 Conflict)
请求与当前状态冲突。
POST /users
Content-Type: application/json
{
"email": "existing@example.com",
"name": "John Doe"
}
Response: 409 Conflict
{
"error": {
"code": "RESOURCE_ALREADY_EXISTS",
"message": "邮箱 'existing@example.com' 对应的用户已存在",
"details": {
"field": "email",
"value": "existing@example.com",
"existing_resource": "/users/123"
}
}
}
6. 速率限制(429 Too Many Requests)
客户端超出了速率限制。
GET /users
Response: 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705320000
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "您已超出速率限制",
"details": {
"limit": 100,
"window": "1 hour",
"retry_after": 60,
"reset_at": "2024-01-15T11:00:00Z"
}
}
}
7. 服务器错误(500 Internal Server Error)
意外的服务器错误。
GET /users/123
Response: 500 Internal Server Error
{
"error": {
"code": "INTERNAL_SERVER_ERROR",
"message": "发生意外错误,请稍后重试。",
"request_id": "req_123456",
"timestamp": "2024-01-15T10:30:00Z"
}
}
切勿暴露:
- 堆栈跟踪
- 数据库错误
- 内部路径
- 敏感配置
8. 服务不可用(503 Service Unavailable)
服务暂时不可用。
GET /users
Response: 503 Service Unavailable
Retry-After: 300
{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "服务因维护暂时不可用",
"details": {
"retry_after": 300,
"maintenance_end": "2024-01-15T12:00:00Z"
}
}
}
错误码目录
为您的 API 定义标准错误码:
{
"VALIDATION_ERROR": {
"status": 400,
"description": "请求验证失败",
"subcodes": {
"REQUIRED": "缺少必填字段",
"INVALID_FORMAT": "字段格式无效",
"OUT_OF_RANGE": "值超出允许范围",
"INVALID_ENUM": "值不在允许集合中"
}
},
"AUTHENTICATION_ERROR": {
"status": 401,
"description": "身份认证失败",
"subcodes": {
"MISSING_TOKEN": "未提供认证令牌",
"INVALID_TOKEN": "令牌无效",
"EXPIRED_TOKEN": "令牌已过期"
}
},
"AUTHORIZATION_ERROR": {
"status": 403,
"description": "权限不足",
"subcodes": {
"INSUFFICIENT_PERMISSIONS": "缺少所需权限",
"RESOURCE_FORBIDDEN": "禁止访问资源"
}
},
"RESOURCE_NOT_FOUND": {
"status": 404,
"description": "资源未找到"
},
"CONFLICT_ERROR": {
"status": 409,
"description": "请求与当前状态冲突",
"subcodes": {
"RESOURCE_ALREADY_EXISTS": "资源已存在",
"CONCURRENT_MODIFICATION": "资源已被其他请求修改"
}
},
"RATE_LIMIT_EXCEEDED": {
"status": 429,
"description": "超出速率限制"
},
"INTERNAL_SERVER_ERROR": {
"status": 500,
"description": "内部服务器错误"
}
}
验证错误详情
字段级验证
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求验证失败",
"details": [
{
"field": "credit_card.number",
"code": "INVALID_FORMAT",
"message": "信用卡号必须为 16 位数字",
"value_provided": "1234",
"constraints": {
"pattern": "^[0-9]{16}$"
}
},
{
"field": "items[0].quantity",
"code": "OUT_OF_RANGE",
"message": "数量必须至少为 1",
"value_provided": 0,
"constraints": {
"min": 1,
"max": 1000
}
}
]
}
}
跨字段验证
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求验证失败",
"details": [
{
"fields": ["start_date", "end_date"],
"code": "INVALID_RANGE",
"message": "结束日期必须在开始日期之后",
"values_provided": {
"start_date": "2024-01-20",
"end_date": "2024-01-15"
}
}
]
}
}
请求 ID 追踪
始终包含请求 ID 以便调试:
Response Headers:
X-Request-ID: req_abc123
Response Body:
{
"error": {
"code": "INTERNAL_SERVER_ERROR",
"message": "发生意外错误",
"request_id": "req_abc123"
}
}
客户端可在工单中引用请求 ID。
错误文档
记录每个端点所有可能的错误:
/users/{id}:
get:
responses:
'200':
description: 成功
'401':
description: 身份认证失败
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
missing_token:
value:
error:
code: MISSING_TOKEN
message: 未提供认证令牌
invalid_token:
value:
error:
code: INVALID_TOKEN
message: 令牌无效或已过期
'404':
description: 用户未找到
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
not_found:
value:
error:
code: RESOURCE_NOT_FOUND
message: 未找到 ID 为 123 的用户
重试指导
帮助客户端判断是否应该重试:
{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "服务暂时不可用",
"retry": {
"retryable": true,
"retry_after": 60,
"max_retries": 3,
"backoff": "exponential"
}
}
}
可重试的错误
- 408 Request Timeout
- 429 Too Many Requests(带 Retry-After)
- 500 Internal Server Error(部分情况)
- 502 Bad Gateway
- 503 Service Unavailable
- 504 Gateway Timeout
不可重试的错误
- 400 Bad Request
- 401 Unauthorized
- 403 Forbidden
- 404 Not Found
- 409 Conflict
- 422 Unprocessable Entity
多语言支持
支持多语言错误消息:
GET /users/invalid
Accept-Language: es
Response: 404 Not Found
Content-Language: es
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Usuario con ID 'invalid' no encontrado"
}
}
始终包含 code 字段,以便客户端实现自己的翻译。
最佳实践
- 使用标准 HTTP 状态码 — 不要在出错时返回 200
- 包含机器可读的错误码 — 供客户端逻辑使用的错误码
- 提供人类可读的消息 — 清晰的解释说明
- 具体但安全 — 不要暴露敏感信息
- 包含请求 ID — 用于追踪和调试
- 记录所有错误 — 每个端点每种可能的错误
- 保持一致性 — 所有端点使用相同格式
- 帮助客户端重试 — 指明错误是否可重试
- 尽早验证 — 立即返回验证错误
- 在服务端记录错误 — 追踪错误以进行监控
反模式
避免以下错误做法:
- 通用的错误消息 — 没有详细信息的"发生错误"
- 暴露堆栈跟踪 — 安全风险
- 不一致的错误格式 — 不同端点使用不同结构
- 缺少错误码 — 只有人类可读的消息
- 错误的状态码 — 在响应体中返回错误时状态码为 200
- 没有请求 ID — 导致无法调试
- 未记录的错误 — 客户端不知道会遇到什么
- 信息过多 — 暴露内部实现细节