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

17 KiB
Raw Blame History

流量模型统一改造方案

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_mbvirtual_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 = truetb_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_mbdata_usage_mb 的注释补充“真总量 / 真已用”的语义说明
  3. 为后续删除 real_data_usage_mbvirtual_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. 当前套餐与套餐历史流量统一展示