feat: 实现运营商模块重构,添加冗余字段优化查询性能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m16s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m16s
主要变更: - 新增 Carrier CRUD API(创建、列表、详情、更新、删除、状态更新) - IotCard/IotCardImportTask 添加 carrier_type/carrier_name 冗余字段 - 移除 Carrier 表的 channel_name/channel_code 字段 - 查询时直接使用冗余字段,避免 JOIN Carrier 表 - 添加数据库迁移脚本(000021-000023) - 添加单元测试和集成测试 - 同步更新 OpenAPI 文档和 specs
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
## Context
|
||||
|
||||
Carrier(运营商)模块当前状态:
|
||||
- Model 已定义于 `internal/model/carrier.go`,表名 `tb_carrier`
|
||||
- 被 IotCard、IotCardImportTask、PollingConfig 通过 `carrier_id` 引用
|
||||
- 缺少管理接口,数据通过其他方式手动管理
|
||||
- `channel_name`、`channel_code` 字段未被任何地方使用
|
||||
- 查询 IotCard 时需要 JOIN Carrier 表获取 carrier_name
|
||||
|
||||
CarrierType 固定为 4 种:CMCC(中国移动)、CUCC(中国联通)、CTCC(中国电信)、CBN(中国广电)
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 提供完整的 Carrier 管理 API(CRUD + 状态管理)
|
||||
- 简化 Carrier Model,移除冗余字段
|
||||
- IotCard/ImportTask 存储冗余快照,实现数据自包含
|
||||
- 优化查询性能,减少不必要的 JOIN
|
||||
|
||||
**Non-Goals:**
|
||||
- PollingConfig 保持 carrier_id 引用方式(配置语义,NULL 表示所有运营商)
|
||||
- 不修改 Carrier 的业务逻辑(仅提供管理接口)
|
||||
- 不支持 CarrierType 动态扩展(固定 4 种)
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1: 冗余快照 vs 保持引用
|
||||
|
||||
**决策**: IotCard/ImportTask 采用冗余快照存储 carrier_type、carrier_name
|
||||
|
||||
**理由**:
|
||||
- 查询时无需 JOIN,性能更好
|
||||
- Carrier 软删除后历史数据完整
|
||||
- 符合"赋予时刻快照"的业务语义——卡分配后运营商信息不应变化
|
||||
|
||||
**备选方案**:
|
||||
- 保持纯引用:需要限制 Carrier 删除,查询需要 JOIN
|
||||
- 软引用 + 视图:复杂度高,维护成本大
|
||||
|
||||
### D2: PollingConfig 不添加冗余字段
|
||||
|
||||
**决策**: PollingConfig 保持 carrier_id 引用
|
||||
|
||||
**理由**:
|
||||
- PollingConfig 是配置表,`carrier_id = NULL` 有特殊含义(所有运营商)
|
||||
- 配置应该跟随 Carrier 变化,不需要历史快照
|
||||
- 场景不同于数据记录
|
||||
|
||||
### D3: CarrierType 枚举校验
|
||||
|
||||
**决策**: 创建时 carrier_type 必须是 CMCC/CUCC/CTCC/CBN 之一,使用 validator 枚举校验
|
||||
|
||||
**理由**:
|
||||
- 运营商类型固定,不会动态扩展
|
||||
- 前端下拉选择,后端强校验
|
||||
- 避免脏数据
|
||||
|
||||
### D4: 删除策略
|
||||
|
||||
**决策**: Carrier 可以软删除,不检查关联数据
|
||||
|
||||
**理由**:
|
||||
- IotCard 已有冗余字段,不依赖 Carrier 表
|
||||
- ImportTask 同理
|
||||
- PollingConfig 的 carrier_id 可能变成悬空引用,但配置场景可接受(管理员负责)
|
||||
|
||||
### D5: API 路由设计
|
||||
|
||||
**决策**: 遵循项目现有 RESTful 风格
|
||||
|
||||
```
|
||||
POST /api/admin/carriers 创建
|
||||
GET /api/admin/carriers 列表(分页+筛选)
|
||||
GET /api/admin/carriers/:id 详情
|
||||
PUT /api/admin/carriers/:id 更新
|
||||
DELETE /api/admin/carriers/:id 软删除
|
||||
PUT /api/admin/carriers/:id/status 启用/禁用
|
||||
```
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
### R1: 数据冗余导致存储增加
|
||||
- **风险**: 每条 IotCard 多存 carrier_type(20B) + carrier_name(100B)
|
||||
- **缓解**: 可接受的存储开销,换取查询性能和数据独立性
|
||||
|
||||
### R2: 迁移时需要填充历史数据
|
||||
- **风险**: 现有 IotCard/ImportTask 记录需要通过 UPDATE 填充冗余字段
|
||||
- **缓解**: 迁移脚本从 Carrier 表 JOIN 填充,一次性操作
|
||||
|
||||
### R3: carrier_code/carrier_type 创建后不可修改
|
||||
- **风险**: 如果创建错误,只能删除重建
|
||||
- **缓解**: 前端选择 + 后端校验,降低出错概率;错误情况下软删除后重建
|
||||
|
||||
### R4: 移除 channel 字段是 BREAKING CHANGE
|
||||
- **风险**: 如果有外部系统依赖这些字段
|
||||
- **缓解**: 已确认这些字段未被使用,可安全移除
|
||||
Reference in New Issue
Block a user