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