fix: 补全三处轮询覆盖缺口
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m11s

问题一: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>
This commit is contained in:
2026-04-02 14:36:24 +08:00
parent 322ded0012
commit 8980e6d999
24 changed files with 617 additions and 54 deletions

View File

@@ -50,3 +50,60 @@
- **WHEN** 轮询任务遇到 is_standalone=true 的卡
- **THEN** 任务直接跳过,不查询设备保护期
---
## 迭代更新fix-polling-coverage-gaps
### MODIFIED Requirement: 保护期一致性检查轮询任务
系统 SHALL 通过轮询调度器定时执行保护期一致性检查任务(`polling:protect`),检查绑定设备有保护期的已实名卡的网络状态,并在状态不一致时强制调网关修正。
**任务类型标识**: `protect`(与现有 `realname``carddata``package` 并列)
**Redis 队列 Key**: `RedisPollingQueueProtectKey()``"polling:queue:protect"`
**触发频率**(变更): 由 `PollingConfig.ProtectCheckInterval`(新增字段,单位秒)控制,`NULL``0` 表示该配置不参与 protect 检查,建议默认 600s与流量检查任务同频固定 10 分钟)
**初始化**:调度器启动时,`initCardsBatch``initCardPolling` SHALL 读取匹配配置的 `ProtectCheckInterval`,将卡加入 `polling:queue:protect` Sorted Set任务完成后通过 `requeueCard` 按间隔重新入队,并更新 `last_protect_check_at`
**数据模型变更**
- `tb_polling_config` 新增 `protect_check_interval INT NULL` 字段
- `tb_iot_card` 新增 `last_protect_check_at TIMESTAMP NULL` 字段(记录上次检查时间,用于计算下次入队 score
**处理逻辑**(与原 spec 一致,不变):
1. 卡未实名(`real_name_status = 0`)→ 跳过
2. 卡未绑定设备(`is_standalone = true`)→ 跳过
3. 设备有 stop 保护期且卡在线(`network_status = 1`)→ 调网关停机,更新 `network_status = 0`
4. 设备有 start 保护期且卡停机(`network_status = 0`)→ 调网关复机,更新 `network_status = 1`
5. 状态已一致 → 跳过
#### Scenario: stop 保护期内卡状态异常(开机)
- **WHEN** protect 轮询任务检查一张已实名、已绑定设备的卡,发现设备有 stop 保护期,且卡当前 `network_status = 1`(开机)
- **THEN** 任务调网关停机,更新卡 `network_status = 0`,更新 `last_protect_check_at`,记录 Info 日志
#### Scenario: start 保护期内卡状态异常(停机)
- **WHEN** protect 轮询任务检查一张已实名、已绑定设备的卡,发现设备有 start 保护期,且卡当前 `network_status = 0`(停机)
- **THEN** 任务调网关复机,更新卡 `network_status = 1`,更新 `last_protect_check_at`,记录 Info 日志
#### Scenario: 状态已一致,跳过
- **WHEN** protect 轮询任务检查一张卡,设备有 stop 保护期,卡已是停机状态(`network_status = 0`
- **THEN** 任务跳过,不调网关,不更新 DB更新 `last_protect_check_at`
#### Scenario: 未实名卡跳过保护期逻辑
- **WHEN** protect 轮询任务遇到 `real_name_status = 0` 的卡
- **THEN** 任务直接跳过,不检查保护期,不调网关
#### Scenario: 独立卡(未绑定设备)跳过
- **WHEN** protect 轮询任务遇到 `is_standalone = true` 的卡
- **THEN** 任务直接跳过,不查询设备保护期
#### Scenario: PollingConfig 未配置 ProtectCheckInterval卡不入 protect 队列
- **WHEN** 调度器初始化时,卡匹配到的 PollingConfig 的 `protect_check_interval` 为 NULL 或 0
- **THEN** 该卡不被加入 `polling:queue:protect`protect 任务不对该卡执行