# 数据持久化与异步任务处理集成 - 使用指南

**功能编号**: 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 查看队列状态。

---

**文档维护**: 如使用方法有变更，请同步更新本文档。
