openapi: 3.0.3
info:
  title: 数据持久化与异步任务处理集成 API
  description: |
    GORM + PostgreSQL + Asynq 集成的数据持久化和异步任务处理功能 API 规范
    
    **Feature**: 002-gorm-postgres-asynq
    **Date**: 2025-11-12
    
    ## 核心功能
    - 数据库连接管理和健康检查
    - 异步任务提交和管理
    - 数据 CRUD 操作（示例：用户管理）
    
    ## 技术栈
    - Fiber (HTTP 框架)
    - GORM (ORM)
    - PostgreSQL (数据库)
    - Asynq (任务队列)
    - Redis (任务队列存储)
    
  version: 1.0.0
  contact:
    name: API Support
    email: support@example.com

servers:
  - url: http://localhost:8080/api/v1
    description: 开发环境
  - url: http://staging.example.com/api/v1
    description: 预发布环境
  - url: https://api.example.com/api/v1
    description: 生产环境

tags:
  - name: Health
    description: 健康检查和系统状态
  - name: Users
    description: 用户管理（数据库操作示例）
  - name: Tasks
    description: 异步任务管理

paths:
  /health:
    get:
      tags:
        - Health
      summary: 健康检查
      description: |
        检查系统健康状态，包括数据库连接和 Redis 连接
        
        **测试用例**:
        - FR-011: 系统必须提供健康检查接口
        - SC-010: 健康检查应在 1 秒内返回
      operationId: healthCheck
      responses:
        '200':
          description: 系统健康
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [ok]
                    description: 系统整体状态
                  postgres:
                    type: string
                    enum: [up, down]
                    description: PostgreSQL 连接状态
                  redis:
                    type: string
                    enum: [up, down]
                    description: Redis 连接状态
                example:
                  status: ok
                  postgres: up
                  redis: up
        '503':
          description: 服务降级或不可用
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [degraded, unavailable]
                  postgres:
                    type: string
                    enum: [up, down]
                  redis:
                    type: string
                    enum: [up, down]
                  error:
                    type: string
                    description: 错误详情
                example:
                  status: degraded
                  postgres: down
                  redis: up
                  error: "数据库连接失败"

  /users:
    post:
      tags:
        - Users
      summary: 创建用户
      description: |
        创建新用户（演示数据库 CRUD 操作）
        
        **测试用例**:
        - FR-002: 支持标准 CRUD 操作
        - FR-003: 支持数据库事务
        - User Story 1 - Acceptance 1: 数据持久化
      operationId: createUser
      security:
        - TokenAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        '200':
          description: 用户创建成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/UserResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalServerError'
    
    get:
      tags:
        - Users
      summary: 用户列表
      description: |
        分页查询用户列表
        
        **测试用例**:
        - FR-002: 支持分页列表查询
        - FR-005: 支持条件查询、分页、排序
        - User Story 1 - Acceptance 5: 分页和排序
      operationId: listUsers
      security:
        - TokenAuth: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
          description: 页码
        - name: page_size
          in: query
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
          description: 每页条数（最大 100）
        - name: status
          in: query
          schema:
            type: string
            enum: [active, inactive, suspended]
          description: 用户状态过滤
      responses:
        '200':
          description: 查询成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ListUsersResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /users/{id}:
    get:
      tags:
        - Users
      summary: 获取用户详情
      description: |
        根据用户 ID 获取详细信息
        
        **测试用例**:
        - FR-002: 支持按 ID 查询
        - User Story 1 - Acceptance 1: 数据检索
      operationId: getUserById
      security:
        - TokenAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
          description: 用户 ID
      responses:
        '200':
          description: 查询成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/UserResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    
    put:
      tags:
        - Users
      summary: 更新用户
      description: |
        更新用户信息
        
        **测试用例**:
        - FR-002: 支持更新操作
        - User Story 1 - Acceptance 2: 数据更新
      operationId: updateUser
      security:
        - TokenAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
          description: 用户 ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateUserRequest'
      responses:
        '200':
          description: 更新成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/UserResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalServerError'
    
    delete:
      tags:
        - Users
      summary: 删除用户
      description: |
        软删除用户（设置 deleted_at 字段）
        
        **测试用例**:
        - FR-002: 支持软删除操作
        - User Story 1 - Acceptance 3: 数据删除
      operationId: deleteUser
      security:
        - TokenAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
          description: 用户 ID
      responses:
        '200':
          description: 删除成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /tasks/email:
    post:
      tags:
        - Tasks
      summary: 提交邮件发送任务
      description: |
        将邮件发送任务提交到异步队列
        
        **测试用例**:
        - FR-006: 提交任务到异步队列
        - FR-008: 任务重试机制
        - User Story 2 - Acceptance 1: 任务提交
      operationId: submitEmailTask
      security:
        - TokenAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailTaskRequest'
      responses:
        '200':
          description: 任务已提交
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/TaskResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /tasks/sync:
    post:
      tags:
        - Tasks
      summary: 提交数据同步任务
      description: |
        将数据同步任务提交到异步队列（支持优先级）
        
        **测试用例**:
        - FR-006: 提交任务到异步队列
        - FR-009: 任务优先级支持
        - User Story 2 - Acceptance 1: 任务提交
      operationId: submitSyncTask
      security:
        - TokenAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SyncTaskRequest'
      responses:
        '200':
          description: 任务已提交
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/TaskResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

