重构: 将卡/设备的套餐系列绑定从分配ID改为系列ID

- 数据库: 重命名 series_allocation_id → series_id
- Model: IotCard 和 Device 字段重命名
- DTO: 所有请求/响应字段统一为 series_id
- Store: 方法重命名,新增 GetByShopAndSeries 查询
- Service: 业务逻辑优化,系列验证和权限验证分离
- 测试: 更新所有测试用例,新增 shop_series_allocation_store_test.go
- 文档: 更新 API 文档说明参数变更

BREAKING CHANGE: API 参数从 series_allocation_id 改为 series_id
This commit is contained in:
2026-02-02 12:09:53 +08:00
parent a30b3036bb
commit 37f43d2e2d
27 changed files with 673 additions and 301 deletions

View File

@@ -0,0 +1,191 @@
# Tasks: refactor-series-binding-to-series-id
## 1. 数据库迁移
- [x] 1.1 创建数据库迁移文件 `migrations/000XXX_refactor_series_binding_to_series_id.up.sql`,重命名 `tb_iot_card.series_allocation_id``series_id``tb_device.series_allocation_id``series_id`,更新字段注释
- [x] 1.2 创建回滚迁移文件 `migrations/000XXX_refactor_series_binding_to_series_id.down.sql`
- [x] 1.3 验证索引是否存在:检查 `tb_shop_series_allocation` 是否有 `(shop_id, series_id)` 复合索引,如不存在则添加
- [x] 1.4 执行迁移:运行 `migrate up`,验证字段重命名成功,无错误
## 2. Model 层修改
- [x] 2.1 修改 `internal/model/iot_card.go`:将 `SeriesAllocationID` 字段重命名为 `SeriesID`,更新 gorm 标签和注释
- [x] 2.2 修改 `internal/model/device.go`:将 `SeriesAllocationID` 字段重命名为 `SeriesID`,更新 gorm 标签和注释
- [x] 2.3 验证编译:运行 `go build ./internal/model/...`,确认无编译错误
## 3. DTO 层修改
- [x] 3.1 修改 `internal/model/dto/iot_card_dto.go`:更新 `ListStandaloneIotCardRequest` 的查询参数 `SeriesAllocationID``SeriesID`
- [x] 3.2 修改 `internal/model/dto/iot_card_dto.go`:更新 `StandaloneIotCardResponse` 的响应字段 `SeriesAllocationID``SeriesID`
- [x] 3.3 修改 `internal/model/dto/iot_card_dto.go`:更新 `BatchSetCardSeriesBindngRequest` 的请求字段 `SeriesAllocationID``SeriesID`,更新 description 为 "套餐系列ID0表示清除关联"
- [x] 3.4 修改 `internal/model/dto/device_dto.go`:更新 `ListDeviceRequest` 的查询参数 `SeriesAllocationID``SeriesID`
- [x] 3.5 修改 `internal/model/dto/device_dto.go`:更新 `DeviceResponse` 的响应字段 `SeriesAllocationID``SeriesID`
- [x] 3.6 修改 `internal/model/dto/device_dto.go`:更新 `BatchSetDeviceSeriesBindngRequest` 的请求字段 `SeriesAllocationID``SeriesID`,更新 description 为 "套餐系列ID0表示清除关联"
- [x] 3.7 验证编译:运行 `go build ./internal/model/dto/...`,确认无编译错误
## 4. Store 层修改
- [x] 4.1 修改 `internal/store/postgres/iot_card_store.go`:更新 `ListStandalone` 方法,将过滤条件 `series_allocation_id` 改为 `series_id`
- [x] 4.2 修改 `internal/store/postgres/iot_card_store.go`:更新 `Count` 方法,将过滤条件 `series_allocation_id` 改为 `series_id`
- [x] 4.3 修改 `internal/store/postgres/iot_card_store.go`:重命名方法 `BatchUpdateSeriesAllocation``BatchUpdateSeriesID`,更新 SQL 字段名
- [x] 4.4 修改 `internal/store/postgres/iot_card_store.go`:重命名方法 `ListBySeriesAllocationID``ListBySeriesID`,更新 WHERE 条件
- [x] 4.5 修改 `internal/store/postgres/device_store.go`:更新 `List` 方法,将过滤条件 `series_allocation_id` 改为 `series_id`
- [x] 4.6 修改 `internal/store/postgres/device_store.go`:重命名方法 `BatchUpdateSeriesAllocation``BatchUpdateSeriesID`,更新 SQL 字段名
- [x] 4.7 修改 `internal/store/postgres/device_store.go`:重命名方法 `ListBySeriesAllocationID``ListBySeriesID`,更新 WHERE 条件
- [x] 4.8 修改 `internal/store/postgres/shop_series_allocation_store.go`:新增方法 `GetByShopAndSeries(ctx, shopID, seriesID)`,实现根据店铺和系列查询分配配置
- [x] 4.9 验证或创建 `internal/store/postgres/package_series_store.go`:如不存在则创建,实现 `GetByID(ctx, id)` 方法
- [x] 4.10 如果创建了新 Store`internal/bootstrap/stores.go` 中注册 `PackageSeriesStore`
- [x] 4.11 验证编译:运行 `go build ./internal/store/...`,确认无编译错误
## 5. Service 层修改 - iot_card
- [x] 5.1 修改 `internal/service/iot_card/service.go`:更新 `ListStandalone` 方法,将过滤条件 key `series_allocation_id` 改为 `series_id`
- [x] 5.2 修改 `internal/service/iot_card/service.go`:更新 `buildStandaloneResponse` 方法,将字段 `SeriesAllocationID` 改为 `SeriesID`
- [x] 5.3 修改 `internal/service/iot_card/service.go`:重构 `BatchSetSeriesBinding` 方法
- [x] 5.4 在 `internal/service/iot_card/service.go``Service` 结构体中添加 `packageSeriesStore` 依赖(如果不存在)
- [x] 5.5 验证编译:运行 `go build ./internal/service/iot_card/...`,确认无编译错误
- [x] 5.6 运行 `lsp_diagnostics` 检查 `internal/service/iot_card/service.go`,确认无类型错误
## 6. Service 层修改 - device
- [x] 6.1 修改 `internal/service/device/service.go`:更新 `List` 方法,将过滤条件 key `series_allocation_id` 改为 `series_id`
- [x] 6.2 修改 `internal/service/device/service.go`:更新 `buildDeviceResponse` 方法,将字段 `SeriesAllocationID` 改为 `SeriesID`
- [x] 6.3 修改 `internal/service/device/service.go`:重构 `BatchSetSeriesBinding` 方法
- [x] 6.4 在 `internal/service/device/service.go``Service` 结构体中添加 `packageSeriesStore` 依赖(如果不存在)
- [x] 6.5 验证编译:运行 `go build ./internal/service/device/...`,确认无编译错误
- [x] 6.6 运行 `lsp_diagnostics` 检查 `internal/service/device/service.go`,确认无类型错误
## 7. Service 层修改 - purchase_validation关键
- [x] 7.1 修改 `internal/service/purchase_validation/service.go`:重构 `ValidateCardPurchase` 方法
- [x] 7.2 修改 `internal/service/purchase_validation/service.go`:重构 `ValidateDevicePurchase` 方法
- [x] 7.3 更新 `ValidateCardPurchase``ValidateDevicePurchase` 的错误消息,从 "套餐系列分配不存在" 改为 "该卡/设备未关联套餐系列"
- [x] 7.4 验证编译:运行 `go build ./internal/service/purchase_validation/...`,确认无编译错误
- [x] 7.5 运行 `lsp_diagnostics` 检查 `internal/service/purchase_validation/service.go`,确认无类型错误
## 8. Service 层修改 - commission_calculation关键
- [x] 8.1 修改 `internal/service/commission_calculation/service.go`:重构 `CalculateOrderCommission` 方法
- [x] 8.2 修改 `internal/service/commission_calculation/service.go`:重构 `CalculateDeviceOrderCommission` 方法(同样的逻辑)
- [x] 8.3 验证编译:运行 `go build ./internal/service/commission_calculation/...`,确认无编译错误
- [x] 8.4 运行 `lsp_diagnostics` 检查 `internal/service/commission_calculation/service.go`,确认无类型错误
## 9. Service 层修改 - recharge
- [x] 9.1 修改 `internal/service/recharge/service.go`:重构充值相关方法,将获取 `seriesAllocationID` 的逻辑改为直接使用 `seriesID`
- [x] 9.2 修改 `internal/service/recharge/service.go`:更新返佣查询逻辑,使用 `GetByShopAndSeries(shopID, seriesID)` 而不是 `GetByID(allocationID)`
- [x] 9.3 验证编译:运行 `go build ./internal/service/recharge/...`,确认无编译错误
- [x] 9.4 运行 `lsp_diagnostics` 检查 `internal/service/recharge/service.go`,确认无类型错误
## 10. Service 层修改 - order
- [x] 10.1 检查 `internal/service/order/service.go` 中是否有直接使用 `SeriesAllocationID` 的地方,如有则更新为 `SeriesID`
- [x] 10.2 验证编译:运行 `go build ./internal/service/order/...`,确认无编译错误
- [x] 10.3 运行 `lsp_diagnostics` 检查 `internal/service/order/service.go`,确认无类型错误
## 11. Bootstrap 依赖注入
- [x] 11.1 如果创建了 `PackageSeriesStore`,在 `internal/bootstrap/stores.go` 中初始化并添加到 `Stores` 结构体
- [x] 11.2 在 `internal/bootstrap/services.go` 中,为 `iot_card.Service``device.Service` 注入 `packageSeriesStore` 依赖
- [x] 11.3 验证编译:运行 `go build ./internal/bootstrap/...`,确认无编译错误
## 12. Handler & Routes 层
- [x] 12.1 修改 `internal/routes/iot_card.go`:更新 `/series-binding` 路由的 Description说明参数从 `series_allocation_id` 改为 `series_id`
- [x] 12.2 修改 `internal/routes/device.go`:更新 `/series-binding` 路由的 Description说明参数从 `series_allocation_id` 改为 `series_id`
- [x] 12.3 验证 Handler 层代码:`internal/handler/admin/iot_card.go``device.go` 无需修改(使用 DTO
- [x] 12.4 验证编译:运行 `go build ./internal/routes/... ./internal/handler/...`,确认无编译错误
## 13. Store 层测试更新
- [x] 13.1 修改 `internal/store/postgres/iot_card_store_test.go`:更新所有测试用例,将 `SeriesAllocationID` 改为 `SeriesID`
- [x] 13.2 修改 `internal/store/postgres/iot_card_store_test.go`:重命名测试函数 `TestIotCardStore_ListBySeriesAllocationID``TestIotCardStore_ListBySeriesID`
- [x] 13.3 修改 `internal/store/postgres/iot_card_store_test.go`:更新过滤条件测试,将 `series_allocation_id` 改为 `series_id`
- [x] 13.4 修改 `internal/store/postgres/device_store_test.go`:更新所有测试用例,将 `SeriesAllocationID` 改为 `SeriesID`
- [x] 13.5 修改 `internal/store/postgres/device_store_test.go`:重命名测试函数 `TestDeviceStore_ListBySeriesAllocationID``TestDeviceStore_ListBySeriesID`
- [x] 13.6 新增测试:在 `shop_series_allocation_store_test.go` 中添加 `TestShopSeriesAllocationStore_GetByShopAndSeries` 测试
- [x] 13.7 运行 Store 层测试:`source .env.local && go test -v ./internal/store/postgres/...`,确认全部通过
## 14. Service 层测试更新 - iot_card
- [x] 14.1 修改 `internal/service/iot_card/service_test.go`:更新 `TestIotCardService_BatchSetSeriesBinding` 测试
- [x] 14.2 更新测试数据准备顺序:先 `PackageSeries`,再 `ShopSeriesAllocation`,最后 `IotCard`
- [x] 14.3 运行 Service 层测试:`source .env.local && go test -v ./internal/service/iot_card/...`,确认全部通过
## 15. Service 层测试更新 - device
- [x] 15.1 修改 `internal/service/device/service_test.go`:更新 `TestDeviceService_BatchSetSeriesBinding` 测试
- [x] 15.2 更新测试数据准备顺序:先 `PackageSeries`,再 `ShopSeriesAllocation`,最后 `Device`
- [x] 15.3 运行 Service 层测试:`source .env.local && go test -v ./internal/service/device/...`,确认全部通过
## 16. Service 层测试更新 - purchase_validation
- [x] 16.1 修改 `internal/service/purchase_validation/service_test.go`:更新所有测试用例
- [x] 16.2 运行 Service 层测试:`source .env.local && go test -v ./internal/service/purchase_validation/...`,确认全部通过
## 17. Service 层测试更新 - commission_calculation
- [x] 17.1 修改 `internal/service/commission_calculation/service_test.go`:更新所有测试用例
- [x] 17.2 运行 Service 层测试:`source .env.local && go test -v ./internal/service/commission_calculation/...`,确认全部通过
## 18. Service 层测试更新 - recharge & order
- [x] 18.1 修改 `internal/service/recharge/service_test.go`:更新测试用例,将 `SeriesAllocationID` 改为 `SeriesID`
- [x] 18.2 修改 `internal/service/order/service_test.go`:更新测试用例,将 `SeriesAllocationID` 改为 `SeriesID`
- [x] 18.3 运行 Service 层测试:`source .env.local && go test -v ./internal/service/recharge/... ./internal/service/order/...`,确认全部通过
## 19. 集成测试更新 - iot_card
- [x] 19.1 修改 `tests/integration/iot_card_test.go`:更新 `TestIotCard_BatchSetSeriesBinding` 测试
- [x] 19.2 更新所有子测试用例的 JSON 请求体(约 10 个)
- [x] 19.3 运行集成测试:`source .env.local && cd tests/integration && go test -v -run "TestIotCard_BatchSetSeriesBinding"`,确认全部通过
## 20. 集成测试更新 - device
- [x] 20.1 修改 `tests/integration/device_test.go`:更新 `TestDevice_BatchSetSeriesBinding` 测试
- [x] 20.2 更新所有子测试用例的 JSON 请求体(约 10 个)
- [x] 20.3 运行集成测试:`source .env.local && cd tests/integration && go test -v -run "TestDevice_BatchSetSeriesBinding"`,确认全部通过
## 21. 单元测试更新 - commission_calculation
- [x] 21.1 修改 `tests/unit/commission_calculation_service_test.go`:更新所有测试用例
- [x] 21.2 运行单元测试:`source .env.local && go test -v ./tests/unit/...`,确认全部通过
## 22. 全量测试验证
- [x] 22.1 运行所有测试:`source .env.local && go test -v ./...`,确认全部通过,无遗漏
- [x] 22.2 运行编译检查:`go build ./...`,确认无编译错误
- [x] 22.3 使用 grep 搜索遗漏:`grep -r "SeriesAllocationID" internal/ --include="*.go"`,确认无遗漏
- [x] 22.4 使用 grep 搜索遗漏:`grep -r "series_allocation_id" internal/ --include="*.go"`,确认无遗漏(仅数据库注释除外)
## 23. API 手动测试
- [ ] 23.1 启动本地服务:`go run cmd/api/main.go`
- [ ] 23.2 测试 IoT 卡系列绑定 API`PATCH /api/admin/iot-cards/series-binding`,使用 Postman 或 curl 发送请求,验证参数 `series_id` 生效
- [ ] 23.3 测试设备系列绑定 API`PATCH /api/admin/devices/series-binding`,使用 Postman 或 curl 发送请求,验证参数 `series_id` 生效
- [ ] 23.4 测试卡列表查询:`GET /api/admin/iot-cards/standalone?series_id=1`,验证过滤生效
- [ ] 23.5 测试设备列表查询:`GET /api/admin/devices?series_id=1`,验证过滤生效
- [ ] 23.6 检查日志确认无错误日志SQL 查询使用 `series_id` 而不是 `series_allocation_id`
## 24. API 文档更新
- [x] 24.1 更新 OpenAPI 文档注释:确保路由描述中明确说明参数从 `series_allocation_id` 改为 `series_id`
- [x] 24.2 重新生成 API 文档:运行 `go run cmd/gendocs/main.go`,生成最新的 OpenAPI spec
- [x] 24.3 验证生成的文档:检查 `docs/admin-openapi.yaml``/iot-cards/series-binding``/devices/series-binding` 的参数定义
- [x] 24.4 如有前端文档,更新前端接口文档,说明 BREAKING CHANGE
## 25. 清理和最终验证
- [x] 25.1 删除所有临时代码和注释
- [x] 25.2 运行 `gofmt -w .` 格式化所有代码
- [x] 25.3 运行 `go mod tidy` 清理依赖
- [x] 25.4 再次运行全量测试:`source .env.local && go test -v ./...`,确认全部通过
- [x] 25.5 使用 `git diff` 检查所有改动,确认无遗漏,无多余修改
- [x] 25.6 更新 CHANGELOG如有记录 BREAKING CHANGE
## 26. 提交和归档
- [ ] 26.1 提交代码:创建 Git commit使用中文 commit message"重构: 将卡/设备的套餐系列绑定从分配ID改为系列ID"
- [ ] 26.2 运行 OpenSpec 归档:`openspec archive --change refactor-series-binding-to-series-id`
- [ ] 26.3 验证归档成功:检查 `openspec/changes/archive/` 目录,确认变更已归档
- [ ] 26.4 清理工作目录:删除 `openspec/changes/refactor-series-binding-to-series-id/`(已归档)