Files
huang 6b1fbf4a92
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m30s
修复金额的问题
2026-04-23 10:09:23 +08:00

4.4 KiB
Raw Blame History

Context

当前 polling_carddata 链路已经能正确更新卡维度流量读数,但套餐扣减存在两个缺口:

  1. 载体路由缺口:扣减调用固定使用 carrier_type=iot_card,导致绑定设备且套餐挂在 device_id 时无法命中可扣套餐。
  2. 精度缺口:上游流量读数是 float64 MB,扣减时转 int64,会丢失 <1MB 的高频增量。

系统现有分层为 Handler → Service → Store → Model扣减核心在 UsageService.DeductDataUsage,轮询和手动刷新都依赖该能力。为避免分叉逻辑,本次设计将修复集中在 Service 层并保持调用链兼容。

Goals / Non-Goals

Goals:

  • 修复绑定设备卡的套餐扣减命中问题,保证设备级套餐能被正确扣减。
  • 修复小数增量丢失问题,保证高频小流量场景下长期扣减准确。
  • 明确流量字段语义:current_month_usage_mb 与运营商周期口径解耦。
  • 保持现有 API、数据库表结构和任务调度不破坏。

Non-Goals:

  • 不重构轮询体系,不调整 Asynq 任务类型。
  • 不新增数据库迁移。
  • 不改变套餐优先级策略(仍是加油包优先、主套餐兜底)。
  • 不新增自动化测试范围(按项目约束,仅提供手动验证方案)。

Decisions

决策 1载体命中策略下沉到 UsageService

  • 方案:DeductDataUsage 先按请求载体查询生效套餐;若 iot_card 未命中且该卡存在有效设备绑定,则回退按 device 查询并扣减。
  • 理由:
    • 轮询与手动刷新共用一套扣减逻辑,避免在多个 Handler/Service 重复实现路由判断。
    • 符合分层约束,业务判断集中在 Service 层Store 层保持通用查询职责。
  • 备选方案:在 polling_carddata_handler 单点改传 device
    • 未采用原因:手动刷新链路仍会错;后续新入口也会重复踩坑。

决策 2小数增量使用“余量累加”而非直接四舍五入

  • 方案:保留扣减单位为整数 MB但把小数部分按载体维度写 Redis 余量键(带 TTL后续增量先与余量相加再计算本次可扣整数 MB。
  • 理由:
    • 不改变 tb_package_usage.data_usage_mb 的整数结构,兼容现有统计与查询。
    • 避免每次四舍五入带来的长期偏差。
    • Redis 读写轻量,满足轮询高频场景性能要求。
  • 备选方案:直接把 data_usage_mb 改为小数。
    • 未采用原因:涉及模型、查询、统计口径和历史数据兼容,变更面过大。

决策 3字段口径保持双轨不再混用

  • 方案:
    • current_month_usage_mb:继续定义为“系统自然月累计(卡维度)”。
    • last_gateway_reading_mb:用于表达“运营商当前周期累计读数(卡维度)”。
  • 理由:
    • 运营商重置日可能不是每月 1 号,不能用自然月字段替代运营商周期口径。
    • 绑定设备不改变流量采集主体,流量仍来自卡 ICCID。
  • 备选方案:让 current_month_usage_mb 直接改为运营商周期口径。
    • 未采用原因:会破坏现有自然月统计语义,影响已有展示与分析逻辑。

Risks / Trade-offs

  • 风险:余量键读写失败时会影响小数累计精度。

    • Mitigation失败仅降级为“本次不累计小数”并记录告警日志主流程不中断。
  • 风险:iot_card -> device 回退可能在极端并发下出现重复判断。

    • Mitigation扣减仍在事务内按当前生效套餐计算保持幂等结果。
  • 风险:前端继续误用 current_month_usage_mb 作为运营商周期口径。

    • Mitigation在 spec 和接口描述中显式标注口径,前端改读 last_gateway_reading_mb

Migration Plan

  1. 先上线 Service 层扣减修复(载体回退 + 小数余量)。
  2. 通过日志与数据库手动核对重点卡(绑定设备卡、低流量高频卡)。
  3. 前端切换运营商周期展示口径到 last_gateway_reading_mb
  4. 观察 1-2 个运营商结算周期,无异常后关闭问题。

回滚策略:

  • 若出现异常,可回滚到旧版本代码;该变更无 DB schema 迁移,不涉及数据结构回滚。

Open Questions

  • 余量 Redis 键 TTL 的最终值(建议 7 天)是否需要按运营商周期动态设置。
  • 是否需要在管理端 realtime-status 直接返回 last_gateway_reading_mb(当前先由前端按现有可用字段接入)。