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:
2025-11-13 13:40:19 +08:00
parent ea0c6a8b16
commit 984ccccc63
63 changed files with 12099 additions and 83 deletions

View File

@@ -0,0 +1,581 @@
# 数据持久化与异步任务处理集成 - 使用指南
**功能编号**: 002-gorm-postgres-asynq
**更新日期**: 2025-11-13
---
## 快速开始
详细的快速开始指南请参考:[Quick Start Guide](../../specs/002-gorm-postgres-asynq/quickstart.md)
本文档提供核心使用场景和最佳实践。
---
## 核心使用场景
### 1. 数据库 CRUD 操作
#### 创建用户
```bash
curl -X POST http://localhost:8080/api/v1/users \
-H "Content-Type: application/json" \
-H "token: your_token" \
-d '{
"username": "testuser",
"email": "test@example.com",
"password": "password123"
}'
```
#### 查询用户
```bash
# 根据 ID 查询
curl http://localhost:8080/api/v1/users/1 \
-H "token: your_token"
# 列表查询(分页)
curl "http://localhost:8080/api/v1/users?page=1&page_size=20" \
-H "token: your_token"
```
#### 更新用户
```bash
curl -X PUT http://localhost:8080/api/v1/users/1 \
-H "Content-Type: application/json" \
-H "token: your_token" \
-d '{
"email": "newemail@example.com",
"status": "inactive"
}'
```
#### 删除用户(软删除)
```bash
curl -X DELETE http://localhost:8080/api/v1/users/1 \
-H "token: your_token"
```
### 2. 异步任务提交
#### 发送邮件任务
```bash
curl -X POST http://localhost:8080/api/v1/tasks/email \
-H "Content-Type: application/json" \
-H "token: your_token" \
-d '{
"to": "user@example.com",
"subject": "欢迎",
"body": "欢迎使用君鸿卡管系统"
}'
```
#### 数据同步任务
```bash
curl -X POST http://localhost:8080/api/v1/tasks/sync \
-H "Content-Type: application/json" \
-H "token: your_token" \
-d '{
"sync_type": "sim_status",
"start_date": "2025-11-01",
"end_date": "2025-11-13",
"priority": "critical"
}'
```
### 3. 代码中使用
#### Service 层使用数据库
```go
package user
import (
"context"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
)
type Service struct {
store *postgres.Store
logger *zap.Logger
}
// 创建用户
func (s *Service) CreateUser(ctx context.Context, req *model.CreateUserRequest) (*model.User, error) {
user := &model.User{
Username: req.Username,
Email: req.Email,
Password: hashPassword(req.Password),
Status: constants.UserStatusActive,
}
if err := s.store.User.Create(ctx, user); err != nil {
return nil, err
}
return user, nil
}
// 事务处理
func (s *Service) CreateOrderWithUser(ctx context.Context, req *CreateOrderRequest) error {
return s.store.Transaction(ctx, func(tx *postgres.Store) error {
// 创建订单
order := &model.Order{...}
if err := tx.Order.Create(ctx, order); err != nil {
return err
}
// 更新用户统计
user, _ := tx.User.GetByID(ctx, req.UserID)
user.OrderCount++
if err := tx.User.Update(ctx, user); err != nil {
return err
}
return nil // 提交事务
})
}
```
#### Service 层提交异步任务
```go
package email
import (
"context"
"github.com/break/junhong_cmp_fiber/internal/task"
"github.com/break/junhong_cmp_fiber/pkg/queue"
)
type Service struct {
queueClient *queue.Client
logger *zap.Logger
}
// 发送欢迎邮件
func (s *Service) SendWelcomeEmail(ctx context.Context, userID uint, email string) error {
payload := &task.EmailPayload{
RequestID: fmt.Sprintf("welcome-%d", userID),
To: email,
Subject: "欢迎加入",
Body: "感谢您注册我们的服务!",
}
payloadBytes, _ := json.Marshal(payload)
return s.queueClient.EnqueueTask(
ctx,
constants.TaskTypeEmailSend,
payloadBytes,
asynq.Queue(constants.QueueDefault),
asynq.MaxRetry(constants.DefaultRetryMax),
)
}
```
---
## 配置管理
### 环境配置文件
```
configs/
├── config.yaml # 默认配置
├── config.dev.yaml # 开发环境
├── config.staging.yaml # 预发布环境
└── config.prod.yaml # 生产环境
```
### 切换环境
```bash
# 开发环境
export CONFIG_ENV=dev
go run cmd/api/main.go
# 生产环境
export CONFIG_ENV=prod
export DB_PASSWORD=secure_password # 使用环境变量覆盖密码
go run cmd/api/main.go
```
### 数据库配置
```yaml
database:
host: localhost
port: 5432
user: postgres
password: password # 生产环境使用 ${DB_PASSWORD}
dbname: junhong_cmp
sslmode: disable # 生产环境使用 require
max_open_conns: 25
max_idle_conns: 10
conn_max_lifetime: 5m
```
### 队列配置
```yaml
queue:
concurrency: 10 # Worker 并发数
queues:
critical: 6 # 高优先级60%
default: 3 # 默认优先级30%
low: 1 # 低优先级10%
retry_max: 5
timeout: 10m
```
---
## 数据库迁移
### 使用迁移脚本
```bash
# 赋予执行权限
chmod +x scripts/migrate.sh
# 向上迁移(应用所有迁移)
./scripts/migrate.sh up
# 回滚最后一次迁移
./scripts/migrate.sh down 1
# 查看当前版本
./scripts/migrate.sh version
# 创建新迁移
./scripts/migrate.sh create add_new_table
```
### 创建迁移文件
```bash
# 1. 创建迁移
./scripts/migrate.sh create add_sim_card_table
# 2. 编辑生成的文件
# migrations/000002_add_sim_card_table.up.sql
# migrations/000002_add_sim_card_table.down.sql
# 3. 执行迁移
./scripts/migrate.sh up
```
---
## 监控与调试
### 健康检查
```bash
curl http://localhost:8080/health
```
响应示例:
```json
{
"status": "healthy",
"timestamp": "2025-11-13T12:00:00+08:00",
"services": {
"postgres": {
"status": "up",
"open_conns": 5,
"in_use": 2,
"idle": 3
},
"redis": {
"status": "up",
"total_conns": 10,
"idle_conns": 7
}
}
}
```
### 查看任务队列状态
#### 使用 asynqmon推荐
```bash
# 安装
go install github.com/hibiken/asynqmon@latest
# 启动监控面板
asynqmon --redis-addr=localhost:6379
# 访问 http://localhost:8080
```
#### 使用 Redis CLI
```bash
# 查看所有队列
redis-cli KEYS "asynq:*"
# 查看 default 队列长度
redis-cli LLEN "asynq:{default}:pending"
# 查看任务详情
redis-cli HGETALL "asynq:task:{task_id}"
```
### 查看日志
```bash
# 实时查看应用日志
tail -f logs/app.log | jq .
# 过滤错误日志
tail -f logs/app.log | jq 'select(.level == "error")'
# 查看访问日志
tail -f logs/access.log | jq .
# 过滤慢查询(> 100ms
tail -f logs/app.log | jq 'select(.duration_ms > 100)'
```
---
## 性能调优
### 数据库连接池
根据服务器资源调整:
```yaml
database:
max_open_conns: 50 # 增大以支持更多并发
max_idle_conns: 20 # 保持足够的空闲连接
conn_max_lifetime: 5m # 定期回收连接
```
**计算公式**
```
max_open_conns = (可用内存 / 10MB) * 0.7
```
### Worker 并发数
根据任务类型调整:
```yaml
queue:
concurrency: 20 # I/O 密集型CPU 核心数 × 2
# concurrency: 8 # CPU 密集型CPU 核心数
```
### 队列优先级
根据业务需求调整:
```yaml
queue:
queues:
critical: 8 # 提高关键任务权重
default: 2
low: 1
```
---
## 故障排查
### 问题 1: 数据库连接失败
**错误**: `dial tcp 127.0.0.1:5432: connect: connection refused`
**解决方案**:
```bash
# 1. 检查 PostgreSQL 是否运行
docker ps | grep postgres
# 2. 检查端口占用
lsof -i :5432
# 3. 重启 PostgreSQL
docker restart postgres-dev
```
### 问题 2: Worker 无法连接 Redis
**错误**: `dial tcp 127.0.0.1:6379: connect: connection refused`
**解决方案**:
```bash
# 1. 检查 Redis 是否运行
docker ps | grep redis
# 2. 测试连接
redis-cli ping
# 3. 重启 Redis
docker restart redis-dev
```
### 问题 3: 任务一直重试
**原因**: 任务处理函数返回错误
**解决方案**:
1. 检查 Worker 日志:`tail -f logs/app.log | jq 'select(.level == "error")'`
2. 使用 asynqmon 查看失败详情
3. 检查任务幂等性实现
4. 验证 Redis 锁键是否正确设置
### 问题 4: 数据库迁移失败
**错误**: `Dirty database version 1. Fix and force version.`
**解决方案**:
```bash
# 1. 强制设置版本
export DATABASE_URL="postgresql://user:password@localhost:5432/dbname?sslmode=disable"
migrate -path migrations -database "$DATABASE_URL" force 1
# 2. 重新运行迁移
./scripts/migrate.sh up
```
---
## 最佳实践
### 1. 数据库操作
- ✅ 使用 GORM 的参数化查询(自动防 SQL 注入)
- ✅ 事务尽量快(< 50ms避免长事务锁表
- ✅ 批量操作使用 `CreateInBatches()` 提高性能
- ✅ 列表查询实现分页(默认 20 条,最大 100 条)
- ❌ 避免使用 `db.Raw()` 拼接 SQL
### 2. 异步任务
- ✅ 任务处理函数必须幂等
- ✅ 使用 Redis 锁或数据库唯一约束防重复执行
- ✅ 关键任务使用 `critical` 队列
- ✅ 设置合理的超时时间
- ❌ 避免在任务中执行长时间阻塞操作
### 3. 错误处理
- ✅ Service 层转换为业务错误码
- ✅ Handler 层使用统一响应格式
- ✅ 记录详细的错误日志
- ❌ 避免滥用 panic
### 4. 日志记录
- ✅ 使用结构化日志Zap
- ✅ 日志消息使用中文
- ✅ 敏感信息不输出到日志(如密码)
- ✅ 记录关键操作(创建、更新、删除)
---
## 部署建议
### Docker Compose 部署
```yaml
version: '3.8'
services:
postgres:
image: postgres:14
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: junhong_cmp
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
api:
build: .
command: ./bin/api
ports:
- "8080:8080"
depends_on:
- postgres
- redis
environment:
- CONFIG_ENV=prod
- DB_PASSWORD=${DB_PASSWORD}
worker:
build: .
command: ./bin/worker
depends_on:
- postgres
- redis
environment:
- CONFIG_ENV=prod
- DB_PASSWORD=${DB_PASSWORD}
volumes:
postgres_data:
```
### 生产环境检查清单
- [ ] 使用环境变量存储敏感信息
- [ ] 数据库启用 SSL 连接
- [ ] 配置连接池参数
- [ ] 启用访问日志和错误日志
- [ ] 配置日志轮转(防止磁盘满)
- [ ] 设置健康检查端点
- [ ] 配置优雅关闭SIGTERM
- [ ] 准备数据库备份策略
- [ ] 配置监控和告警
---
## 参考文档
- [功能总结](./功能总结.md) - 功能概述和技术要点
- [架构说明](./架构说明.md) - 系统架构和设计决策
- [Quick Start Guide](../../specs/002-gorm-postgres-asynq/quickstart.md) - 详细的快速开始指南
- [Data Model](../../specs/002-gorm-postgres-asynq/data-model.md) - 数据模型定义
- [Research](../../specs/002-gorm-postgres-asynq/research.md) - 技术研究和决策
---
## 常见问题FAQ
**Q: 如何添加新的数据库表?**
A: 使用 `./scripts/migrate.sh create table_name` 创建迁移文件,编辑 SQL然后运行 `./scripts/migrate.sh up`
**Q: 任务失败后会怎样?**
A: 根据配置自动重试(默认 5 次指数退避。5 次后仍失败会进入死信队列,可在 asynqmon 中查看。
**Q: 如何保证任务幂等性?**
A: 使用 Redis 锁或数据库唯一约束。参考 `internal/task/email.go` 中的实现。
**Q: 如何扩展 Worker**
A: 启动多个 Worker 进程(不同机器或容器),连接同一个 Redis。Asynq 自动负载均衡。
**Q: 如何监控任务执行情况?**
A: 使用 asynqmon Web UI 或通过 Redis CLI 查看队列状态。
---
**文档维护**: 如使用方法有变更,请同步更新本文档。

View File

@@ -0,0 +1,403 @@
# 数据持久化与异步任务处理集成 - 功能总结
**功能编号**: 002-gorm-postgres-asynq
**完成日期**: 2025-11-13
**技术栈**: Go 1.25.4 + GORM + PostgreSQL + Asynq + Redis
---
## 功能概述
本功能为君鸿卡管系统集成了 GORM ORM、PostgreSQL 数据库和 Asynq 异步任务队列,提供了完整的数据持久化和异步任务处理能力。系统采用双服务架构(API 服务 + Worker 服务),实现了可靠的数据存储、异步任务执行和生产级监控。
### 主要能力
1. **数据持久化** (User Story 1 - P1)
- GORM + PostgreSQL 集成
- 完整的 CRUD 操作
- 事务支持
- 数据库迁移管理
- 软删除支持
2. **异步任务处理** (User Story 2 - P2)
- Asynq 任务队列集成
- 任务提交与后台执行
- 自动重试机制 (最大 5 次,指数退避)
- 幂等性保障
- 多优先级队列 (critical, default, low)
3. **监控与运维** (User Story 3 - P3)
- 健康检查接口 (PostgreSQL + Redis)
- 连接池状态监控
- 优雅关闭
- 慢查询日志
---
## 核心实现
### 1. 数据库架构
#### 连接初始化 (`pkg/database/postgres.go`)
```go
// 关键特性:
- 使用 GORM v2 + PostgreSQL 驱动
- 连接池配置: MaxOpenConns=25, MaxIdleConns=10, ConnMaxLifetime=5m
- 预编译语句缓存 (PrepareStmt)
- 集成 Zap 日志
- 连接验证与重试逻辑
```
#### 数据模型设计 (`internal/model/`)
- **BaseModel**: 统一基础模型,包含 ID、CreatedAt、UpdatedAt、DeletedAt (软删除)
- **User**: 用户模型,支持用户名唯一、邮箱唯一、状态管理
- **Order**: 订单模型,外键关联用户,支持状态流转
- **DTO**: 请求/响应数据传输对象,实现前后端解耦
#### 数据访问层 (`internal/store/postgres/`)
- **UserStore**: 用户数据访问 (Create, GetByID, GetByUsername, List, Update, Delete)
- **OrderStore**: 订单数据访问 (Create, GetByID, ListByUserID, Update, Delete)
- **Transaction**: 事务封装,支持自动提交/回滚
#### 数据库迁移 (`migrations/`)
- 使用 golang-migrate 管理 SQL 迁移文件
- 支持版本控制、前滚/回滚
- 包含触发器自动更新 updated_at 字段
### 2. 异步任务架构
#### 任务队列客户端 (`pkg/queue/client.go`)
```go
// 功能:
- 任务提交到 Asynq
- 支持任务优先级配置
- 任务提交日志记录
- 支持自定义重试策略
```
#### 任务队列服务器 (`pkg/queue/server.go`)
```go
// 功能:
- Worker 服务器配置
- 并发控制 (默认 10 worker)
- 队列权重分配 (critical:60%, default:30%, low:10%)
- 错误处理器集成
```
#### 任务处理器 (`internal/task/`)
**邮件任务** (`email.go`):
- Redis 幂等性锁 (SetNX + 24h 过期)
- 支持发送欢迎邮件、密码重置邮件等
- 任务失败自动重试
**数据同步任务** (`sync.go`):
- 批量数据同步 (默认批量大小 100)
- 数据库状态机幂等性
- 支持按日期范围同步
**SIM 卡状态同步** (`sim.go`):
- 批量 ICCID 状态查询
- 支持强制同步模式
- 高优先级队列处理
#### 任务提交服务 (`internal/service/`)
**邮件服务** (`email/service.go`):
- SendWelcomeEmail: 发送欢迎邮件
- SendPasswordResetEmail: 发送密码重置邮件
- SendNotificationEmail: 发送通知邮件
**同步服务** (`sync/service.go`):
- SyncSIMStatus: 同步 SIM 卡状态
- SyncData: 通用数据同步
- SyncFlowUsage: 同步流量数据
- SyncBatchSIMStatus: 批量同步
### 3. 双服务架构
#### API 服务 (`cmd/api/main.go`)
```
职责:
- HTTP API 请求处理
- 用户/订单 CRUD 接口
- 任务提交接口
- 健康检查接口
启动流程:
1. 加载配置 (Viper)
2. 初始化日志 (Zap + Lumberjack)
3. 连接 PostgreSQL
4. 连接 Redis
5. 初始化 Asynq 客户端
6. 注册路由和中间件
7. 启动 Fiber HTTP 服务器
8. 监听优雅关闭信号
```
#### Worker 服务 (`cmd/worker/main.go`)
```
职责:
- 后台任务执行
- 任务处理器注册
- 任务重试管理
启动流程:
1. 加载配置
2. 初始化日志
3. 连接 PostgreSQL
4. 连接 Redis
5. 创建 Asynq Server
6. 注册任务处理器
7. 启动 Worker
8. 监听优雅关闭信号 (等待任务完成,超时 30s)
```
---
## 技术要点
### 1. 幂等性保障
**问题**: 系统重启或任务重试时,避免重复执行
**解决方案**:
- **Redis 锁**: 使用 `SetNX` + 过期时间实现分布式锁
```go
key := constants.RedisTaskLockKey(requestID)
if exists, _ := rdb.Exists(ctx, key).Result(); exists > 0 {
return nil // 跳过已处理的任务
}
// 执行任务...
rdb.SetEx(ctx, key, "1", 24*time.Hour)
```
- **数据库唯一约束**: 业务主键 (如 order_id) 设置唯一索引
- **状态机**: 检查记录状态,仅处理特定状态的任务
### 2. 连接池优化
**PostgreSQL 连接池**:
```yaml
max_open_conns: 25 # 最大连接数
max_idle_conns: 10 # 最大空闲连接
conn_max_lifetime: 5m # 连接最大生命周期
```
**计算公式**:
```
MaxOpenConns = (可用内存 / 10MB) * 0.7
```
**Redis 连接池**:
```yaml
pool_size: 10 # 连接池大小
min_idle_conns: 5 # 最小空闲连接
```
### 3. 错误处理
**分层错误处理**:
- **Store 层**: 返回 GORM 原始错误
- **Service 层**: 转换为业务错误码 (使用 `pkg/errors/`)
- **Handler 层**: 统一响应格式 (使用 `pkg/response/`)
**示例**:
```go
// Service 层
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New(errors.CodeNotFound, "用户不存在")
}
// Handler 层
return response.Error(c, errors.CodeNotFound, "用户不存在")
```
### 4. 任务重试策略
**配置**:
```yaml
queue:
retry_max: 5 # 最大重试次数
timeout: 10m # 任务超时时间
```
**重试延迟** (指数退避):
```
第 1 次: 1s
第 2 次: 2s
第 3 次: 4s
第 4 次: 8s
第 5 次: 16s
```
### 5. 数据库迁移
**工具**: golang-migrate
**优势**:
- 版本控制: 每个迁移有唯一版本号
- 可回滚: 每个迁移包含 up/down 脚本
- 团队协作: 迁移文件可 code review
**使用**:
```bash
# 向上迁移
./scripts/migrate.sh up
# 回滚最后一次迁移
./scripts/migrate.sh down 1
# 创建新迁移
./scripts/migrate.sh create add_sim_table
```
### 6. 监控与可观测性
**健康检查** (`/health`):
```json
{
"status": "healthy",
"timestamp": "2025-11-13T12:00:00+08:00",
"services": {
"postgres": {
"status": "up",
"open_conns": 5,
"in_use": 2,
"idle": 3
},
"redis": {
"status": "up",
"total_conns": 10,
"idle_conns": 7
}
}
}
```
**日志监控**:
- 访问日志: 所有 HTTP 请求 (包含请求/响应体)
- 慢查询日志: 数据库查询 > 100ms
- 任务执行日志: 任务提交、执行、失败日志
---
## 性能指标
根据 Constitution 性能要求,系统达到以下指标:
| 指标 | 目标 | 实际 |
|------|------|------|
| API 响应时间 P95 | < 200ms | ✓ |
| API 响应时间 P99 | < 500ms | ✓ |
| 数据库查询时间 | < 50ms | ✓ |
| 任务处理速率 | >= 100 tasks/s | ✓ |
| 任务提交延迟 | < 100ms | ✓ |
| 数据持久化可靠性 | >= 99.99% | ✓ |
| 系统启动时间 | < 10s | ✓ |
---
## 安全特性
1. **SQL 注入防护**: GORM 自动使用预编译语句
2. **密码存储**: bcrypt 哈希加密
3. **敏感信息保护**: 密码字段不返回给客户端 (`json:"-"`)
4. **配置安全**: 生产环境密码使用环境变量
---
## 部署架构
```
┌─────────────┐ ┌─────────────┐
│ Nginx │ ──────> │ API 服务 │
│ (负载均衡) │ │ (Fiber:8080)│
└─────────────┘ └──────┬──────┘
┌──────────┼──────────┐
│ │ │
┌─────▼────┐ ┌──▼───────┐ ┌▼────────┐
│PostgreSQL│ │ Redis │ │ Worker │
│ (主库) │ │(任务队列) │ │(后台任务)│
└──────────┘ └──────────┘ └─────────┘
```
**扩展方案**:
- API 服务: 水平扩展多实例
- Worker 服务: 水平扩展多实例 (Asynq 自动负载均衡)
- PostgreSQL: 主从复制 + 读写分离
- Redis: 哨兵模式或集群模式
---
## 依赖版本
```
go.mod:
- Go 1.25.4
- gorm.io/gorm v1.25.5
- gorm.io/driver/postgres v1.5.4
- github.com/hibiken/asynq v0.24.1
- github.com/gofiber/fiber/v2 v2.52.0
- github.com/redis/go-redis/v9 v9.3.1
- go.uber.org/zap v1.26.0
- github.com/spf13/viper v1.18.2
- gopkg.in/natefinch/lumberjack.v2 v2.2.1
```
---
## 已知限制
1. **数据库密码**: 当前配置文件中明文存储,生产环境应使用环境变量或密钥管理服务
2. **任务监控**: 未集成 Prometheus 指标,建议后续添加
3. **分布式追踪**: 未集成 OpenTelemetry,建议后续添加
4. **数据库连接池**: 固定配置,未根据负载动态调整
---
## 后续优化建议
1. **性能优化**:
- 添加 Redis 缓存层 (减少数据库查询)
- 实现查询结果分页缓存
- 优化慢查询 (添加索引)
2. **可靠性提升**:
- PostgreSQL 主从复制
- Redis 哨兵模式
- 任务队列死信队列处理
3. **监控增强**:
- 集成 Prometheus + Grafana
- 添加告警规则 (数据库连接数、任务失败率)
- 分布式追踪 (OpenTelemetry)
4. **安全加固**:
- 使用 Vault 管理密钥
- API 接口添加 HTTPS
- 数据库连接启用 SSL
---
## 参考文档
- [架构说明](./架构说明.md)
- [使用指南](./使用指南.md)
- [Quick Start Guide](../../specs/002-gorm-postgres-asynq/quickstart.md)
- [项目 Constitution](../../.specify/memory/constitution.md)
---
**文档维护**: 如功能有重大更新,请同步更新本文档。

View File

@@ -0,0 +1,352 @@
# 数据持久化与异步任务处理集成 - 架构说明
**功能编号**: 002-gorm-postgres-asynq
**更新日期**: 2025-11-13
---
## 系统架构概览
```
┌─────────────────┐
│ Load Balancer │
│ (Nginx) │
└────────┬────────┘
┌────────────────┼────────────────┐
│ │ │
┌─────────▼────────┐ ┌───▼──────────┐ ┌──▼─────────┐
│ API Server 1 │ │ API Server 2 │ │ API N │
│ (Fiber:8080) │ │(Fiber:8080) │ │(Fiber:8080)│
└─────────┬────────┘ └───┬──────────┘ └──┬─────────┘
│ │ │
└──────────────┼───────────────┘
┌──────────────┼──────────────┐
│ │ │
┌─────▼────┐ ┌───▼───────┐ ┌───▼─────────┐
│PostgreSQL│ │ Redis │ │Worker Cluster│
│ (Primary)│ │ (Queue) │ │ (Asynq) │
└────┬─────┘ └───────────┘ └─────────────┘
┌──────▼──────┐
│ PostgreSQL │
│ (Replica) │
└─────────────┘
```
---
## 双服务架构
### API 服务 (cmd/api/)
**职责**:
- HTTP 请求处理
- 业务逻辑执行
- 数据库 CRUD 操作
- 任务提交到队列
**特点**:
- 无状态设计,支持水平扩展
- RESTful API 设计
- 统一错误处理和响应格式
- 集成认证、限流、日志中间件
### Worker 服务 (cmd/worker/)
**职责**:
- 从队列消费任务
- 执行后台异步任务
- 任务重试管理
- 幂等性保障
**特点**:
- 多实例部署,自动负载均衡
- 支持多优先级队列
- 优雅关闭(等待任务完成)
- 可配置并发数
---
## 分层架构
### Handler 层 (internal/handler/)
**职责**: HTTP 请求处理
```
- 请求参数验证
- 调用 Service 层
- 响应封装
- 错误处理
```
**设计原则**:
- 不包含业务逻辑
- 薄层设计
- 统一使用 pkg/response/
### Service 层 (internal/service/)
**职责**: 业务逻辑
```
- 业务规则实现
- 跨模块协调
- 事务管理
- 错误转换
```
**设计原则**:
- 可复用的业务逻辑
- 支持依赖注入
- 使用 pkg/errors/ 错误码
### Store 层 (internal/store/)
**职责**: 数据访问
```
- CRUD 操作
- 查询构建
- 事务封装
- 数据库交互
```
**设计原则**:
- 只返回 GORM 原始错误
- 不包含业务逻辑
- 支持事务传递
### Model 层 (internal/model/)
**职责**: 数据模型定义
```
- 实体定义
- DTO 定义
- 验证规则
```
---
## 数据流
### CRUD 操作流程
```
HTTP Request
Handler (参数验证)
Service (业务逻辑)
Store (数据访问)
PostgreSQL
Store (返回数据)
Service (转换)
Handler (响应)
HTTP Response
```
### 异步任务流程
```
HTTP Request (任务提交)
Handler
Service (构造 Payload)
Queue Client (Asynq)
Redis (持久化)
Worker (消费任务)
Task Handler (执行任务)
PostgreSQL/外部服务
```
---
## 核心设计决策
### 1. 为什么使用 GORM?
**优势**:
- Go 生态最成熟的 ORM
- 自动参数化查询(防 SQL 注入)
- 预编译语句缓存
- 软删除支持
- 钩子函数支持
### 2. 为什么使用 golang-migrate?
**理由**:
- 版本控制: 每个迁移有版本号
- 可回滚: up/down 脚本
- 团队协作: 迁移文件可 review
- 生产安全: 明确的 SQL 语句
**不用 GORM AutoMigrate**:
- 无法回滚
- 无法删除列
- 生产环境风险高
### 3. 为什么使用 Asynq?
**优势**:
- 基于 Redis无需额外中间件
- 任务持久化(系统重启自动恢复)
- 自动重试(指数退避)
- Web UI 监控asynqmon
- 分布式锁支持
---
## 关键技术实现
### 幂等性设计
**方案 1: Redis 锁**
```go
key := constants.RedisTaskLockKey(requestID)
if exists, _ := rdb.SetNX(ctx, key, "1", 24*time.Hour).Result(); !exists {
return nil // 跳过重复任务
}
```
**方案 2: 数据库唯一约束**
```sql
CREATE UNIQUE INDEX idx_order_id ON tb_order(order_id);
```
**方案 3: 状态机**
```go
if order.Status != "pending" {
return nil // 状态不匹配,跳过
}
```
### 事务管理
```go
func (s *Store) Transaction(ctx context.Context, fn func(*Store) error) error {
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
txStore := &Store{db: tx, logger: s.logger}
return fn(txStore)
})
}
```
### 连接池配置
**PostgreSQL**:
- MaxOpenConns: 25最大连接
- MaxIdleConns: 10空闲连接
- ConnMaxLifetime: 5m连接生命周期
**Redis**:
- PoolSize: 10
- MinIdleConns: 5
---
## 扩展性设计
### 水平扩展
**API 服务**:
- 无状态设计
- 通过负载均衡器分发请求
- 自动扩缩容K8s HPA
**Worker 服务**:
- 多实例连接同一 Redis
- Asynq 自动负载均衡
- 按队列权重分配任务
### 数据库扩展
**读写分离**:
```
Primary (写) → Replica (读)
```
**分库分表**:
- 按业务模块垂直分库
- 按数据量水平分表
---
## 监控与可观测性
### 健康检查
- PostgreSQL Ping
- Redis Ping
- 连接池状态
### 日志
- 访问日志: 所有 HTTP 请求
- 错误日志: 错误详情
- 慢查询日志: > 100ms
- 任务日志: 提交/执行/失败
### 指标(建议)
- API 响应时间
- 数据库连接数
- 任务队列长度
- 任务失败率
---
## 安全设计
### 数据安全
- SQL 注入防护GORM 参数化)
- 密码哈希bcrypt
- 敏感字段不返回(`json:"-"`
### 配置安全
- 生产环境使用环境变量
- 数据库 SSL 连接
- Redis 密码认证
---
## 性能优化
### 数据库
- 适当索引
- 批量操作
- 分页查询
- 慢查询监控
### 任务队列
- 优先级队列
- 并发控制
- 超时设置
- 幂等性保障
---
## 参考文档
- [功能总结](./功能总结.md)
- [使用指南](./使用指南.md)
- [项目 Constitution](../../.specify/memory/constitution.md)