feat: 实现套餐管理模块,包含套餐系列、双状态管理、废弃模型清理
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m24s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m24s
- 新增套餐系列管理 (CRUD + 状态切换) - 新增套餐管理 (CRUD + 启用/禁用 + 上架/下架双状态) - 清理 8 个废弃分佣模型及对应数据库表 - Package 模型新增建议成本价、建议售价、上架状态字段 - 完整的 Store/Service/Handler 三层实现 - 包含单元测试和集成测试 - 归档 add-package-module change - 新增多个 OpenSpec changes (订单支付、店铺套餐分配、一次性分佣、卡设备系列绑定)
This commit is contained in:
199
openspec/changes/archive/2026-01-27-add-package-module/design.md
Normal file
199
openspec/changes/archive/2026-01-27-add-package-module/design.md
Normal file
@@ -0,0 +1,199 @@
|
||||
## 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 实现代理分配后再添加约束检查
|
||||
Reference in New Issue
Block a user