Files
junhong_cmp_fiber/openspec/changes/add-card-traffic-queue-and-observed-deduction/tasks.md
邹进 9c3c258f16
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m27s
feat: 卡流量读数先过采集队列再入账,扣减按采集时刻确定套餐
- 每卡 Redis 采集队列(默认 N=3、跨天清空、回退容差 1 MB),攒满且不下降才写最后一个点
- 测试环境全量切换日快照计量,下线灰度名单、切换水位与卡表基线扣费;全部卡置待建起点
- 读数区分没给值与 0;有套餐的卡读数 0 也写快照与日用量 0 行;重置日比较基准为 0 照常防旧值
- 新增采集记录(按月分区保留 90 天)、人工冲正流水与后台接口、采集告警
- 扣减按采集时刻有效套餐查找,只对生效中套餐做状态迁移,查不到套餐按成功跳过
- 换货新卡(新设备下的卡)打待建起点;采集间隔上限只校验启用中的轮询配置

Change: add-card-traffic-queue-and-observed-deduction

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:18:47 +09:00

75 lines
7.9 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.
## 0. 前置
- [ ] 0.1 经维护者确认,归档 `rebuild-card-traffic-metering-and-stop-resume`(9.2–9.5 灰度与切换水位验证作废)与 `add-tiered-polling-and-pre-activation-settlement`,确认 `openspec/specs/card-traffic-metering/spec.md` 存在且包含本 Change 修改与删除的要求。
## 1. 迁移与模型
- [x] 1.1 新增成对迁移:采集记录表(按 `observed_at` 按月分区,字段见方案 7.1,索引 `(iot_card_id, observed_at DESC)`、`observation_id`),预建当月与下月分区;down 删除。
- [x] 1.2 新增成对迁移:冲正流水表(字段见方案第 11 节);down 删除。
- [x] 1.3 新增成对迁移:`tb_iot_card` 增加待建起点标记,up 内把全部卡置位;down 删除该列。
- [x] 1.4 新增成对迁移:套餐日用量 `(package_usage_id, date)` 唯一索引,建前合并已有重复行;down 删除索引。(测试库已有唯一索引 `idx_package_usage_daily_record_unique` 且无重复行,无需新迁移)
- [x] 1.5 新增对应 model;采集记录分区的按月新建与过期整月删除任务(保留 90 天)。
## 2. 读数有效性
- [x] 2.1 `internal/gateway/models.go`:流量查询结果的已用流量改为零值表示"没值"的类型,区分 null/空/无法解析/缺字段与 0;其他使用 `FlexFloat` 的字段不变。
- [x] 2.2 `internal/task/polling_carddata_handler.go`:按方案第 3 节把结果分为有效、失败、限流、响应无效、无读数、读数无效。
- [x] 2.3 `internal/domain/cardflow/rules.go`:`ValidReading` 放宽为有限且 ≥ 0;`isStale` 去掉 `ThisMonthBeforeResetMax > 0` 条件;同步注释。
## 3. 采集队列
- [x] 3.1 新增队列组件:key `traffic:queue:{card_id}`,Lua 原子完成跨天清空、过期丢弃、按采集时间插入去重、保留最后 N 个、设置过期(跨天清空开启时为次日 01:00 上海时间,关闭时为最长点龄后),返回队列与新点是否在队尾。
- [x] 3.2 写入判断:新点在队尾、队列满 N、相邻两点不下降(容差内算持平);返回写入或不写原因(队列未满、读数下降、乱序)。
- [x] 3.3 系统配置注册 `traffic.queue.size`(默认 3,最小 2)、`traffic.queue.clear_on_new_day`(默认 true)、`traffic.queue.max_point_age_hours`(默认 72)、`traffic.queue.drop_tolerance_mb`(默认 1),读法与 `package.silence.enabled` 相同(带短缓存)。
- [x] 3.4 轮询配置保存与启用时、队列长度或跨天清空开关修改时,校验启用中轮询配置的流量采集间隔上限;停用配置不校验。
## 4. 有套餐判断与观测写入
- [x] 4.1 package 服务新增"采集时刻有效套餐"查询(状态生效中/已用完/已过期、`activated_at ≤ observed_at` 或为空、`expires_at` 为空或晚于采集时间、未退款,卡没有时看当前绑定设备),供观测与扣减共用。
- [x] 4.2 `traffic_calc_policy.go`:`hasActivePackageForCard` 改调 4.1;删除灰度名单与计量模式;删除 `pkg/config` 中 `TrafficCalc` 的模式与名单配置及部署变量。
- [x] 4.3 `traffic.go`、`traffic_flow.go`:只在队列写入点进入快照;删除未切换分支、`firstSwitch` 与切换水位读写;卡表基线算法不再产生扣费增量,`last_gateway_reading_mb` 不再使用。
- [x] 4.4 写入范围:有套餐读数 0 写快照、没套餐读数 0 不写;Δ 有套餐发事件(带 `observed_at`、`observation_id`),没套餐只记已结算。
- [x] 4.5 待建起点:带标记的卡第一次写入时写快照、本月最大全部记为已结算、不发事件、清标记,采集记录判断为建起点。
- [x] 4.6 观测事务内当天第一次写入时,调用 package 服务为采集时刻每个有效套餐确保当天日用量 0 行。
- [x] 4.7 通道阈值判定只在写入点、读数 > 0 且未触发防旧值时调用;卡表本月用量对全部卡用本月最大快照覆盖;日流量对全部卡读快照。
- [x] 4.8 采集记录:有套餐的卡每次采集写一条(含失败、读数 0、被拦下),字段按方案 7.1。
## 5. 其他读数来源
- [x] 5.1 人工刷新过队列,接口返回本次读数、是否写入与原因;更新路由文档说明。
- [x] 5.2 激活前结算过队列;未写入时用已有本月最大快照结算、不新写快照,采集记录判断为"激活前结算用已有快照";删除"未切换卡不做结算"的分支。
## 6. 换卡
- [x] 6.1 `internal/service/exchange`:换卡完成事务内给新卡置待建起点标记;设备换货时给新设备当前绑定的每张卡置位;不调用运营商接口。
## 7. 扣减
- [x] 7.1 `internal/infrastructure/cardobservation/event.go`:把 `observed_at`、`observation_id` 传给扣减;日流量缓冲 key 改用 `cardflow.ShanghaiDate(observed_at)`。
- [x] 7.2 `DeductDataUsage` 按采集时刻调用 4.1 查套餐;排序、容量、超出承接与小数累积不变。
- [x] 7.3 状态迁移只作用于当前生效中套餐;已用完、已过期只累加不改状态;停机判断仍按当前状态。
- [x] 7.4 查不到套餐时写告警与跳过审计,事件按成功处理不重试。(没有套餐可挂审计资源,跳过只写带 metric 的告警日志)
- [x] 7.5 采集早于套餐 `last_reset_at` 的照扣在重置后周期,审计记录 `observed_at` 与 `last_reset_at`。
- [x] 7.6 `updateDailyRecord` 改为 upsert(当天用量累加、累计用量覆盖),日期取采集时间上海日期;提供"确保当天日用量行"方法。
## 8. 冲正与后台接口
- [x] 8.1 冲正用例:事务内 `SELECT ... FOR UPDATE` 锁套餐行,按当前值加减,结果 < 0 拒绝;写流水、更新已用量、按阈值重算状态(已过期/已失效只改已用量);写审计 `package_usage.adjust_traffic`;提交后调用 `evaluator.EvaluateAndAct`。
- [x] 8.2 后台接口:冲正(必填原因、可选关联采集与事件)、冲正流水查询、采集记录按卡与时间范围查询;权限与现有套餐管理一致。(原"修改资产套餐已用量"接口改为按差额写冲正流水)
- [x] 8.3 同步 `cmd/api/docs.go`、`cmd/gendocs/main.go`,运行 `go run cmd/gendocs/main.go`。
## 9. 告警
- [x] 9.1 开启轮询且有套餐的卡一整天没有已写入;开启轮询的卡待建起点超过 1 天;单卡读数下降占比过高或全局拦截率突增;乱序突增;重置日防旧值命中数统计。不轮询的卡不告警。
## 10. 构建与测试环境验证
- [x] 10.1 对变更 Go 文件执行 `gofmt -w`,运行 `go build ./cmd/api ./cmd/worker`、`openspec validate add-card-traffic-queue-and-observed-deduction --strict`。
- [ ] 10.2 按 ENG-TEST-001 在测试环境执行迁移 up/down/up,核对新表、分区、唯一索引与全部卡待建起点标记。
- [ ] 10.3 用构造的测试卡(参照 `cmd/zz_tmp_cardflow_verify`)逐项核对:正常累加、中间点暴涨被拦、清零前后不混写、容差内回退写入、乱序不写、跨天清空、读数 0 有/没套餐的写入范围、null 与缺字段不入队。
- [ ] 10.4 重置日:配置重置日 1 与 27 各一张卡,核对重置日全天防旧值、比较基准为 0 时当天按 0 计且次日如实写入、重置日早上的旧数不更新前一天快照。
- [ ] 10.5 全量切换与换卡:切换后第一次写入只建起点不扣费;换卡新卡写入 55 建起点、58 扣 3;起点读数异常不被当起点。
- [ ] 10.6 扣减:已用完在有效期内的套餐承接超出;采集后到期照扣、状态不被改回已用完;采集后退款跳过不重试;套餐日用量按采集日期 upsert、没扣写 0。
- [ ] 10.7 冲正:退回后状态恢复并交给停复机判断;结果为负被拒;与扣减并发不覆盖;因未实名停机的卡冲正后不复机。
- [ ] 10.8 人工刷新返回写入结果;激活前结算未写入时用已有快照结算;采集记录可从扣费审计经采集 ID 追到;轮询配置间隔校验只拦启用中配置。