## 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
