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"
