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>
62 lines
2.9 KiB
Markdown
62 lines
2.9 KiB
Markdown
# 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 日志,不报错。
|