Files
junhong_cmp_fiber/openspec/changes/archive/2026-04-02-fix-polling-coverage-gaps/proposal.md
huang 8980e6d999
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m11s
fix: 补全三处轮询覆盖缺口
问题一:protect 轮询任务从未被调度器初始化入队
PollingConfig 新增 protect_check_interval 字段(NULL=不参与),
调度器 initCardsBatch/initCardPolling/requeueCard 补全 protect 队列
初始化逻辑,IotCard 新增 last_protect_check_at 记录上次检查时间。
迁移文件:000102_add_polling_protect_fields

问题二:设备套餐流量耗尽时绑定卡未被停机
UsageService.checkAndTriggerSuspension 新增 carrier_type=device 分支,
通过 DeviceSimBindingStore.ListByDeviceID 查询绑定卡,对每张卡异步
触发 CheckAndStopCard,同步注入 Bootstrap 依赖。

问题三:主套餐过期后不立即停机,依赖下次轮询兜底
PackageActivationHandler.processExpiredPackage 在 updateCarrierSuspended
Status 后,若载体无后续生效套餐,立即异步调用 CheckAndStopCard(iot_card
类型直接触发,device 类型遍历绑定卡),消除停机延迟窗口。

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-02 14:36:24 +08:00

2.5 KiB
Raw Blame History

Why

轮询系统审计发现 3 个覆盖盲点:polling:protect 队列从未被初始化导致保护期一致性检查完全失效;设备级套餐耗尽时停机逻辑分支为空导致绑定卡继续在线消耗流量;套餐过期后卡的网络状态不主动停机,依赖下一轮 carddata 轮询兜底存在最长 60s 的流量泄露窗口。这三个问题均直接影响计费准确性和资源管控。

What Changes

  • 补全 polling:protect 调度器初始化:在 PollingConfig 添加 protect_check_interval 字段,在 tb_iot_card 添加 last_protect_check_at 字段,在 initCardsBatch/initCardPolling/requeueCard 补全 protect 队列写入逻辑,使保护期一致性检查能够定时执行
  • 设备级套餐耗尽触发停机:在 UsageService.checkAndTriggerSuspension 补全 carrierType == "device" 分支,查询设备绑定的所有卡并异步触发停机
  • 套餐过期时主动停机:在 PackageActivationHandler.processExpiredPackage 过期主套餐且无后续套餐时,直接异步触发 CheckAndStopCard,消除依赖轮询兜底的延迟窗口
  • 修正 getCardCondition 死代码:已实名+离线的卡永远返回 "real_name" 而非 "suspended",移除永不可达的分支

Capabilities

New Capabilities

  • device-package-exhausted-stop:设备级套餐(device_id 绑定)流量耗尽时,自动触发设备绑定卡的停机操作

Modified Capabilities

  • polling-protect-consistency:补全调度器初始化逻辑,使保护期一致性检查能够按配置间隔定时调度(新增 DB 字段 + 初始化代码)
  • auto-stop-resume:新增套餐过期主动停机场景,不再完全依赖下一次 carddata 轮询触发停机

Impact

  • 数据库迁移tb_polling_config 新增 protect_check_interval 字段;tb_iot_card 新增 last_protect_check_at 字段
  • 受影响代码
    • internal/polling/scheduler.goinitCardsBatchinitCardPollingrequeueCard
    • internal/service/package/usage_service.gocheckAndTriggerSuspension
    • internal/polling/package_activation_handler.goprocessExpiredPackageupdateIotCardSuspendedStatus
    • internal/model/polling.goPollingConfig struct
    • internal/model/iot_card.goIotCard struct
    • internal/task/polling_handler.gogetCardCondition 死代码清理
  • 无 API 变更:所有修复为内部调度逻辑,不影响对外接口
  • 需要数据库迁移两张表各增一列向下兼容nullable