## ADDED Requirements ### Requirement: 实名状态查询 系统 SHALL 调用 Gateway API 查询卡的实名状态,并更新数据库和缓存。 #### Scenario: 查询未实名卡 - **WHEN** 实名检查任务执行,卡A的 ICCID 为 "89860123456789012345",real_name_status=0 - **THEN** 系统调用 Gateway.QueryRealnameStatus(ICCID),获取实名状态响应 #### Scenario: 实名状态未变化 - **WHEN** Gateway 返回实名状态为"未实名"(status=0),与数据库当前值相同 - **THEN** 系统更新 last_realname_check_at 字段,不触发配置重新匹配 #### Scenario: 实名状态变化为已实名 - **WHEN** Gateway 返回实名状态为"已实名"(status=1),数据库当前值为 0 - **THEN** 系统更新 real_name_status=1 和 last_realname_check_at,触发 OnCardStatusChanged() 重新匹配配置 #### Scenario: Gateway API 超时 - **WHEN** 调用 Gateway API 超时(>30秒) - **THEN** 系统记录错误日志,任务失败,不重试,卡重新入队(按原计划下次检查) #### Scenario: Gateway API 返回错误 - **WHEN** Gateway API 返回业务错误(如卡号不存在) - **THEN** 系统记录错误日志,不更新数据库,任务失败,卡重新入队 ### Requirement: 并发控制 系统 SHALL 使用 Redis 信号量控制实名检查的并发数,避免打爆 Gateway。 #### Scenario: 获取并发信号量成功 - **WHEN** 实名检查任务开始执行,当前并发数为 30,配置的最大并发数为 50 - **THEN** 系统使用 INCR 增加计数,获取信号量成功,执行任务 #### Scenario: 并发已满 - **WHEN** 实名检查任务开始执行,当前并发数为 50,配置的最大并发数为 50 - **THEN** 系统 INCR 后发现超过限制,DECR 归还,任务返回 SkipRetry(不执行,等待下次调度) #### Scenario: 任务完成释放信号量 - **WHEN** 实名检查任务完成(成功或失败) - **THEN** 系统使用 DECR 释放信号量 ### Requirement: 数据库更新 系统 SHALL 更新卡的实名状态和最后检查时间到数据库。 #### Scenario: 更新实名状态 - **WHEN** Gateway 返回实名状态为 1 - **THEN** 系统执行 `UPDATE iot_cards SET real_name_status=1, last_realname_check_at=NOW() WHERE id=?` #### Scenario: 数据库更新失败 - **WHEN** 数据库更新失败(如连接断开) - **THEN** 系统记录错误日志,任务失败,卡重新入队 ### Requirement: Redis 缓存更新 系统 SHALL 同步更新 Redis 缓存中的卡信息。 #### Scenario: 更新缓存实名状态 - **WHEN** 数据库更新成功 - **THEN** 系统使用 HSET 更新 Redis 缓存(polling:card:{card_id})的 real_name_status 和 last_realname_check_at 字段 #### Scenario: 缓存更新失败不影响主流程 - **WHEN** Redis 更新失败 - **THEN** 系统记录警告日志,但任务仍视为成功(缓存可以通过定时同步或懒加载恢复) ### Requirement: 配置重新匹配 系统 SHALL 在实名状态变化时重新匹配轮询配置。 #### Scenario: 从未实名变为已实名 - **WHEN** 卡A从 real_name_status=0 变为 1 - **THEN** 系统调用 OnCardStatusChanged(),重新匹配配置(从"未实名卡配置"切换到"已实名卡配置") #### Scenario: 切换到低频检查 - **WHEN** 卡A匹配的配置从"未实名-30秒"切换到"已实名-3600秒" - **THEN** 系统更新 Redis 缓存的 matched_config_id,更新队列中的 next_check_time(下次检查时间推迟) #### Scenario: 不再匹配任何配置 - **WHEN** 卡A状态变化后,不再匹配任何启用的配置 - **THEN** 系统从所有队列移除该卡 ### Requirement: 重新入队 系统 SHALL 在检查完成后,将卡重新加入轮询队列。 #### Scenario: 计算下次检查时间 - **WHEN** 实名检查完成,当前配置的检查间隔为 60 秒 - **THEN** 系统计算 next_check_time = NOW() + 60秒 #### Scenario: 加回队列 - **WHEN** 计算出下次检查时间 - **THEN** 系统使用 ZADD 将卡ID和时间戳加入 polling:queue:realname #### Scenario: 任务失败也重新入队 - **WHEN** 任务因 Gateway 超时失败 - **THEN** 系统仍然将卡重新入队(按原计划下次检查),不阻塞后续检查 ### Requirement: 行业卡特殊处理 系统 SHALL 识别行业卡,行业卡无需实名检查。 #### Scenario: 行业卡不参与实名检查 - **WHEN** 轮询配置匹配卡时,卡A的 card_category="industry" - **THEN** 如果配置启用实名检查,系统跳过该卡或使用不启用实名检查的配置 #### Scenario: 行业卡配置示例 - **WHEN** 管理员创建配置,card_category="industry",real_name_check_enabled=false,card_data_check_enabled=true - **THEN** 行业卡只参与流量检查,不参与实名检查 ### Requirement: 监控统计 系统 SHALL 记录实名检查的成功率和耗时。 #### Scenario: 记录成功统计 - **WHEN** 实名检查成功完成,耗时 123 毫秒 - **THEN** 系统更新 Redis Hash(polling:stats:realname),增加 success_count_1h,累加 total_duration_1h #### Scenario: 记录失败统计 - **WHEN** 实名检查失败(Gateway 超时) - **THEN** 系统更新 Redis Hash,增加 failure_count_1h #### Scenario: 每小时重置计数器 - **WHEN** 每小时整点(如 10:00:00) - **THEN** 系统重置计数器(success_count_1h、failure_count_1h、total_duration_1h),保持时间窗口滚动 ### Requirement: 日志记录 系统 SHALL 记录详细的实名检查日志,便于排查问题。 #### Scenario: 记录开始日志 - **WHEN** 实名检查任务开始执行 - **THEN** 系统记录 Info 日志,包含 card_id、iccid、config_id #### Scenario: 记录成功日志 - **WHEN** 实名检查成功完成 - **THEN** 系统记录 Info 日志,包含 card_id、iccid、old_status、new_status、duration_ms #### Scenario: 记录失败日志 - **WHEN** 实名检查失败 - **THEN** 系统记录 Error 日志,包含 card_id、iccid、error 详情 #### Scenario: 记录状态变化日志 - **WHEN** 实名状态发生变化 - **THEN** 系统记录 Info 日志,包含 card_id、old_status、new_status、old_config、new_config