openapi: 3.0.3 info: title: 君鸿卡管系统 - 统一错误响应规范 description: | 本文档定义了系统所有 API 端点的统一错误响应格式和错误码。 **关键原则**: - 所有错误响应使用统一的 JSON 格式 - 错误码范围: 1000-1999 (客户端错误), 2000-2999 (服务端错误) - HTTP 状态码与错误码映射一致 - Request ID 仅在响应 Header 中传递 (X-Request-ID) - 敏感信息仅记录到日志,不返回给客户端 version: 1.0.0 contact: name: 君鸿卡管系统开发团队 servers: - url: http://localhost:8080 description: 本地开发环境 - url: https://api.example.com description: 生产环境 components: schemas: ErrorResponse: type: object required: - code - data - msg - timestamp properties: code: type: integer description: | 应用错误码 - 0: 成功 - 1000-1999: 客户端错误 - 2000-2999: 服务端错误 example: 1001 data: type: 'null' description: 错误响应时始终为 null example: null msg: type: string description: 用户友好的错误消息 (中文, 已脱敏) example: "参数验证失败" timestamp: type: string format: date-time description: ISO 8601 格式的时间戳 example: "2025-11-14T16:00:00+08:00" example: code: 1001 data: null msg: "参数验证失败" timestamp: "2025-11-14T16:00:00+08:00" responses: BadRequest: description: 请求参数验证失败 headers: X-Request-ID: schema: type: string format: uuid description: 请求唯一标识符 example: "f1d8b767-dfb3-4588-9fa0-8a97e5337184" content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: InvalidParam: value: code: 1001 data: null msg: "参数验证失败" timestamp: "2025-11-14T16:00:00+08:00" RequestTooLarge: value: code: 1009 data: null msg: "请求体过大" timestamp: "2025-11-14T16:00:00+08:00" Unauthorized: description: 未授权访问 (缺失或无效的认证令牌) headers: X-Request-ID: schema: type: string format: uuid description: 请求唯一标识符 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: MissingToken: value: code: 1002 data: null msg: "缺失认证令牌" timestamp: "2025-11-14T16:00:00+08:00" InvalidToken: value: code: 1003 data: null msg: "无效或过期的令牌" timestamp: "2025-11-14T16:00:00+08:00" Forbidden: description: 禁止访问 (权限不足) headers: X-Request-ID: schema: type: string format: uuid content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: code: 1005 data: null msg: "禁止访问" timestamp: "2025-11-14T16:00:00+08:00" NotFound: description: 资源未找到 headers: X-Request-ID: schema: type: string format: uuid content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: code: 1006 data: null msg: "资源未找到" timestamp: "2025-11-14T16:00:00+08:00" Conflict: description: 资源冲突 (如重复创建) headers: X-Request-ID: schema: type: string format: uuid content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: code: 1007 data: null msg: "资源冲突" timestamp: "2025-11-14T16:00:00+08:00" TooManyRequests: description: 请求过多 (触发限流) headers: X-Request-ID: schema: type: string format: uuid Retry-After: schema: type: integer description: 建议重试的秒数 example: 60 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: code: 1008 data: null msg: "请求过多,请稍后重试" timestamp: "2025-11-14T16:00:00+08:00" InternalServerError: description: 内部服务器错误 (通用服务端错误) headers: X-Request-ID: schema: type: string format: uuid description: 请求唯一标识符 (用于追踪和调试) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: InternalError: value: code: 2001 data: null msg: "内部服务器错误" timestamp: "2025-11-14T16:00:00+08:00" DatabaseError: value: code: 2002 data: null msg: "数据库错误" timestamp: "2025-11-14T16:00:00+08:00" RedisError: value: code: 2003 data: null msg: "缓存服务错误" timestamp: "2025-11-14T16:00:00+08:00" ServiceUnavailable: description: 服务暂时不可用 headers: X-Request-ID: schema: type: string format: uuid Retry-After: schema: type: integer description: 建议重试的秒数 example: 300 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: code: 2004 data: null msg: "服务暂时不可用" timestamp: "2025-11-14T16:00:00+08:00" GatewayTimeout: description: 请求超时 headers: X-Request-ID: schema: type: string format: uuid content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: code: 2005 data: null msg: "请求超时" timestamp: "2025-11-14T16:00:00+08:00" # 错误码完整清单 paths: {} x-error-codes: success: - code: 0 message: "成功" http_status: 200 client_errors: - code: 1001 message: "参数验证失败" http_status: 400 description: "请求参数不符合验证规则" - code: 1002 message: "缺失认证令牌" http_status: 401 description: "请求头中缺少 Authorization 令牌" - code: 1003 message: "无效或过期的令牌" http_status: 401 description: "认证令牌无效或已过期" - code: 1004 message: "未授权访问" http_status: 401 description: "用户未通过认证" - code: 1005 message: "禁止访问" http_status: 403 description: "用户权限不足" - code: 1006 message: "资源未找到" http_status: 404 description: "请求的资源不存在" - code: 1007 message: "资源冲突" http_status: 409 description: "资源已存在或状态冲突" - code: 1008 message: "请求过多,请稍后重试" http_status: 429 description: "触发限流规则" - code: 1009 message: "请求体过大" http_status: 400 description: "请求体大小超过限制" server_errors: - code: 2001 message: "内部服务器错误" http_status: 500 description: "服务器内部发生未预期的错误" - code: 2002 message: "数据库错误" http_status: 500 description: "数据库操作失败 (具体错误仅记录到日志)" - code: 2003 message: "缓存服务错误" http_status: 500 description: "Redis 操作失败 (具体错误仅记录到日志)" - code: 2004 message: "服务暂时不可用" http_status: 503 description: "服务正在维护或过载" - code: 2005 message: "请求超时" http_status: 504 description: "请求处理超时" - code: 2006 message: "任务队列错误" http_status: 500 description: "Asynq 任务队列操作失败" x-security-notes: | ## 敏感信息保护 所有错误响应遵循以下安全原则: 1. **服务端错误 (2xxx)**: 始终返回通用错误消息,不暴露: - 数据库错误详情 (SQL 语句、表结构) - 文件路径或系统路径 - 堆栈跟踪信息 - 配置信息或密钥 2. **客户端错误 (1xxx)**: 可返回具体的业务错误消息,但不包括: - 其他用户的数据 - 系统内部状态 3. **Request ID**: - 仅在响应 Header X-Request-ID 中传递 - 不在响应体中包含 - 用于日志追踪和调试 4. **日志记录**: - 完整的错误详情 (包括堆栈、原始错误) 仅记录到日志 - 日志访问需要运维团队权限 - 敏感字段 (密码、密钥) 不记录到日志 x-error-handling-flow: | ## 错误处理流程 1. **请求处理**: - 中间件或 Handler 返回 error - 错误被 Fiber ErrorHandler 捕获 2. **错误分类**: - *AppError: 提取错误码和消息 - *fiber.Error: 映射 HTTP 状态码 - 其他 error: 默认 500 Internal Server Error 3. **响应检查**: - 如果响应已发送: 仅记录日志,不修改响应 - 如果响应未发送: 生成错误响应 4. **日志记录**: - 记录完整的错误上下文 (Request ID, 路径, 参数, 原始错误) - 客户端错误 (1xxx): Warn 级别 - 服务端错误 (2xxx): Error 级别 5. **响应返回**: - 设置响应 Header: X-Request-ID - 返回统一格式的 JSON 响应体 - HTTP 状态码与错误码映射一致 x-examples: successful_request: summary: 成功请求示例 request: method: GET url: /api/v1/users/123 headers: Authorization: "Bearer valid-token" response: status: 200 headers: X-Request-ID: "f1d8b767-dfb3-4588-9fa0-8a97e5337184" body: code: 0 data: id: "123" username: "testuser" email: "test@example.com" msg: "success" timestamp: "2025-11-14T16:00:00+08:00" client_error_missing_token: summary: 缺失认证令牌 request: method: GET url: /api/v1/users/123 headers: {} response: status: 401 headers: X-Request-ID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" body: code: 1002 data: null msg: "缺失认证令牌" timestamp: "2025-11-14T16:00:00+08:00" client_error_validation: summary: 参数验证失败 request: method: POST url: /api/v1/users headers: Authorization: "Bearer valid-token" body: username: "" email: "invalid-email" response: status: 400 headers: X-Request-ID: "b2c3d4e5-f6a7-8901-bcde-f12345678901" body: code: 1001 data: null msg: "参数验证失败" timestamp: "2025-11-14T16:00:00+08:00" server_error_database: summary: 数据库错误 (敏感信息已隐藏) request: method: GET url: /api/v1/users/123 headers: Authorization: "Bearer valid-token" response: status: 500 headers: X-Request-ID: "c3d4e5f6-a7b8-9012-cdef-123456789012" body: code: 2002 data: null msg: "数据库错误" timestamp: "2025-11-14T16:00:00+08:00" note: | 客户端仅收到通用错误消息 "数据库错误"。 完整的错误详情 (如 "pq: relation 'users' does not exist") 仅记录到服务器日志。 客户端可使用 X-Request-ID 联系技术支持进行排查。 rate_limit_exceeded: summary: 触发限流 request: method: GET url: /api/v1/users headers: Authorization: "Bearer valid-token" response: status: 429 headers: X-Request-ID: "d4e5f6a7-b8c9-0123-def1-234567890123" Retry-After: "60" body: code: 1008 data: null msg: "请求过多,请稍后重试" timestamp: "2025-11-14T16:00:00+08:00"