Files
sub2api/openspec/changes/migrate-orm-gorm-to-ent/tasks.md
yangjianbo 3d617de577 refactor(数据库): 迁移持久层到 Ent 并清理 GORM
将仓储层/基础设施改为 Ent + 原生 SQL 执行路径,并移除 AutoMigrate 与 GORM 依赖。
重构内容包括:
- 仓储层改用 Ent/SQL(含 usage_log/account 等复杂查询),统一错误映射
- 基础设施与 setup 初始化切换为 Ent + SQL migrations
- 集成测试与 fixtures 迁移到 Ent 事务模型
- 清理遗留 GORM 模型/依赖,补充迁移与文档说明
- 增加根目录 Makefile 便于前后端编译

测试:
- go test -tags unit ./...
- go test -tags integration ./...
2025-12-29 10:03:27 +08:00

7.4 KiB
Raw Blame History

0. 基线确认与准备

  • 0.1 梳理生产依赖的软删除表清单(所有带 deleted_at 的实体)。
  • 0.2 盘点所有 GORM 用法:PreloadTransactionLockingExprdatatypes.JSONMapRaw 统计 SQL。
  • 0.3 确认数据库为 PostgreSQL明确迁移执行位置部署期 vs 启动期)。
  • 0.3.1 确定迁移工具链(第一阶段):使用 backend/migrations/*.sql 作为唯一迁移来源;由内置 runner 记录 schema_migrations(含 checksum
  • 0.3.2 补齐迁移脚本覆盖面:新增 schema parity/legacy 数据修复迁移,确保空库可重建并覆盖当前代码所需表/列(含 settingsredeem_codes 扩展列、accounts 调度字段、usage_logs.billing_type 等)。
  • 0.4 修复现有 GORM 错误处理 bugapi_key_auth_google.go 已改为判断业务错误(service.ErrApiKeyNotFound),并补充单元测试覆盖。

1. 引入 Ent代码生成与基础设施

  • 1.1 新增 backend/ent/ 目录schema、生成代码、mixin配置 entc 生成go generate 或 make target
  • 1.1.1 固化 go:generate 命令与 feature flagsintercept + sql/upsert,并指定 --idtype int64)。
  • 1.2 实现 SoftDelete mixinQuery Interceptor + Mutation Hook + SkipSoftDelete(ctx)),确保默认过滤/软删 delete 语义可用。
  • 1.3 改造 backend/internal/infrastructure:提供 *ent.Client;同时提供 *sql.DB(当前阶段通过 gorm.DB.DB() 暴露,供 raw SQL 使用)。
  • 1.4 改造 backend/cmd/server/wire.go cleanup关闭 ent client。
  • 1.5 更新 Wire 依赖注入配置:更新所有 Provider 函数签名,从 *gorm.DB 改为 *ent.Client
  • 1.6 在服务入口引入 backend/ent/runtimeEnt 生成)以注册 schema hooks/interceptors避免循环依赖导致未注册
    • 代码 import 示例:github.com/Wei-Shaw/sub2api/ent/runtime

2. 数据模型与迁移(向后兼容优先)

  • 2.1 新增 user_allowed_groups 表:定义字段、索引、唯一约束;从 users.allowed_groups 回填数据。
  • 2.1.1 为 user_allowed_groups 编写回填校验 SQLold_pairs vs new_pairs并把执行步骤写入部署文档/README。
  • 2.1.2 设计 Phase 2 清理:在灰度完成后删除 users.allowed_groups 列(独立迁移文件,确保可回滚窗口足够)。
  • 2.2 account_groups 保持现有复合主键,迁移为 Ent Edge Schema无 DB 变更);补充校验:确保 (account_id, group_id) 唯一性在 DB 层已被约束PK 或 Unique
  • 2.3 为软删除字段建立必要索引(deleted_at)。
  • 2.4 移除启动时 AutoMigrate,改为执行 backend/migrations/*.sql(对齐单一迁移来源)。
  • 2.5 更新安装/初始化流程:internal/setup 不再调用 repository.AutoMigrate,改为执行 backend/migrations/*.sql(确保新安装环境与生产迁移链路一致)。

3. 仓储层迁移(按风险分批)

3.A 低风险仓储(优先迁移,用于验证 Ent 基础设施)

  • 3.1 迁移 setting_repo:简单 CRUD + upsertEnt OnConflictColumns(...).UpdateNewValues())。
  • 3.2 迁移 proxy_repoCRUD + 软删除 + 账户数量统计(统计保持 raw SQLproxy 表读写改为 Ent

3.B 中等风险仓储

  • 3.3 迁移 api_key_repo:关联 eager-loadWithUserWithGroup),错误翻译为业务错误。
  • 3.4 迁移 redeem_code_repoCRUD + 状态更新。
  • 3.5 迁移 group_repo:事务、级联删除逻辑(可保留 raw SQL但必须在 ent Tx 内执行,例如 tx.ExecContext,避免绕开事务)。
    • 迁移 users.allowed_groups 相关逻辑:在删除分组时改为 DELETE FROM user_allowed_groups WHERE group_id = ?

3.C 高风险仓储

  • 3.6 迁移 user_repoCRUD、分页/过滤、余额/并发原子更新(gorm.Exprallowed groups 改为 join 表实现。
    • 替换 ANY(allowed_groups)/array_remove 语义:改为对 user_allowed_groups 的 join/filter/delete
    • 覆盖 RemoveGroupFromAllowedGroups:改为 DELETE FROM user_allowed_groups WHERE group_id = ? 并返回 rowsAffected
  • 3.7 迁移 user_subscription_repo:批量过期、用量增量更新(gorm.Expr)、关联预加载。
  • 3.8 迁移 account_repojoin 表排序、JSONB merge写操作优先用 client.ExecContext/tx.ExecContext 执行 raw SQL校验 bulk update 的 rowsAffected 语义一致。

3.D 保留 Raw SQL

  • 3.9 usage_log_repo 保留原 SQL底层改为注入/获取 *sql.DB 执行(例如 infrastructure 同时提供 *sql.DB)。
    • 识别可用 Ent Builder 的简单查询(如 CreateGetByID
    • 保留 CTE/聚合等复杂 SQL趋势统计、Top N 等)

4. 错误处理与边角清理

  • 4.1 替换 repository/error_translate.go:用 ent.IsNotFound/IsConstraintError 等映射。
  • 4.2 清理 GORM 泄漏点:
    • middleware/api_key_auth_google.go - 已修复:从 gorm.ErrRecordNotFound 判断迁移为业务错误判断
    • repository/account_repo.go:50 - 直接判断 gorm.ErrRecordNotFound
    • repository/redeem_code_repo.go:125 - 使用 gorm.ErrRecordNotFound
  • 4.3 检查 internal/setup/ 包是否有 GORM 依赖。
  • 4.4 检查 *_cache.go 文件是否有潜在 GORM 依赖。

5. 测试与回归

  • 5.1 迁移测试基础设施(优先级高):
    • 建表策略对齐生产GORM 阶段):在 Postgres testcontainer 中执行 backend/migrations/*.sql 初始化 schema不再依赖 AutoMigrate
    • 增加“schema 对齐/可重建”校验:新增集成测试断言关键表/列存在,并验证 migrations runner 幂等性。
    • 为已迁移仓储增加 Ent 事务测试工具:使用 *sql.Tx + Ent driver 绑定到同一事务,实现按测试用例回滚(见 testEntSQLTx)。
    • 更新 integration_harness_test.go:从 *gorm.DB 改为 *ent.Client
    • 更新 IntegrationDBSuite:从 testTx() 返回 *gorm.DB 改为 *ent.Tx*sql.Tx
    • 确保事务回滚机制在 Ent 下正常工作
  • 5.2 新增软删除回归用例:
    • delete 后默认不可见
    • SkipSoftDelete(ctx) 可见
    • 重复 delete 的幂等性(不应引入新的 NotFound 行为)
    • hard delete 可用(仅管理场景)
  • 5.3 跑全量单测 + 集成测试;重点覆盖:
    • API key 鉴权
    • 订阅扣费/限额
    • 账号调度
    • 统计接口

6. 收尾(去除 GORM

  • 6.1 移除 gorm.io/* 依赖与相关代码路径。
  • 6.2 更新 README/部署文档:迁移命令、回滚策略、开发者生成代码指引。
  • 6.3 清理 go.mod 中的 GORM 相关依赖:
    • gorm.io/gorm
    • gorm.io/driver/postgres
    • gorm.io/datatypes

附录:工作量参考

组件 代码行数 GORM 调用点 复杂度
仓储层总计 ~13,000 行 (待统计) -
Raw SQL - (待统计)
gorm.Expr - (待统计)
集成测试 (待统计) -

建议迁移顺序

  1. 测试基础设施5.1)→ 确保后续迁移可验证
  2. 低风险仓储3.1-3.2)→ 验证 Ent 基础设施
  3. 中等风险仓储3.3-3.5)→ 验证关联加载和事务
  4. 高风险仓储3.6-3.8)→ 处理复杂场景
  5. 错误处理清理4.x→ 统一错误映射
  6. 收尾6.x→ 移除 GORM