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

81 lines
4.4 KiB
Markdown
Raw 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.
## 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`(当前先由前端按现有可用字段接入)。