Files
huang fb83c9a706 feat: 实现统一错误处理系统 (003-error-handling)
- 新增统一错误码定义和管理 (pkg/errors/codes.go)
- 新增全局错误处理器和中间件 (pkg/errors/handler.go, internal/middleware/error_handler.go)
- 新增错误上下文管理 (pkg/errors/context.go)
- 增强 Panic 恢复中间件 (internal/middleware/recover.go)
- 新增完整的单元测试和集成测试
- 新增功能文档 (docs/003-error-handling/)
- 新增功能规范 (specs/003-error-handling/)
- 更新 CLAUDE.md 和 README.md
2025-11-15 12:17:44 +08:00

490 lines
13 KiB
YAML

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"