feat: 实现 IoT 卡轮询系统(支持千万级卡规模)
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m35s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m35s
实现功能: - 实名状态检查轮询(可配置间隔) - 卡流量检查轮询(支持跨月流量追踪) - 套餐检查与超额自动停机 - 分布式并发控制(Redis 信号量) - 手动触发轮询(单卡/批量/条件筛选) - 数据清理配置与执行 - 告警规则与历史记录 - 实时监控统计(队列/性能/并发) 性能优化: - Redis 缓存卡信息,减少 DB 查询 - Pipeline 批量写入 Redis - 异步流量记录写入 - 渐进式初始化(10万卡/批) 压测工具(scripts/benchmark/): - Mock Gateway 模拟上游服务 - 测试卡生成器 - 配置初始化脚本 - 实时监控脚本 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
## 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
|
||||
Reference in New Issue
Block a user