## Context 当前系统中存在大量为号卡业务设计的分佣模型(冻结/解冻、组合分佣、运营商结算等),但流量卡业务只需要简单的一次性佣金机制。这些模型增加了代码复杂度且从未使用。 现有套餐模型 `Package` 缺少建议价格字段和上架状态管理,无法支持后续的代理套餐分配功能。 **当前代码结构**: - Handler 在 `internal/handler/admin/` 下,每个模块一个文件 - Service 在 `internal/service/{module}/service.go`,每个模块一个包 - Store 在 `internal/store/postgres/{module}_store.go` - Bootstrap 在 `internal/bootstrap/` 负责组件注册 ## Goals / Non-Goals **Goals:** - 清理 8 个废弃模型,减少代码复杂度 - 扩展 Package 模型支持建议价格和上架状态 - 提供完整的套餐系列 CRUD API - 提供完整的套餐 CRUD API(含双状态管理) - 遵循现有代码架构风格 **Non-Goals:** - 不实现代理套餐分配(Phase 2) - 不实现一次性佣金计算(Phase 5) - 不迁移现有数据(表内无数据) - 不修改 `CommissionRecord` 模型(后续 Phase 简化) ## Decisions ### 1. 模型文件处理策略 **决策**:直接删除废弃模型定义,不保留注释或空文件 **理由**: - 这些模型从未在生产环境使用 - Git 历史可追溯 - 保留空定义增加维护负担 **替代方案**: - ❌ 标记为 deprecated 保留:增加代码噪音 - ❌ 移到 archive 目录:过度设计 ### 2. Package 模型字段设计 **决策**:新增三个字段 ```go type Package struct { // ... 现有字段 ... SuggestedCostPrice int64 `gorm:"column:suggested_cost_price;type:bigint;default:0;comment:建议成本价(分为单位)" json:"suggested_cost_price"` SuggestedRetailPrice int64 `gorm:"column:suggested_retail_price;type:bigint;default:0;comment:建议售价(分为单位)" json:"suggested_retail_price"` ShelfStatus int `gorm:"column:shelf_status;type:int;default:2;not null;comment:上架状态 1-上架 2-下架" json:"shelf_status"` } ``` **理由**: - `suggested_cost_price`:平台定义的建议成本价,代理分配时参考 - `suggested_retail_price`:平台定义的建议零售价,代理设置售价时参考 - `shelf_status`:与 `status`(启用/禁用)分离,支持独立的上架控制 - 默认 `shelf_status=2`(下架):新套餐需要显式上架 **替代方案**: - ❌ 用 JSON 字段存储扩展属性:查询不便,类型不安全 - ❌ 合并 status 和 shelf_status:语义不同,分开更清晰 ### 3. 双状态业务规则 **决策**:启用状态(status)和上架状态(shelf_status)独立但有约束 | status | shelf_status | 允许操作 | |--------|--------------|----------| | 启用(1) | 上架(1) | 可购买 | | 启用(1) | 下架(2) | 不可购买,可上架 | | 禁用(2) | 上架(1) | ❌ 禁止 - 禁用时强制下架 | | 禁用(2) | 下架(2) | 不可购买,需先启用再上架 | **理由**: - 禁用套餐不应该可购买,强制下架保证数据一致性 - 启用但下架:允许平台配置套餐但暂不开放购买 ### 4. API 路由设计 **决策**:使用 RESTful 风格,状态变更使用 PATCH ``` # 套餐系列 POST /api/admin/package-series 创建 GET /api/admin/package-series 列表 GET /api/admin/package-series/:id 详情 PUT /api/admin/package-series/:id 更新 DELETE /api/admin/package-series/:id 删除 PATCH /api/admin/package-series/:id/status 启用/禁用 # 套餐 POST /api/admin/packages 创建 GET /api/admin/packages 列表 GET /api/admin/packages/:id 详情 PUT /api/admin/packages/:id 更新 DELETE /api/admin/packages/:id 删除 PATCH /api/admin/packages/:id/status 启用/禁用 PATCH /api/admin/packages/:id/shelf 上架/下架 ``` **理由**: - 与现有 API 风格一致(参考 `/api/admin/carriers`) - 状态变更使用 PATCH 符合 HTTP 语义 - 路径清晰,易于前端对接 ### 5. Service 层设计 **决策**:每个模块独立 Service 包 ``` internal/service/package_series/service.go # 套餐系列 Service internal/service/package/service.go # 套餐 Service ``` **理由**: - 与现有架构一致(carrier, iot_card 等) - 便于后续扩展(如套餐关联其他模块) ### 6. 数据库迁移策略 **决策**:单个迁移文件,先删后改 迁移顺序: 1. DROP 8 个废弃表 2. ALTER tb_package 添加 3 个新字段 **理由**: - 这些表无生产数据,可直接删除 - 单文件便于回滚 ## Risks / Trade-offs ### 风险 1:删除模型后发现有隐藏引用 **风险**:代码中可能有对废弃模型的隐藏引用导致编译失败 **缓解**: - 删除模型后执行 `go build ./...` 确认编译通过 - 使用 IDE 全局搜索确认无引用 ### 风险 2:双状态逻辑复杂度 **风险**:禁用时强制下架的逻辑可能被遗漏 **缓解**: - 在 Service 层统一处理状态变更逻辑 - 添加单元测试覆盖所有状态组合 ### 风险 3:API 命名与现有冲突 **风险**:`/packages` 路径可能与未来其他套餐类型冲突 **缓解**: - 当前只有流量卡套餐,命名合理 - 未来如有号卡套餐,可使用 `/number-card-packages` ## Migration Plan ### 部署步骤 1. **代码部署前**: - 确认生产环境废弃表无数据 - 备份数据库(预防措施) 2. **执行迁移**: ```bash go run cmd/migrate/main.go up ``` 3. **验证**: - 确认 8 个表已删除 - 确认 tb_package 新增 3 个字段 - API 健康检查 ### 回滚策略 ```bash go run cmd/migrate/main.go down ``` 迁移 down 脚本: - 重建 8 个废弃表(结构保留) - 删除 tb_package 的 3 个新字段 **注意**:回滚不恢复数据,仅恢复表结构 ## Open Questions 1. **套餐系列禁用是否级联影响套餐?** - 当前设计:不级联,套餐系列禁用只影响系列本身 - 待确认:是否需要禁用系列时自动禁用下属套餐? 2. **删除套餐/套餐系列的约束?** - 当前设计:物理删除(soft delete via GORM) - 待确认:是否需要检查关联数据(如已分配给代理的套餐)? - 建议:Phase 2 实现代理分配后再添加约束检查