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

104 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 0. 基线确认与准备
- [x] 0.1 梳理生产依赖的软删除表清单(所有带 `deleted_at` 的实体)。
- [x] 0.2 盘点所有 GORM 用法:`Preload``Transaction``Locking``Expr``datatypes.JSONMap``Raw` 统计 SQL。
- [x] 0.3 确认数据库为 PostgreSQL明确迁移执行位置部署期 vs 启动期)。
- [x] 0.3.1 **确定迁移工具链(第一阶段)**:使用 `backend/migrations/*.sql` 作为唯一迁移来源;由内置 runner 记录 `schema_migrations`(含 checksum
- [x] 0.3.2 **补齐迁移脚本覆盖面**:新增 schema parity/legacy 数据修复迁移,确保空库可重建并覆盖当前代码所需表/列(含 `settings``redeem_codes` 扩展列、`accounts` 调度字段、`usage_logs.billing_type` 等)。
- [x] 0.4 **修复现有 GORM 错误处理 bug**`api_key_auth_google.go` 已改为判断业务错误(`service.ErrApiKeyNotFound`),并补充单元测试覆盖。
## 1. 引入 Ent代码生成与基础设施
- [x] 1.1 新增 `backend/ent/` 目录schema、生成代码、mixin配置 `entc` 生成go generate 或 make target
- [x] 1.1.1 固化 `go:generate` 命令与 feature flags`intercept` + `sql/upsert`,并指定 `--idtype int64`)。
- [x] 1.2 实现 SoftDelete mixinQuery Interceptor + Mutation Hook + SkipSoftDelete(ctx)),确保默认过滤/软删 delete 语义可用。
- [x] 1.3 改造 `backend/internal/infrastructure`:提供 `*ent.Client`;同时提供 `*sql.DB`(当前阶段通过 `gorm.DB.DB()` 暴露,供 raw SQL 使用)。
- [x] 1.4 改造 `backend/cmd/server/wire.go` cleanup关闭 ent client。
- [x] 1.5 **更新 Wire 依赖注入配置**:更新所有 Provider 函数签名,从 `*gorm.DB` 改为 `*ent.Client`
- [x] 1.6 在服务入口引入 `backend/ent/runtime`Ent 生成)以注册 schema hooks/interceptors避免循环依赖导致未注册
- 代码 import 示例:`github.com/Wei-Shaw/sub2api/ent/runtime`
## 2. 数据模型与迁移(向后兼容优先)
- [x] 2.1 新增 `user_allowed_groups` 表:定义字段、索引、唯一约束;从 `users.allowed_groups` 回填数据。
- [x] 2.1.1 为 `user_allowed_groups` 编写回填校验 SQLold_pairs vs new_pairs并把执行步骤写入部署文档/README。
- [x] 2.1.2 设计 Phase 2 清理:在灰度完成后删除 `users.allowed_groups` 列(独立迁移文件,确保可回滚窗口足够)。
- [x] 2.2 `account_groups` 保持现有复合主键,迁移为 Ent Edge Schema无 DB 变更);补充校验:确保 `(account_id, group_id)` 唯一性在 DB 层已被约束PK 或 Unique
- [x] 2.3 为软删除字段建立必要索引(`deleted_at`)。
- [x] 2.4 移除启动时 `AutoMigrate`,改为执行 `backend/migrations/*.sql`(对齐单一迁移来源)。
- [x] 2.5 更新安装/初始化流程:`internal/setup` 不再调用 `repository.AutoMigrate`,改为执行 `backend/migrations/*.sql`(确保新安装环境与生产迁移链路一致)。
## 3. 仓储层迁移(按风险分批)
### 3.A 低风险仓储(优先迁移,用于验证 Ent 基础设施)
- [x] 3.1 迁移 `setting_repo`:简单 CRUD + upsertEnt `OnConflictColumns(...).UpdateNewValues()`)。
- [x] 3.2 迁移 `proxy_repo`CRUD + 软删除 + 账户数量统计(统计保持 raw SQLproxy 表读写改为 Ent
### 3.B 中等风险仓储
- [x] 3.3 迁移 `api_key_repo`:关联 eager-load`WithUser``WithGroup`),错误翻译为业务错误。
- [x] 3.4 迁移 `redeem_code_repo`CRUD + 状态更新。
- [x] 3.5 迁移 `group_repo`:事务、级联删除逻辑(可保留 raw SQL但必须在 ent Tx 内执行,例如 `tx.ExecContext`,避免绕开事务)。
- 迁移 `users.allowed_groups` 相关逻辑:在删除分组时改为 `DELETE FROM user_allowed_groups WHERE group_id = ?`
### 3.C 高风险仓储
- [x] 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
- [x] 3.7 迁移 `user_subscription_repo`:批量过期、用量增量更新(`gorm.Expr`)、关联预加载。
- [x] 3.8 迁移 `account_repo`join 表排序、JSONB merge写操作优先用 `client.ExecContext/tx.ExecContext` 执行 raw SQL校验 bulk update 的 rowsAffected 语义一致。
### 3.D 保留 Raw SQL
- [x] 3.9 `usage_log_repo` 保留原 SQL底层改为注入/获取 `*sql.DB` 执行(例如 infrastructure 同时提供 `*sql.DB`)。
- 识别可用 Ent Builder 的简单查询(如 `Create``GetByID`
- 保留 CTE/聚合等复杂 SQL趋势统计、Top N 等)
## 4. 错误处理与边角清理
- [x] 4.1 替换 `repository/error_translate.go`:用 `ent.IsNotFound/IsConstraintError` 等映射。
- [x] 4.2 清理 GORM 泄漏点:
- [x] `middleware/api_key_auth_google.go` - 已修复:从 `gorm.ErrRecordNotFound` 判断迁移为业务错误判断
- [x] `repository/account_repo.go:50` - 直接判断 `gorm.ErrRecordNotFound`
- [x] `repository/redeem_code_repo.go:125` - 使用 `gorm.ErrRecordNotFound`
- [x] 4.3 检查 `internal/setup/` 包是否有 GORM 依赖。
- [x] 4.4 检查 `*_cache.go` 文件是否有潜在 GORM 依赖。
## 5. 测试与回归
- [x] 5.1 **迁移测试基础设施**(优先级高):
- [x] **建表策略对齐生产GORM 阶段)**:在 Postgres testcontainer 中执行 `backend/migrations/*.sql` 初始化 schema不再依赖 AutoMigrate
- [x] 增加“schema 对齐/可重建”校验:新增集成测试断言关键表/列存在,并验证 migrations runner 幂等性。
- [x] 为已迁移仓储增加 Ent 事务测试工具:使用 `*sql.Tx` + Ent driver 绑定到同一事务,实现按测试用例回滚(见 `testEntSQLTx`)。
- [x] 更新 `integration_harness_test.go`:从 `*gorm.DB` 改为 `*ent.Client`
- [x] 更新 `IntegrationDBSuite`:从 `testTx()` 返回 `*gorm.DB` 改为 `*ent.Tx``*sql.Tx`
- [x] 确保事务回滚机制在 Ent 下正常工作
- [x] 5.2 新增软删除回归用例:
- delete 后默认不可见
- `SkipSoftDelete(ctx)` 可见
- 重复 delete 的幂等性(不应引入新的 `NotFound` 行为)
- hard delete 可用(仅管理场景)
- [ ] 5.3 跑全量单测 + 集成测试;重点覆盖:
- API key 鉴权
- 订阅扣费/限额
- 账号调度
- 统计接口
## 6. 收尾(去除 GORM
- [x] 6.1 移除 `gorm.io/*` 依赖与相关代码路径。
- [x] 6.2 更新 README/部署文档:迁移命令、回滚策略、开发者生成代码指引。
- [x] 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