将仓储层/基础设施改为 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 ./...
7.4 KiB
7.4 KiB
0. 基线确认与准备
- 0.1 梳理生产依赖的软删除表清单(所有带
deleted_at的实体)。 - 0.2 盘点所有 GORM 用法:
Preload、Transaction、Locking、Expr、datatypes.JSONMap、Raw统计 SQL。 - 0.3 确认数据库为 PostgreSQL,明确迁移执行位置(部署期 vs 启动期)。
- 0.3.1 确定迁移工具链(第一阶段):使用
backend/migrations/*.sql作为唯一迁移来源;由内置 runner 记录schema_migrations(含 checksum)。 - 0.3.2 补齐迁移脚本覆盖面:新增 schema parity/legacy 数据修复迁移,确保空库可重建并覆盖当前代码所需表/列(含
settings、redeem_codes扩展列、accounts调度字段、usage_logs.billing_type等)。 - 0.4 修复现有 GORM 错误处理 bug:
api_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 flags(intercept+sql/upsert,并指定--idtype int64)。 - 1.2 实现 SoftDelete mixin(Query 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.gocleanup:关闭 ent client。 - 1.5 更新 Wire 依赖注入配置:更新所有 Provider 函数签名,从
*gorm.DB改为*ent.Client。 - 1.6 在服务入口引入
backend/ent/runtime(Ent 生成)以注册 schema hooks/interceptors(避免循环依赖导致未注册)。- 代码 import 示例:
github.com/Wei-Shaw/sub2api/ent/runtime
- 代码 import 示例:
2. 数据模型与迁移(向后兼容优先)
- 2.1 新增
user_allowed_groups表:定义字段、索引、唯一约束;从users.allowed_groups回填数据。 - 2.1.1 为
user_allowed_groups编写回填校验 SQL(old_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 + upsert(EntOnConflictColumns(...).UpdateNewValues())。 - 3.2 迁移
proxy_repo:CRUD + 软删除 + 账户数量统计(统计保持 raw SQL,proxy 表读写改为 Ent)。
3.B 中等风险仓储
- 3.3 迁移
api_key_repo:关联 eager-load(WithUser、WithGroup),错误翻译为业务错误。 - 3.4 迁移
redeem_code_repo:CRUD + 状态更新。 - 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_repo:CRUD、分页/过滤、余额/并发原子更新(gorm.Expr);allowed 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_repo:join 表排序、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 的简单查询(如
Create、GetByID) - 保留 CTE/聚合等复杂 SQL(趋势统计、Top N 等)
- 识别可用 Ent Builder 的简单查询(如
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.ErrRecordNotFoundrepository/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 下正常工作
- 建表策略对齐生产(GORM 阶段):在 Postgres testcontainer 中执行
- 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/gormgorm.io/driver/postgresgorm.io/datatypes
附录:工作量参考
| 组件 | 代码行数 | GORM 调用点 | 复杂度 |
|---|---|---|---|
| 仓储层总计 | ~13,000 行 | (待统计) | - |
| Raw SQL | - | (待统计) | 高 |
| gorm.Expr | - | (待统计) | 中 |
| 集成测试 | (待统计) | - | 高 |
建议迁移顺序:
- 测试基础设施(5.1)→ 确保后续迁移可验证
- 低风险仓储(3.1-3.2)→ 验证 Ent 基础设施
- 中等风险仓储(3.3-3.5)→ 验证关联加载和事务
- 高风险仓储(3.6-3.8)→ 处理复杂场景
- 错误处理清理(4.x)→ 统一错误映射
- 收尾(6.x)→ 移除 GORM