Files
junhong_cmp_fiber/docs/traffic-model-unification/流量模型统一改造方案.md
huang fe4c545308
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m55s
修正数据
2026-04-27 18:10:08 +08:00

635 lines
17 KiB
Markdown
Raw Permalink 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.
# 流量模型统一改造方案
## 1. 背景
当前项目中的流量逻辑同时存在以下几套口径:
1. **卡同步口径**
- `tb_iot_card.last_gateway_reading_mb`
- `tb_iot_card.current_month_usage_mb`
- `tb_iot_card.data_usage_mb`
2. **套餐使用口径**
- `tb_package_usage.data_limit_mb`
- `tb_package_usage.data_usage_mb`
3. **展示换算口径**
- `tb_package.virtual_ratio`
- B 端/C 端多个接口中对 `virtual_*` 字段的换算
这三套口径目前没有统一到同一业务模型上,导致以下问题:
1. 流量同步与流量展示不是同一套业务事实源。
2. 停机判断仍按真总量而不是按虚阈值判断。
3. 历史套餐展示依赖 `tb_package` 当前值,缺少使用记录快照。
4. 资产视角接口和套餐视角接口混用,前端不知道该信哪一个字段。
5. 已存在字段 `real_data_usage_mb``virtual_data_usage_mb` 没有进入主链路,语义噪音较大。
本方案的目标不是“小修展示口径”,而是**统一整个项目的流量业务模型**。
---
## 2. 核心结论
### 2.1 业务事实源统一
**结论:业务流量永远跟随套餐,不跟随卡。**
- **卡表(`tb_iot_card`** 只负责:
- 同步上游网关累计流量
- 计算增量
- 记录同步状态与同步时间
- 排查上游异常
- **套餐使用表(`tb_package_usage`** 才是:
- 停机判断事实源
- B 端资产流量展示事实源
- C 端流量展示事实源
- 套餐历史与当前套餐流量事实源
### 2.2 流量摘要主体统一
**结论:流量摘要的主体应为 `package_usage_id`,不是 `asset_id`,也不是 `package_id`。**
原因:
1. 同一个资产可能存在多条套餐使用记录(主套餐、加油包、历史套餐)。
2. 同一个 `package_id` 会被多个资产、多次购买复用。
3. 前端真正要看的,是某一条具体套餐使用记录当前的四个业务值。
因此,流量摘要应以 **`tb_package_usage.id`** 为主键进行查询和展示。
### 2.3 前端最终只看四个业务值
本次统一后的对外流量模型只保留四个业务值:
1. `real_total_mb`:真总量
2. `real_used_mb`:真已用
3. `virtual_total_mb`:虚总量(原始虚阈值)
4. `virtual_used_mb`:按比例换算后、与真总量同尺度的展示值
说明:
- 用户已确认:`virtual_total_mb` 使用**原始虚总量**,不是换算后的总量。
- 用户已确认:`virtual_used_mb` 使用**按比例换算后的展示值**。
---
## 3. 统一后的业务定义
### 3.1 套餐模板层(`tb_package`
套餐模板保留以下字段定义:
- `real_data_mb`:套餐真总量
- `virtual_data_mb`:套餐虚总量 / 虚阈值
- `enable_virtual_data`:是否启用虚流量
- `virtual_ratio`:内部展示倍率,公式为 `real_data_mb / virtual_data_mb`
### 3.2 套餐使用层(`tb_package_usage`
套餐使用记录作为最终业务事实源,统一定义为:
- `data_limit_mb`**真总量快照**
- `data_usage_mb`**真已用**
- `virtual_total_mb_snapshot`**虚总量快照**
- `display_gain_ratio_snapshot`**展示倍率快照**
- `enable_virtual_data_snapshot`**是否启用虚流量快照**
### 3.3 四个业务值公式
对于任意一条 `package_usage`,统一用以下公式计算:
#### 场景 A启用虚流量且 `virtual_total_mb_snapshot > 0`
- `real_total_mb = data_limit_mb`
- `real_used_mb = data_usage_mb`
- `virtual_total_mb = virtual_total_mb_snapshot`
- `virtual_used_mb = min(real_used_mb * display_gain_ratio_snapshot, real_total_mb)`
- `reduction_pct = display_gain_ratio_snapshot - 1`
#### 场景 B未启用虚流量
- `real_total_mb = data_limit_mb`
- `real_used_mb = data_usage_mb`
- `virtual_total_mb = data_limit_mb`
- `virtual_used_mb = real_used_mb`
说明:
1. 未启用虚流量时,虚流量视图退化为真流量视图,保证前端仍然能稳定收到四个值。
2. `virtual_used_mb` 的上限必须封顶到 `real_total_mb`,避免出现展示值超过真总量。
### 3.4 停机判断公式
统一后的停机判断必须基于套餐使用记录:
#### 场景 A启用虚流量且 `virtual_total_mb_snapshot > 0`
-`real_used_mb >= virtual_total_mb` 时,套餐视为已耗尽
#### 场景 B未启用虚流量
-`real_used_mb >= real_total_mb` 时,套餐视为已耗尽
这一定义与本次 OpenSpec 的典型样例一致:
-`100` / 虚 `70`:真已用到 `70` 时停机,展示 `virtual_used_mb = 100`
- 未启用虚流量:真已用到 `100` 时停机,展示 `virtual_used_mb = real_used_mb`
### 3.5 `virtual_data_mb` 校验规则
本次统一后的规则为:
1. `enable_virtual_data = true` 时,`virtual_data_mb` 必须大于 `0`
2. `enable_virtual_data = true` 时,`virtual_data_mb` 必须小于等于 `real_data_mb`
3. 未启用虚流量时,展示视图退化为真流量视图
---
## 4. 同步链路统一方案
### 4.1 卡同步链路保留,但降级为“同步事实链路”
当前同步链路的核心算法可以保留:
1. 从网关读取当前累计流量读数 `Used`
2. 使用 `Used - last_gateway_reading_mb` 计算本次增量
3. 遇到上游重置窗口时,将当前读数视为新周期增量
该链路应继续写入卡表:
- `tb_iot_card.last_gateway_reading_mb`
- `tb_iot_card.current_month_usage_mb`
- `tb_iot_card.current_month_start_date`
- `tb_iot_card.last_month_total_mb`
- `tb_iot_card.last_sync_time`
- `tb_iot_card.last_data_check_at`
### 4.2 卡同步增量如何进入套餐层
同步出的真流量增量必须继续扣减到 `tb_package_usage.data_usage_mb`
统一口径:
- **同步层永远只同步“真流量增量”**
- **套餐层永远只累加“真已用”**
- **虚流量不参与同步,只参与展示换算和停机阈值判断**
### 4.3 `tb_iot_card.data_usage_mb` 的处理
当前 `tb_iot_card.data_usage_mb` 表示卡生命周期累计流量,但它不是业务展示主来源。
本次建议:
- **保留字段**
- **降级为同步辅助/运维诊断字段**
- 不再作为 B 端/C 端业务展示的主流量来源
说明:
1. 当前该字段写入时直接使用 `int64(increment)`,存在小数截断。
2. 套餐层已通过 `UsageService` 的余量缓存机制处理小数累计,更适合作为业务事实源。
---
## 5. 展示链路统一方案
### 5.1 资产视角不再直接承载流量摘要
`asset` 本身不是流量摘要的正确主体,因为一个资产可能有多条套餐使用记录。
因此:
- `资产详情`
- `资产解析`
- `C 端资产信息`
不应再把流量摘要作为“资产级别唯一摘要”长期保留。
### 5.2 套餐视角承载流量摘要
流量摘要应绑定到具体的 `package_usage_id`
- B 端:资产套餐列表 / 当前套餐
- C 端:当前套餐 / 套餐历史 / 套餐流量详情
都应围绕 `package_usage_id` 返回四个业务值。
### 5.3 建议的接口模型
#### 方案 A现有套餐接口直接返回四值
- `GET /api/admin/assets/:identifier/packages`
- `GET /api/admin/assets/:identifier/current-package`
- `GET /api/c/v1/asset/package-history`
每条 `AssetPackageResponse` 直接返回:
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
#### 方案 B新增“套餐流量摘要”接口
`package_usage_id` 查询单条套餐流量摘要,例如:
- `GET /api/admin/asset-packages/:package_usage_id/traffic-summary`
- `GET /api/c/v1/asset/package-usages/:package_usage_id/traffic-summary`
推荐做法:
1. 套餐列表接口仍返回四值,方便列表页直接展示
2. 同时提供单条摘要接口,供详情弹窗/详情页按 `package_usage_id` 精确查询
### 5.4 本次建议下线的旧展示字段
以下字段语义模糊,应从对外 DTO 中逐步移除:
- `package_total_mb`
- `package_used_mb`
- `package_remain_mb`
- `virtual_remain_mb`
替代为显式字段:
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
---
## 6. 数据库改造方案
### 6.1 本次必须新增的字段
`tb_package_usage` 新增:
1. `virtual_total_mb_snapshot BIGINT NOT NULL DEFAULT 0`
2. `display_gain_ratio_snapshot DECIMAL(18,6) NOT NULL DEFAULT 1.0`
3. `enable_virtual_data_snapshot BOOLEAN NOT NULL DEFAULT FALSE`
### 6.2 为什么必须加快照字段
如果不加快照,会有三个问题:
1. 历史套餐展示依赖 `tb_package` 当前值,模板一改,历史流量就漂
2. 历史停机判断无法复现当时口径
3. 退款、历史订单、历史套餐详情都无法稳定回放
### 6.3 回填策略
对已有 `tb_package_usage` 数据执行回填:
#### 若 `tb_package.enable_virtual_data = true` 且 `tb_package.virtual_data_mb > 0`
- `virtual_total_mb_snapshot = tb_package.virtual_data_mb`
- `display_gain_ratio_snapshot = tb_package.virtual_ratio`
- `enable_virtual_data_snapshot = true`
#### 否则
- `virtual_total_mb_snapshot = data_limit_mb`
- `display_gain_ratio_snapshot = 1.0`
- `enable_virtual_data_snapshot = false`
### 6.4 现有字段暂不重命名
本次不建议直接重命名以下数据库字段:
- `tb_package_usage.data_limit_mb`
- `tb_package_usage.data_usage_mb`
原因:
1. 改名会牵涉大量 SQL、模型、历史脚本、迁移与兼容问题
2. 当前可以通过“统一文义 + 新增快照字段”先完成业务收口
本次统一后的文义为:
- `data_limit_mb` = 真总量
- `data_usage_mb` = 真已用
### 6.5 最终可删除字段
以下字段当前未进入主链路,本轮已在迁移中物理删除:
- `tb_package_usage.real_data_usage_mb`
- `tb_package_usage.virtual_data_usage_mb`
执行顺序:
1. 代码层先完全停止依赖它们
2. 再通过独立迁移删除数据库旧列
---
## 7. 代码改造清单
### 7.1 数据模型与迁移
需要改动:
- `migrations/`:新增 `tb_package_usage` 快照字段迁移与历史回填
- `internal/model/package.go`
改动内容:
1. `PackageUsage` 增加三类快照字段
2.`data_limit_mb``data_usage_mb` 的注释补充“真总量 / 真已用”的语义说明
3. 为后续删除 `real_data_usage_mb``virtual_data_usage_mb` 做准备
### 7.2 套餐创建与激活链路
需要改动:
- `internal/service/order/service.go`
- `internal/task/auto_purchase.go`
改动内容:
1. 创建 `PackageUsage` 时写入:
- `data_limit_mb = pkg.RealDataMB`
- `virtual_total_mb_snapshot`
- `display_gain_ratio_snapshot`
- `enable_virtual_data_snapshot`
2. 所有主套餐、加油包、自动购包路径保持一致
### 7.3 套餐配置校验
需要改动:
- `internal/service/package/service.go`
改动内容:
1. 保留“启用虚流量时必须填写虚流量”的校验
2. 收紧为 `virtual_data_mb > 0 且 <= real_data_mb`
3. `virtual_ratio` 继续使用 `real_data_mb / virtual_data_mb`
### 7.4 流量同步链路
需要改动:
- `internal/task/polling_carddata_handler.go`
- `internal/service/iot_card/service.go`
改动内容:
1. 核心同步算法保持不变
2. 明确注释与日志:该链路同步的是“真流量增量”
3. 保持将真增量扣减到套餐层
4. 不在该链路引入任何虚流量换算逻辑
### 7.5 套餐扣减链路
需要改动:
- `internal/service/package/usage_service.go`
改动内容:
1. `data_usage_mb` 继续作为真已用累加
2. 套餐是否“已用完”必须改为按“虚阈值快照或真总量”判断
3. 加油包 / 主套餐的耗尽逻辑全部统一到快照字段
4. 日记录 `PackageUsageDailyRecord` 继续记录真流量使用量
### 7.6 停机判断链路
需要改动:
- `internal/service/iot_card/stop_resume_service.go`
改动内容:
1. `isTrafficExhausted` 改为基于 `package_usage` 快照字段判断
2. 启用虚流量时,按 `real_used >= virtual_total_snapshot`
3. 未启用时,按 `real_used >= real_total`
### 7.7 套餐重置链路
需要改动:
- `internal/service/package/reset_service.go`
- `internal/store/postgres/package_usage_store.go`
改动内容:
1. 重置时继续清零 `data_usage_mb`
2. 重置后根据快照阈值恢复 `status=active`
3. 不引入额外的虚流量计数器
### 7.8 后台管理接口
需要改动:
- `internal/service/asset/service.go`
- `internal/model/dto/asset_dto.go`
- 如有前端依赖生成文档,还需同步更新 `docs/admin-openapi.yaml`
改动内容:
1. `AssetResolveResponse` 去掉资产级别模糊流量摘要字段
2. `AssetPackageResponse` 改为输出四个显式业务值:
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
3. `packages` / `current-package` 统一按 `package_usage_id` 展示和计算
### 7.9 C 端接口
需要改动:
- `internal/handler/app/client_asset.go`
- `internal/model/dto/client_asset_dto.go`
- `internal/service/package/customer_view_service.go`
改动内容:
1. C 端资产信息页不再把资产本身当作唯一流量摘要主体
2. C 端套餐历史/当前套餐改为套餐视角四值
3. 如果需要单条摘要页,则新增按 `package_usage_id` 查询的接口
### 7.10 套餐商品展示联动确认
需要关注:
- `internal/handler/app/client_asset.go` 中可购套餐列表当前 `data_allowance` 在启用虚流量时直接展示 `virtual_data_mb`
该处不是本次核心链路,但建议联动确认:
1. 商品展示要展示真流量、虚流量,还是只展示一种
2. 若前端也要透明展示两种总量DTO 需补充显式字段
---
## 8. DTO 与接口建议
### 8.1 建议新增或替换的字段
建议在套餐相关 DTO 中统一使用:
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
### 8.2 建议废弃的字段
建议废弃:
- `package_total_mb`
- `package_used_mb`
- `package_remain_mb`
- `virtual_limit_mb`
- `virtual_remain_mb`
- `data_limit_mb`(对外 DTO 层)
- `data_usage_mb`(对外 DTO 层)
说明:
数据库内部暂时仍可保留 `data_limit_mb` / `data_usage_mb`,但 DTO 层应改为语义明确的新字段。
### 8.3 接口职责建议
#### 卡实时状态接口
保留同步诊断字段,例如:
- `last_gateway_reading_mb`
- `current_month_usage_mb`
- `last_sync_time`
#### 套餐流量接口
只返回四个业务值,不再掺杂卡级同步字段。
---
## 9. 迁移与上线顺序
建议按以下顺序执行:
### 第一步:数据库准备
1. 新增 `tb_package_usage` 快照字段
2. 回填历史数据
3. 为后续删列保留可回滚窗口
### 第二步:写链路切换
1. 套餐激活/自动购包路径写入快照字段
2. 扣减逻辑与停机逻辑改为读取快照字段
### 第三步:读链路切换
1. B 端套餐接口改为返回四个业务值
2. C 端套餐接口改为返回四个业务值
3. 资产级模糊流量摘要降级或移除
### 第四步:前端联调
1. 管理后台改为只消费套餐视角四值
2. C 端改为只消费套餐视角四值
3. 如需套餐流量详情页,按 `package_usage_id` 查询
### 第五步:清理旧字段
1. 停止使用 `real_data_usage_mb` / `virtual_data_usage_mb`
2. 物理删除旧列
3. 对外移除旧 DTO 字段
---
## 10. 手工验证清单
本项目不写自动化测试,本次改造建议用以下方式手工验证:
### 10.1 PostgreSQL 验证
验证项:
1. `tb_package_usage` 快照字段是否已正确回填
2. 启用虚流量的套餐是否满足:
- `virtual_total_mb_snapshot = tb_package.virtual_data_mb`
- `display_gain_ratio_snapshot = tb_package.virtual_ratio`
3. 未启用虚流量的套餐是否满足:
- `virtual_total_mb_snapshot = data_limit_mb`
- `display_gain_ratio_snapshot = 1.0`
### 10.2 上游同步验证
验证项:
1. 轮询后 `tb_iot_card.last_gateway_reading_mb` 是否更新
2. 同步增量是否正确进入 `tb_package_usage.data_usage_mb`
3. 跨月与运营商重置窗口时增量是否正确
### 10.3 停机验证
验证项:
1.`100` / 虚 `70`:真已用到 `70` 时停机
2.`100` / 虚 `70` / 真已用 `69`:展示 `virtual_used_mb = 98.57`
3. 未启用虚流量:真已用到真总量时停机
### 10.4 展示验证
验证项:
1. B 端套餐列表与当前套餐返回四个业务值
2. C 端套餐信息返回四个业务值
3. 资产详情页不再混用卡同步流量与套餐业务流量
---
## 11. 本次改造范围边界
### 本次必须做
1. 套餐使用记录补快照
2. 同步、扣减、停机、展示统一到套餐层
3. DTO 和接口统一四个业务值
4. 资产流量视角切换为套餐视角
### 本次不建议强行做
1. 直接重命名数据库旧字段
2. 在本轮立即物理删除所有历史字段
3. 把卡同步辅助字段全部移除
原因:
1. 本次首要目标是统一业务口径,先消除逻辑混乱
2. 物理清理应放在口径稳定后单独做
---
## 12. 最终结论
本次流量统一改造的核心原则只有两条:
1. **同步看卡,业务看套餐**
2. **展示永远以 `package_usage_id` 为主体,不以资产为主体**
按本方案落地后:
1. 同步链路只负责真流量增量采集
2. 套餐链路负责真已用、停机阈值和四值展示
3. B 端和 C 端最终只消费:
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
这套模型可以同时覆盖:
1. 真总量大于虚阈值
2. 真总量小于虚阈值
3. 未启用虚流量
4. 历史套餐快照展示
5. 当前套餐与套餐历史流量统一展示