Files
junhong_cmp_fiber/openspec/specs/device-package-exhausted-stop/spec.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

62 lines
2.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.
# Spec: 设备级套餐耗尽触发绑定卡停机
## 业务背景
### 为什么需要设备级套餐耗尽停机
**现状问题**
- 设备套餐(`carrier_type = "device"`)流量耗尽时,`checkAndTriggerSuspension` 缺少对绑定卡的停机逻辑
- 仅有 `iot_card` 类型载体的停机分支,设备类型无对应处理
- 导致设备套餐流量耗尽后,绑定卡仍保持在线状态,无法及时停机
**业务目标**
- 设备套餐流量耗尽时,自动对所有绑定卡触发停机检查
- 与 iot_card 类型保持行为一致性
- 幂等安全,重复触发不产生副作用
---
## ADDED Requirements
### Requirement: 设备级套餐耗尽时触发绑定卡停机
当套餐载体为设备(`carrier_type = "device"`)且所有生效套餐流量耗尽时,系统 SHALL 查询该设备绑定的所有 IoT 卡,并对每张卡异步触发停机检查(`CheckAndStopCard`)。
**触发位置**`UsageService.checkAndTriggerSuspension`,在确认无生效套餐后执行。
**幂等保护**`CheckAndStopCard` 内部检查卡是否已为停机状态(`network_status = 0`),重复调用不会重复调网关。
**仅处理绑定卡**:通过 `DeviceSimBindingStore.ListByDeviceID` 获取当前绑定关系(`bind_status = 1`),未绑定或已解绑的卡不受影响。
#### Scenario: 设备套餐流量耗尽,绑定卡在线
- **WHEN** `DeductDataUsage``carrier_type = "device"` 场景下扣减流量后,`checkAndTriggerSuspension` 发现该设备无生效套餐(`status IN (0,1)`
- **THEN** 系统查询该设备的绑定卡列表,对每张在线(`network_status = 1`)且已实名(`real_name_status = 1`)的卡异步调用 `CheckAndStopCard`,将卡停机(`network_status = 0``stop_reason = "traffic_exhausted"`
#### Scenario: 设备套餐流量耗尽,绑定卡已停机
- **WHEN** `checkAndTriggerSuspension` 触发设备级停机检查,但绑定卡已经是停机状态(`network_status = 0`
- **THEN** `CheckAndStopCard` 检查到卡已停机,跳过网关调用,不产生重复操作
#### Scenario: 设备无绑定卡
- **WHEN** `checkAndTriggerSuspension` 触发设备级停机检查,但该设备当前无有效绑定记录
- **THEN** 系统静默跳过,不报错,记录 Debug 日志
#### Scenario: 套餐载体为 iot_card不受影响
- **WHEN** `DeductDataUsage``carrier_type = "iot_card"` 场景下触发停机检查
- **THEN** 仅对该卡本身执行 `CheckAndStopCard`,不查询设备绑定关系(与现有行为一致)
---
## 实现说明
### 依赖注入变更
`UsageService` 新增 `deviceSimBindingStore *postgres.DeviceSimBindingStore` 字段,通过构造函数注入,`bootstrap` 层负责传入。
### 异步触发模式
对每张绑定卡启动独立 goroutine 异步执行 `CheckAndStopCard`,不阻塞当前流量扣减流程。若 `stopResumeCallback == nil`,仅记录 Warn 日志,不报错。