# API 错误处理 ## 错误响应设计 一致且信息丰富的错误响应对于 API 的可用性至关重要。 ## 标准错误格式 ### 基础错误响应 ```json { "error": { "code": "RESOURCE_NOT_FOUND", "message": "未找到 ID 为 123 的用户", "details": null } } ``` ### RFC 7807 问题详情 标准化错误格式 (application/problem+json): ```http 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 引用 ### 扩展错误响应 ```json { "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) 客户端发送了无效数据。 ```http 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) 缺少或提供了无效的身份认证凭据。 ```http 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) 已通过身份认证但无权执行操作。 ```http 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) 资源不存在。 ```http 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) 请求与当前状态冲突。 ```http 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) 客户端超出了速率限制。 ```http 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) 意外的服务器错误。 ```http 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) 服务暂时不可用。 ```http 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 定义标准错误码: ```json { "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": "内部服务器错误" } } ``` ## 验证错误详情 ### 字段级验证 ```json { "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 } } ] } } ``` ### 跨字段验证 ```json { "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 以便调试: ```http Response Headers: X-Request-ID: req_abc123 Response Body: { "error": { "code": "INTERNAL_SERVER_ERROR", "message": "发生意外错误", "request_id": "req_abc123" } } ``` 客户端可在工单中引用请求 ID。 ## 错误文档 记录每个端点所有可能的错误: ```yaml /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 的用户 ``` ## 重试指导 帮助客户端判断是否应该重试: ```json { "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 ## 多语言支持 支持多语言错误消息: ```http 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` 字段,以便客户端实现自己的翻译。 ## 最佳实践 1. **使用标准 HTTP 状态码** — 不要在出错时返回 200 2. **包含机器可读的错误码** — 供客户端逻辑使用的错误码 3. **提供人类可读的消息** — 清晰的解释说明 4. **具体但安全** — 不要暴露敏感信息 5. **包含请求 ID** — 用于追踪和调试 6. **记录所有错误** — 每个端点每种可能的错误 7. **保持一致性** — 所有端点使用相同格式 8. **帮助客户端重试** — 指明错误是否可重试 9. **尽早验证** — 立即返回验证错误 10. **在服务端记录错误** — 追踪错误以进行监控 ## 反模式 避免以下错误做法: - **通用的错误消息** — 没有详细信息的"发生错误" - **暴露堆栈跟踪** — 安全风险 - **不一致的错误格式** — 不同端点使用不同结构 - **缺少错误码** — 只有人类可读的消息 - **错误的状态码** — 在响应体中返回错误时状态码为 200 - **没有请求 ID** — 导致无法调试 - **未记录的错误** — 客户端不知道会遇到什么 - **信息过多** — 暴露内部实现细节