Files
huang 804145332b
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 44s
chore: 归档轮询系统实现变更 (polling-system-implementation)
已完成千万级卡规模轮询系统的完整实现和集成测试验证,将变更归档到 openspec/changes/archive/2026-02-10-polling-system-implementation/

主要成果:
- 三大轮询任务:实名检查、卡流量检查、套餐流量检查
- 快速启动(<10秒)和渐进式初始化
- 完整运营工具:配置管理、并发控制、监控面板、告警系统、数据清理、手动触发
- 任务完成度:215/216(99.5%)
- 所有 24 个新增接口已生成 OpenAPI 文档

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-10 10:28:47 +08:00

6.1 KiB
Raw Blame History

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=falsecard_data_check_enabled=true
  • THEN 行业卡只参与流量检查,不参与实名检查

Requirement: 监控统计

系统 SHALL 记录实名检查的成功率和耗时。

Scenario: 记录成功统计

  • WHEN 实名检查成功完成,耗时 123 毫秒
  • THEN 系统更新 Redis Hashpolling: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