components:
  securitySchemes:
    TokenAuth:
      type: apiKey
      in: header
      name: token
      description: 认证令牌

  schemas:
    # 通用响应
    SuccessResponse:
      type: object
      required:
        - code
        - msg
        - timestamp
      properties:
        code:
          type: integer
          enum: [0]
          description: 响应码（0 表示成功）
        msg:
          type: string
          example: success
          description: 响应消息
        data:
          type: object
          description: 响应数据（具体结构由各端点定义）
        timestamp:
          type: string
          format: date-time
          example: "2025-11-12T16:00:00+08:00"
          description: 响应时间戳（ISO 8601 格式）
    
    ErrorResponse:
      type: object
      required:
        - code
        - msg
        - timestamp
      properties:
        code:
          type: integer
          description: 错误码（非 0）
          example: 1001
        msg:
          type: string
          description: 错误消息（中文）
          example: "参数验证失败"
        data:
          type: object
          nullable: true
          description: 错误详情（可选）
        timestamp:
          type: string
          format: date-time
          example: "2025-11-12T16:00:00+08:00"
    
    # 用户相关
    CreateUserRequest:
      type: object
      required:
        - username
        - email
        - password
      properties:
        username:
          type: string
          minLength: 3
          maxLength: 50
          pattern: '^[a-zA-Z0-9_]+$'
          description: 用户名（3-50 个字母数字下划线）
          example: testuser
        email:
          type: string
          format: email
          maxLength: 100
          description: 邮箱地址
          example: test@example.com
        password:
          type: string
          format: password
          minLength: 8
          description: 密码（至少 8 个字符）
          example: password123
    
    UpdateUserRequest:
      type: object
      properties:
        email:
          type: string
          format: email
          maxLength: 100
          description: 邮箱地址
          example: newemail@example.com
        status:
          type: string
          enum: [active, inactive, suspended]
          description: 用户状态
    
    UserResponse:
      type: object
      required:
        - id
        - username
        - email
        - status
        - created_at
        - updated_at
      properties:
        id:
          type: integer
          description: 用户 ID
          example: 1
        username:
          type: string
          description: 用户名
          example: testuser
        email:
          type: string
          description: 邮箱地址
          example: test@example.com
        status:
          type: string
          enum: [active, inactive, suspended]
          description: 用户状态
          example: active
        created_at:
          type: string
          format: date-time
          description: 创建时间
          example: "2025-11-12T16:00:00+08:00"
        updated_at:
          type: string
          format: date-time
          description: 更新时间
          example: "2025-11-12T16:00:00+08:00"
        last_login_at:
          type: string
          format: date-time
          nullable: true
          description: 最后登录时间
          example: "2025-11-12T16:30:00+08:00"
    
    ListUsersResponse:
      type: object
      required:
        - users
        - page
        - page_size
        - total
        - total_pages
      properties:
        users:
          type: array
          items:
            $ref: '#/components/schemas/UserResponse'
          description: 用户列表
        page:
          type: integer
          description: 当前页码
          example: 1
        page_size:
          type: integer
          description: 每页条数
          example: 20
        total:
          type: integer
          format: int64
          description: 总记录数
          example: 100
        total_pages:
          type: integer
          description: 总页数
          example: 5
    
    # 任务相关
    EmailTaskRequest:
      type: object
      required:
        - to
        - subject
        - body
      properties:
        to:
          type: string
          format: email
          description: 收件人邮箱
          example: user@example.com
        subject:
          type: string
          maxLength: 200
          description: 邮件主题
          example: Welcome to our service
        body:
          type: string
          description: 邮件正文
          example: Thank you for signing up!
        cc:
          type: array
          items:
            type: string
            format: email
          description: 抄送列表
          example: ["manager@example.com"]
        priority:
          type: string
          enum: [critical, default, low]
          default: default
          description: 任务优先级
    
    SyncTaskRequest:
      type: object
      required:
        - sync_type
        - start_date
        - end_date
      properties:
        sync_type:
          type: string
          enum: [sim_status, flow_usage, real_name]
          description: 同步类型
          example: sim_status
        start_date:
          type: string
          format: date
          pattern: '^\d{4}-\d{2}-\d{2}$'
          description: 开始日期（YYYY-MM-DD）
          example: "2025-11-01"
        end_date:
          type: string
          format: date
          pattern: '^\d{4}-\d{2}-\d{2}$'
          description: 结束日期（YYYY-MM-DD）
          example: "2025-11-12"
        batch_size:
          type: integer
          minimum: 1
          maximum: 1000
          default: 100
          description: 批量大小
        priority:
          type: string
          enum: [critical, default, low]
          default: default
          description: 任务优先级
    
    TaskResponse:
      type: object
      required:
        - task_id
        - queue
      properties:
        task_id:
          type: string
          format: uuid
          description: 任务唯一 ID
          example: "550e8400-e29b-41d4-a716-446655440000"
        queue:
          type: string
          enum: [critical, default, low]
          description: 任务所在队列
          example: default
        estimated_time:
          type: string
          description: 预计执行时间
          example: "within 5 minutes"

  responses:
    BadRequest:
      description: 请求参数错误
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: 1001
            msg: "参数验证失败"
            data: null
            timestamp: "2025-11-12T16:00:00+08:00"
    
    Unauthorized:
      description: 未授权或令牌无效
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: 1002
            msg: "缺失认证令牌"
            data: null
            timestamp: "2025-11-12T16:00:00+08:00"
    
    NotFound:
      description: 资源不存在
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: 1003
            msg: "用户不存在"
            data: null
            timestamp: "2025-11-12T16:00:00+08:00"
    
    Conflict:
      description: 资源冲突（如用户名已存在）
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: 1004
            msg: "用户名已存在"
            data: null
            timestamp: "2025-11-12T16:00:00+08:00"
    
    InternalServerError:
      description: 服务器内部错误
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: 5000
            msg: "服务器内部错误"
            data: null
            timestamp: "2025-11-12T16:00:00+08:00"
