# Data Model: 数据持久化与异步任务处理集成 **Feature**: 002-gorm-postgres-asynq **Date**: 2025-11-12 **Purpose**: 定义数据模型、配置结构和系统实体 ## 概述 本文档定义了数据持久化和异步任务处理功能的数据模型,包括配置结构、数据库实体示例和任务载荷结构。 --- ## 1. 配置模型 ### 1.1 数据库配置 ```go // pkg/config/config.go // DatabaseConfig 数据库连接配置 type DatabaseConfig struct { // 连接参数 Host string `mapstructure:"host"` // 数据库主机地址 Port int `mapstructure:"port"` // 数据库端口 User string `mapstructure:"user"` // 数据库用户名 Password string `mapstructure:"password"` // 数据库密码(明文存储) DBName string `mapstructure:"dbname"` // 数据库名称 SSLMode string `mapstructure:"sslmode"` // SSL 模式:disable, require, verify-ca, verify-full // 连接池配置 MaxOpenConns int `mapstructure:"max_open_conns"` // 最大打开连接数(默认:25) MaxIdleConns int `mapstructure:"max_idle_conns"` // 最大空闲连接数(默认:10) ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"` // 连接最大生命周期(默认:5m) } ``` **字段说明**: | 字段 | 类型 | 默认值 | 说明 | |------|------|--------|------| | Host | string | localhost | PostgreSQL 服务器地址 | | Port | int | 5432 | PostgreSQL 服务器端口 | | User | string | postgres | 数据库用户名 | | Password | string | - | 数据库密码(明文存储在配置文件中) | | DBName | string | junhong_cmp | 数据库名称 | | SSLMode | string | disable | SSL 连接模式 | | MaxOpenConns | int | 25 | 最大数据库连接数 | | MaxIdleConns | int | 10 | 最大空闲连接数 | | ConnMaxLifetime | duration | 5m | 连接最大存活时间 | ### 1.2 任务队列配置 ```go // pkg/config/config.go // QueueConfig 任务队列配置 type QueueConfig struct { // 并发配置 Concurrency int `mapstructure:"concurrency"` // Worker 并发数(默认:10) // 队列优先级配置(队列名 -> 权重) Queues map[string]int `mapstructure:"queues"` // 例如:{"critical": 6, "default": 3, "low": 1} // 重试配置 RetryMax int `mapstructure:"retry_max"` // 最大重试次数(默认:5) Timeout time.Duration `mapstructure:"timeout"` // 任务超时时间(默认:10m) } ``` **队列优先级**: - `critical`: 关键任务(权重 6,约 60% 处理时间) - `default`: 普通任务(权重 3,约 30% 处理时间) - `low`: 低优先级任务(权重 1,约 10% 处理时间) ### 1.3 完整配置结构 ```go // pkg/config/config.go // Config 应用配置 type Config struct { Server ServerConfig `mapstructure:"server"` Logging LoggingConfig `mapstructure:"logging"` Redis RedisConfig `mapstructure:"redis"` Database DatabaseConfig `mapstructure:"database"` // 新增 Queue QueueConfig `mapstructure:"queue"` // 新增 Middleware MiddlewareConfig `mapstructure:"middleware"` } ``` --- ## 2. 数据库实体模型 ### 2.1 基础模型(Base Model) ```go // internal/model/base.go import ( "time" "gorm.io/gorm" ) // BaseModel 基础模型,包含通用字段 type BaseModel struct { ID uint `gorm:"primarykey" json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` // 软删除 } ``` **字段说明**: - `ID`: 自增主键 - `CreatedAt`: 创建时间(GORM 自动管理) - `UpdatedAt`: 更新时间(GORM 自动管理) - `DeletedAt`: 删除时间(软删除,GORM 自动过滤已删除记录) ### 2.2 示例实体:用户模型 ```go // internal/model/user.go // User 用户实体 type User struct { BaseModel // 基本信息 Username string `gorm:"uniqueIndex;not null;size:50" json:"username"` Email string `gorm:"uniqueIndex;not null;size:100" json:"email"` Password string `gorm:"not null;size:255" json:"-"` // 不返回给客户端 // 状态字段 Status string `gorm:"not null;size:20;default:'active';index" json:"status"` // 元数据 LastLoginAt *time.Time `json:"last_login_at,omitempty"` } // TableName 指定表名 func (User) TableName() string { return "tb_user" } ``` **索引策略**: - `username`: 唯一索引(快速查找和去重) - `email`: 唯一索引(快速查找和去重) - `status`: 普通索引(状态过滤查询) - `deleted_at`: 自动索引(软删除过滤) **验证规则**: - `username`: 长度 3-50 字符,字母数字下划线 - `email`: 标准邮箱格式 - `password`: 长度 >= 8 字符,bcrypt 哈希存储 - `status`: 枚举值(active, inactive, suspended) ### 2.3 示例实体:订单模型(演示手动关联关系) ```go // internal/model/order.go // Order 订单实体 type Order struct { BaseModel // 业务唯一键 OrderID string `gorm:"uniqueIndex;not null;size:50" json:"order_id"` // 关联关系(仅存储 ID,不使用 GORM 关联) UserID uint `gorm:"not null;index" json:"user_id"` // 订单信息 Amount int64 `gorm:"not null" json:"amount"` // 金额(分) Status string `gorm:"not null;size:20;index" json:"status"` Remark string `gorm:"size:500" json:"remark,omitempty"` // 时间字段 PaidAt *time.Time `json:"paid_at,omitempty"` CompletedAt *time.Time `json:"completed_at,omitempty"` } // TableName 指定表名 func (Order) TableName() string { return "tb_order" } ``` **关联关系说明**: - `UserID`: 存储关联用户的 ID(普通字段,无数据库外键约束) - **无 ORM 关联**:遵循 Constitution Principle IX,不使用 `foreignKey`、`belongsTo` 等标签 - 关联数据查询在 Service 层手动实现(见下方示例) **手动查询关联数据示例**: ```go // internal/service/order/service.go // GetOrderWithUser 查询订单及关联的用户信息 func (s *Service) GetOrderWithUser(ctx context.Context, orderID uint) (*OrderDetail, error) { // 1. 查询订单 order, err := s.store.Order.GetByID(ctx, orderID) if err != nil { return nil, fmt.Errorf("查询订单失败: %w", err) } // 2. 手动查询关联的用户 user, err := s.store.User.GetByID(ctx, order.UserID) if err != nil { return nil, fmt.Errorf("查询用户失败: %w", err) } // 3. 组装返回数据 return &OrderDetail{ Order: order, User: user, }, nil } // ListOrdersByUserID 查询指定用户的订单列表 func (s *Service) ListOrdersByUserID(ctx context.Context, userID uint, page, pageSize int) ([]*Order, int64, error) { return s.store.Order.ListByUserID(ctx, userID, page, pageSize) } ``` **状态流转**: ``` pending → paid → processing → completed ↓ cancelled ``` --- ## 3. 数据传输对象(DTO) ### 3.1 用户 DTO ```go // internal/model/user_dto.go // CreateUserRequest 创建用户请求 type CreateUserRequest struct { Username string `json:"username" validate:"required,min=3,max=50,alphanum"` Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required,min=8"` } // UpdateUserRequest 更新用户请求 type UpdateUserRequest struct { Email *string `json:"email" validate:"omitempty,email"` Status *string `json:"status" validate:"omitempty,oneof=active inactive suspended"` } // UserResponse 用户响应 type UserResponse struct { ID uint `json:"id"` Username string `json:"username"` Email string `json:"email"` Status string `json:"status"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` LastLoginAt *time.Time `json:"last_login_at,omitempty"` } // ListUsersResponse 用户列表响应 type ListUsersResponse struct { Users []UserResponse `json:"users"` Page int `json:"page"` PageSize int `json:"page_size"` Total int64 `json:"total"` TotalPages int `json:"total_pages"` } ``` --- ## 4. 任务载荷模型 ### 4.1 任务类型常量 ```go // pkg/constants/constants.go const ( // 任务类型 TaskTypeEmailSend = "email:send" // 发送邮件 TaskTypeDataSync = "data:sync" // 数据同步 TaskTypeSIMStatusSync = "sim:status:sync" // SIM 卡状态同步 TaskTypeCommission = "commission:calculate" // 分佣计算 ) ``` ### 4.2 邮件任务载荷 ```go // internal/task/email.go // EmailPayload 邮件任务载荷 type EmailPayload struct { RequestID string `json:"request_id"` // 幂等性标识 To string `json:"to"` // 收件人 Subject string `json:"subject"` // 主题 Body string `json:"body"` // 正文 CC []string `json:"cc,omitempty"` // 抄送 Attachments []string `json:"attachments,omitempty"` // 附件路径 } ``` ### 4.3 数据同步任务载荷 ```go // internal/task/sync.go // DataSyncPayload 数据同步任务载荷 type DataSyncPayload struct { RequestID string `json:"request_id"` // 幂等性标识 SyncType string `json:"sync_type"` // 同步类型:sim_status, flow_usage, real_name StartDate string `json:"start_date"` // 开始日期(YYYY-MM-DD) EndDate string `json:"end_date"` // 结束日期(YYYY-MM-DD) BatchSize int `json:"batch_size"` // 批量大小(默认:100) } ``` ### 4.4 SIM 卡状态同步载荷 ```go // internal/task/sim.go // SIMStatusSyncPayload SIM 卡状态同步任务载荷 type SIMStatusSyncPayload struct { RequestID string `json:"request_id"` // 幂等性标识 ICCIDs []string `json:"iccids"` // ICCID 列表 ForceSync bool `json:"force_sync"` // 强制同步(忽略缓存) } ``` --- ## 5. 数据库 Schema(SQL) ### 5.1 初始化 Schema ```sql -- migrations/000001_init_schema.up.sql -- 用户表 CREATE TABLE IF NOT EXISTS tb_user ( id SERIAL PRIMARY KEY, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMP, -- 基本信息 username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, password VARCHAR(255) NOT NULL, -- 状态字段 status VARCHAR(20) NOT NULL DEFAULT 'active', -- 元数据 last_login_at TIMESTAMP, -- 唯一约束 CONSTRAINT uk_user_username UNIQUE (username), CONSTRAINT uk_user_email UNIQUE (email) ); -- 用户表索引 CREATE INDEX idx_user_deleted_at ON tb_user(deleted_at); CREATE INDEX idx_user_status ON tb_user(status); CREATE INDEX idx_user_created_at ON tb_user(created_at); -- 订单表 CREATE TABLE IF NOT EXISTS tb_order ( id SERIAL PRIMARY KEY, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMP, -- 业务唯一键 order_id VARCHAR(50) NOT NULL, -- 关联关系(注意:无数据库外键约束,在代码中管理) user_id INTEGER NOT NULL, -- 订单信息 amount BIGINT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending', remark VARCHAR(500), -- 时间字段 paid_at TIMESTAMP, completed_at TIMESTAMP, -- 唯一约束 CONSTRAINT uk_order_order_id UNIQUE (order_id) ); -- 订单表索引 CREATE INDEX idx_order_deleted_at ON tb_order(deleted_at); CREATE INDEX idx_order_user_id ON tb_order(user_id); CREATE INDEX idx_order_status ON tb_order(status); CREATE INDEX idx_order_created_at ON tb_order(created_at); -- 添加注释 COMMENT ON TABLE tb_user IS '用户表'; COMMENT ON COLUMN tb_user.username IS '用户名(唯一)'; COMMENT ON COLUMN tb_user.email IS '邮箱(唯一)'; COMMENT ON COLUMN tb_user.password IS '密码(bcrypt 哈希)'; COMMENT ON COLUMN tb_user.status IS '用户状态:active, inactive, suspended'; COMMENT ON COLUMN tb_user.deleted_at IS '软删除时间'; COMMENT ON TABLE tb_order IS '订单表'; COMMENT ON COLUMN tb_order.order_id IS '订单号(业务唯一键)'; COMMENT ON COLUMN tb_order.user_id IS '用户 ID(在代码中维护关联,无数据库外键)'; COMMENT ON COLUMN tb_order.amount IS '金额(分)'; COMMENT ON COLUMN tb_order.status IS '订单状态:pending, paid, processing, completed, cancelled'; COMMENT ON COLUMN tb_order.deleted_at IS '软删除时间'; ``` **重要说明**: - ✅ **无外键约束**:`user_id` 仅作为普通字段存储,无 `REFERENCES` 约束 - ✅ **无触发器**:`created_at` 和 `updated_at` 由 GORM 自动管理,无需数据库触发器 - ✅ **遵循 Constitution Principle IX**:表关系在代码层面手动维护 ### 5.2 回滚 Schema ```sql -- migrations/000001_init_schema.down.sql -- 删除表(按依赖顺序倒序删除) DROP TABLE IF EXISTS tb_order; DROP TABLE IF EXISTS tb_user; ``` --- ## 6. Redis 键结构 ### 6.1 任务锁键 ```go // pkg/constants/redis.go // RedisTaskLockKey 生成任务锁键 // 格式: task:lock:{request_id} // 用途: 幂等性控制 // 过期时间: 24 小时 func RedisTaskLockKey(requestID string) string { return fmt.Sprintf("task:lock:%s", requestID) } ``` **使用示例**: ```go key := constants.RedisTaskLockKey("req-123456") // 结果: "task:lock:req-123456" ``` ### 6.2 任务状态键 ```go // RedisTaskStatusKey 生成任务状态键 // 格式: task:status:{task_id} // 用途: 存储任务执行状态 // 过期时间: 7 天 func RedisTaskStatusKey(taskID string) string { return fmt.Sprintf("task:status:%s", taskID) } ``` --- ## 7. 常量定义 ### 7.1 用户状态常量 ```go // pkg/constants/constants.go const ( // 用户状态 UserStatusActive = "active" // 激活 UserStatusInactive = "inactive" // 未激活 UserStatusSuspended = "suspended" // 暂停 ) ``` ### 7.2 订单状态常量 ```go const ( // 订单状态 OrderStatusPending = "pending" // 待支付 OrderStatusPaid = "paid" // 已支付 OrderStatusProcessing = "processing" // 处理中 OrderStatusCompleted = "completed" // 已完成 OrderStatusCancelled = "cancelled" // 已取消 ) ``` ### 7.3 数据库配置常量 ```go const ( // 数据库连接池默认值 DefaultMaxOpenConns = 25 DefaultMaxIdleConns = 10 DefaultConnMaxLifetime = 5 * time.Minute // 查询限制 DefaultPageSize = 20 MaxPageSize = 100 // 慢查询阈值 SlowQueryThreshold = 100 * time.Millisecond ) ``` ### 7.4 任务队列常量 ```go const ( // 队列名称 QueueCritical = "critical" QueueDefault = "default" QueueLow = "low" // 默认重试配置 DefaultRetryMax = 5 DefaultTimeout = 10 * time.Minute // 默认并发数 DefaultConcurrency = 10 ) ``` --- ## 8. 实体关系图(ER Diagram) ``` ┌─────────────────┐ │ tb_user │ ├─────────────────┤ │ id (PK) │ │ username (UQ) │ │ email (UQ) │ │ password │ │ status │ │ last_login_at │ │ created_at │ │ updated_at │ │ deleted_at │ └────────┬────────┘ │ │ 1:N (代码层面维护) │ ┌────────▼────────┐ │ tb_order │ ├─────────────────┤ │ id (PK) │ │ order_id (UQ) │ │ user_id │ ← 存储关联 ID(无数据库外键) │ amount │ │ status │ │ remark │ │ paid_at │ │ completed_at │ │ created_at │ │ updated_at │ │ deleted_at │ └─────────────────┘ ``` **关系说明**: - 一个用户可以有多个订单(1:N 关系) - 订单通过 `user_id` 字段存储用户 ID,**在代码层面维护关联** - **无数据库外键约束**:遵循 Constitution Principle IX - 关联查询在 Service 层手动实现(参见 2.3 节示例代码) --- ## 9. 数据验证规则 ### 9.1 用户字段验证 | 字段 | 验证规则 | 错误消息 | |------|----------|----------| | username | required, min=3, max=50, alphanum | 用户名必填,3-50 个字母数字字符 | | email | required, email | 邮箱必填且格式正确 | | password | required, min=8 | 密码必填,至少 8 个字符 | | status | oneof=active inactive suspended | 状态必须为 active, inactive, suspended 之一 | ### 9.2 订单字段验证 | 字段 | 验证规则 | 错误消息 | |------|----------|----------| | order_id | required, min=10, max=50 | 订单号必填,10-50 个字符 | | user_id | required, gt=0 | 用户 ID 必填且大于 0 | | amount | required, gte=0 | 金额必填且大于等于 0 | | status | oneof=pending paid processing completed cancelled | 状态值无效 | --- ## 10. 数据迁移版本 | 版本 | 文件名 | 描述 | 日期 | |------|--------|------|------| | 1 | 000001_init_schema | 初始化用户表和订单表 | 2025-11-12 | **添加新迁移**: ```bash # 创建新迁移文件 migrate create -ext sql -dir migrations -seq add_sim_table # 生成文件: # migrations/000002_add_sim_table.up.sql # migrations/000002_add_sim_table.down.sql ``` --- ## 总结 本数据模型定义了: 1. **配置模型**:数据库连接配置、任务队列配置 2. **实体模型**:基础模型、用户模型、订单模型(示例) 3. **DTO 模型**:请求/响应数据传输对象 4. **任务载荷**:各类异步任务的载荷结构 5. **数据库 Schema**:SQL 迁移脚本 6. **Redis 键结构**:任务锁、任务状态等键生成函数 7. **常量定义**:状态枚举、默认配置值 8. **验证规则**:字段级别的数据验证规则 **设计原则**: - 遵循 GORM 约定(BaseModel、软删除) - 遵循 Constitution 命名规范(PascalCase 字段、snake_case 列名) - 统一使用常量定义(避免硬编码) - 支持软删除和审计字段(created_at, updated_at) - 使用数据库约束保证数据完整性