在项目宪章中新增第九条原则"数据库设计原则",明确禁止使用数据库外键约束和ORM关联标签。 主要变更: - 新增原则IX:数据库设计原则(Database Design Principles) - 强制要求:数据库表不得使用外键约束 - 强制要求:GORM模型不得使用ORM关联标签(foreignKey、hasMany等) - 强制要求:表关系必须通过ID字段手动维护 - 强制要求:关联数据查询必须显式编写,避免ORM魔法 - 强制要求:时间字段由GORM处理,不使用数据库触发器 设计理念: - 提升业务逻辑灵活性(无数据库约束限制) - 优化高并发性能(无外键检查开销) - 增强代码可读性(显式查询,无隐式预加载) - 简化数据库架构和迁移流程 - 支持分布式和微服务场景 版本升级:2.3.0 → 2.4.0(MINOR)
734 lines
19 KiB
YAML
734 lines
19 KiB
YAML
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"
|