数据库、事务与启动迁移
约 1268 字大约 4 分钟
2025-03-03
平台关系数据库由 share/stores 统一管理,业务模型与仓储放在所属服务 internal/repo/relationDB。本章保留从连接、模型、仓储到事务与升级的完整开发路径。
1. 配置与初始化连接
在服务 Config 添加 Database conf.Database。YAML 通过环境变量传入所选驱动的 DSN,避免把口令写进源码:
Database:
DBType: ${dbType:-pgsql}
DSN: ${dbDSN}
IsInitTable: ${dbIsInitTable:-true}在 ServiceContext 初始化时调用 stores.InitConn(c.Database),再执行迁移。连接由进程复用;不能每个请求初始化,也不能在测试中连接生产库。多服务同进程使用全局连接时,配置须一致。
2. 定义模型
下面为业务模型示例。企业字段、时间与软删除类型应跟随本服务相邻模型,确认自动过滤机制后再决定是否使用其公共类型:
// 本文件定义演示业务的每日统计模型。
package relationDB
import "time"
// DemoDailyCount 保存企业按日统计;唯一键约束重复调度写入。
type DemoDailyCount struct {
ID int64 `gorm:"primaryKey;autoIncrement"` // 记录编号。
TenantCode string `gorm:"size:64;uniqueIndex:tenant_day"` // 数据所属企业。
Date time.Time `gorm:"uniqueIndex:tenant_day"` // 统计日期。
Type string `gorm:"size:32;uniqueIndex:tenant_day"` // 统计类别。
Num int64 // 统计数量。
}
// TableName 返回业务专属表名,防止不同模块模型命名冲突。
func (*DemoDailyCount) TableName() string { return "demo_daily_count" }示例中 TenantCode 是普通字符串,不会因此自动获得企业过滤。采用 stores 的作用域类型时,也要通过 SQL 和跨企业用例确认实际生效。
3. 建立仓储
复制本服务已有的 example.go 模板,替换模型及过滤结构;参考系统服务 internal/repo/relationDB/example.go。仓储构造接受 ctx 或事务 *gorm.DB,查询使用 WithContext(ctx),数据库错误使用 stores.ErrFmt 转换。
所有读写都限定业务范围。基于 ID 更新、删除前也必须检查企业、项目及对象归属;不能只在列表页过滤。
4. 事务必须传递同一个 tx
示意片段中 ctx 为已认证业务上下文,模型为上面的示例:
// 在同一个数据库事务中更新当前企业的统计数据,错误触发回滚。
err := stores.GetCommonConn(ctx).Transaction(func(tx *gorm.DB) error {
return stores.ErrFmt(tx.WithContext(ctx).
Model(&DemoDailyCount{}).
Where("tenant_code = ? AND id = ?", tenantCode, recordID).
UpdateColumn("num", gorm.Expr("num + ?", increment)).Error)
})若在事务中调用仓储,使用 NewXxxRepo(tx);传 ctx 会重新获取普通连接,导致部分操作脱离事务。并发计数采用原子更新或锁,不用无保护的“读出 +1 再保存”。
RPC、消息发布、文件操作不属于本地数据库事务。需要可靠联动时,在事务内记录持久任务/发件箱,提交后执行并使用幂等键,不能假定跨系统一起回滚。
5. 联表过滤用子查询
查询已绑定某应用的企业等场景,使用模型子查询与参数绑定。下面为概念示例,SysTenantApp、SysAppInfo 使用系统服务已有模型:
// 子查询也限定企业,避免关联表把其他企业的数据带进结果。
subQuery := db.WithContext(ctx).Model(&SysTenantApp{}).
Select("app_id").Where("tenant_code = ?", tenantCode)
query := db.WithContext(ctx).Model(&SysAppInfo{}).
Where("id IN (?)", subQuery)未绑定筛选可使用 NOT IN,同时检查子查询列的 NULL 语义。不要拼接用户提供的列名、排序或 SQL。即使主模型自动过滤,关联模型与子查询也需独立核对,不能概括为“GORM 会自动过滤所有表”。
复杂设备过滤可参考设备信息仓储的子查询组合;列表查询和总数统计必须共用同一组过滤条件。
6. 启动时执行幂等迁移
迁移入口为本服务 modelMigrate.go 的 Migrate/versionUpdate 等现有流程,由 ServiceContext 或 startup 调用。没有统一的额外 migrate 子命令。
- 新模型加入所属服务迁移清单,按
IsInitTable及该服务现行策略执行。 - 索引、默认数据与存量回填必须考虑重跑、部分失败和多实例并发。
- 种子数据按稳定编码/唯一键 upsert,不能每次启动插入重复记录。
- 改列类型、删字段和大表回填不要只依赖 AutoMigrate;按兼容升级设计分阶段收敛。
7. 验证与常用组件
验证空库、已有库升级、重复启动、事务回滚、并发唯一键及跨企业读写。使用本机隔离库,测试不能修改共享或生产数据。
| 位置 | 用途 |
|---|---|
share/stores/conn.go | 连接与上下文连接管理 |
share/stores/cmp.go | 查询条件辅助 |
share/stores/time.go | 时间类型 |
share/stores/barrier/ | 已有分布式事务辅助,按模块需求选用 |
新增跨企业查询属于显式特权行为,要有独立授权和审计。应用自有数据库与平台内部模型分离,应用方式见业务开发。
更新日志
2026/10/16 19:46
查看所有更新日志
b3b4d-docs(平台开发): 恢复有效专题并更新当前实现示例于
