## MODIFIED Requirements ### Requirement: 流量耗尽自动复机 系统 SHALL 在套餐激活或流量重置后,对满足以下全部条件的卡自动调用运营商接口复机: 1. `stop_reason = "traffic_exhausted"`(仅限流量耗尽停机,手动停机不自动复机) 2. `real_name_status = 1`(已完成实名认证) 3. `network_status = 0`(当前处于停机状态) 系统 SHALL 在执行复机前获取 Redis 分布式锁(Key:`card:resume:lock:{cardID}`,TTL 30s),防止并发重复调用 Gateway。 #### Scenario: 所有条件满足时自动复机 - **GIVEN** 卡 C1 满足:`stop_reason=traffic_exhausted`,`real_name_status=1`,`network_status=0` - **WHEN** `ResumeCardIfStopped(ctx, "iot_card", cardID)` 被调用 - **THEN** 系统获取分布式锁,调用 `gateway.StartCard`,更新 `network_status=1`,清空 `stop_reason`,释放锁 #### Scenario: 未实名卡跳过自动复机 - **GIVEN** 卡 C2 `stop_reason=traffic_exhausted`,`real_name_status=0`,`network_status=0` - **WHEN** `ResumeCardIfStopped` 被调用 - **THEN** 系统记录 WARN 日志,静默跳过,不调用 Gateway,函数返回 nil #### Scenario: 手动停机的卡不被自动复机 - **GIVEN** 卡 C3 `stop_reason=manual`,`real_name_status=1`,`network_status=0` - **WHEN** `ResumeCardIfStopped` 被调用 - **THEN** 系统检测到 `stop_reason != traffic_exhausted`,静默跳过 #### Scenario: 并发调用时只有一次复机执行 - **GIVEN** 卡 C4 满足复机条件,复机操作正在执行(分布式锁被持有) - **WHEN** 第二个 `ResumeCardIfStopped` 调用同时到达 - **THEN** 第二个调用获锁失败,直接返回,不调用 Gateway #### Scenario: 设备类型载体复机遍历所有绑定卡 - **GIVEN** 设备 D1 绑定 3 张卡(C1 已实名停机、C2 已实名停机、C3 未实名停机) - **WHEN** `ResumeCardIfStopped(ctx, "device", deviceID)` 被调用 - **THEN** C1 和 C2 自动复机,C3 因未实名跳过 ## ADDED Requirements ### Requirement: 套餐超额停机必须写入 stop_reason 和 stopped_at 系统 SHALL 在套餐虚流量超额触发停机时(`stopCards` 函数),写入 `stop_reason=traffic_exhausted` 和 `stopped_at` 到 IoT 卡记录。 #### Scenario: 虚流量超额停机完整写入 DB - **GIVEN** 卡 C1 当月流量超过套餐 `virtual_data_mb` 上限,`network_status=1` - **WHEN** `HandlePackageCheck` 的 `stopCards` 执行 - **THEN** Gateway 调用成功后,DB 更新包含:`network_status=0`,`stop_reason=traffic_exhausted`,`stopped_at=now()`,`updated_at=now()` ### Requirement: 保护期停机使用常量 stop_reason 系统 SHALL 在保护期一致性检查停机时,使用 `constants.StopReasonProtectPeriod` 常量(值:`protect_period`)记录 `stop_reason`。 #### Scenario: 保护期停机写入正确原因 - **GIVEN** 设备处于停机保护期,绑定的卡 C1 `network_status=1`(开机状态不一致) - **WHEN** `HandleProtectConsistencyCheck` 触发停机 - **THEN** DB 中 `stop_reason=protect_period`(使用常量,非中文硬编码) - **AND** `ResumeCardIfStopped` 调用时因 `stop_reason != traffic_exhausted` 跳过,不会错误触发自动复机