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,196 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 卡流量查询
|
||||
|
||||
系统 SHALL 调用 Gateway API 查询卡的流量使用情况,并处理跨月流量计算。
|
||||
|
||||
#### Scenario: 查询卡流量
|
||||
|
||||
- **WHEN** 流量检查任务执行,卡A的 ICCID 为 "89860123456789012345"
|
||||
- **THEN** 系统调用 Gateway.QueryFlow(ICCID),获取流量响应(包含月总流量 MB)
|
||||
|
||||
#### Scenario: Gateway 返回月总流量
|
||||
|
||||
- **WHEN** Gateway 返回流量数据,total_usage_mb=500(本自然月累计)
|
||||
- **THEN** 系统获取到月总流量数据,准备计算增量
|
||||
|
||||
#### Scenario: Gateway API 超时
|
||||
|
||||
- **WHEN** 调用 Gateway API 超时(>30秒)
|
||||
- **THEN** 系统记录错误日志,任务失败,不重试,卡重新入队(按原计划下次检查)
|
||||
|
||||
#### Scenario: Gateway API 返回错误
|
||||
|
||||
- **WHEN** Gateway API 返回业务错误(如卡号不存在)
|
||||
- **THEN** 系统记录错误日志,不更新数据库,任务失败,卡重新入队
|
||||
|
||||
### Requirement: 跨月流量计算
|
||||
|
||||
系统 SHALL 正确处理跨月流量计算,识别月份切换并重置月度统计。
|
||||
|
||||
#### Scenario: 首次查询流量(冷启动)
|
||||
|
||||
- **WHEN** 卡A从未查询过流量,current_month_start_date 为 NULL
|
||||
- **THEN** 系统初始化 current_month_start_date=当前月1日,current_month_usage_mb=Gateway返回值,last_month_total_mb=0
|
||||
|
||||
#### Scenario: 同月内流量增长
|
||||
|
||||
- **WHEN** 卡A上次查询在本月(current_month_start_date=2024-01-01),Gateway 返回 total_usage_mb=500,数据库当前 current_month_usage_mb=400
|
||||
- **THEN** 系统计算增量 100 MB,更新 current_month_usage_mb=500
|
||||
|
||||
#### Scenario: 跨月流量重置
|
||||
|
||||
- **WHEN** 卡A上次查询在上月(current_month_start_date=2024-01-01),当前时间为 2024-02-05,Gateway 返回 total_usage_mb=50(新月重置后)
|
||||
- **THEN** 系统检测到跨月,保存 last_month_total_mb=400(上月结束值),重置 current_month_start_date=2024-02-01,current_month_usage_mb=50
|
||||
|
||||
#### Scenario: Gateway 返回值回退(异常)
|
||||
|
||||
- **WHEN** Gateway 返回 total_usage_mb=300,小于数据库当前值 current_month_usage_mb=400(同月内)
|
||||
- **THEN** 系统记录警告日志,怀疑数据异常,但仍更新为 Gateway 值(信任 Gateway 数据源)
|
||||
|
||||
### Requirement: 数据库更新
|
||||
|
||||
系统 SHALL 更新卡的流量字段到数据库。
|
||||
|
||||
#### Scenario: 更新流量字段
|
||||
|
||||
- **WHEN** 计算出增量流量 100 MB
|
||||
- **THEN** 系统执行 `UPDATE iot_cards SET current_month_usage_mb=500, current_month_start_date='2024-01-01', last_realname_check_at=NOW() WHERE id=?`
|
||||
|
||||
#### Scenario: 数据库更新失败
|
||||
|
||||
- **WHEN** 数据库更新失败(如连接断开)
|
||||
- **THEN** 系统记录错误日志,任务失败,卡重新入队
|
||||
|
||||
### Requirement: Redis 缓存更新
|
||||
|
||||
系统 SHALL 同步更新 Redis 缓存中的卡流量信息。
|
||||
|
||||
#### Scenario: 更新缓存流量字段
|
||||
|
||||
- **WHEN** 数据库更新成功
|
||||
- **THEN** 系统使用 HSET 更新 Redis 缓存(polling:card:{card_id})的 current_month_usage_mb、current_month_start_date、last_month_total_mb 字段
|
||||
|
||||
#### Scenario: 缓存更新失败不影响主流程
|
||||
|
||||
- **WHEN** Redis 更新失败
|
||||
- **THEN** 系统记录警告日志,但任务仍视为成功(缓存可以通过定时同步或懒加载恢复)
|
||||
|
||||
### Requirement: 流量历史记录
|
||||
|
||||
系统 SHALL 记录流量变化历史到 data_usage_records 表。
|
||||
|
||||
#### Scenario: 记录流量增量
|
||||
|
||||
- **WHEN** 计算出增量流量 100 MB
|
||||
- **THEN** 系统插入记录到 data_usage_records 表(card_id、usage_mb=100、usage_type='data'、recorded_at=NOW())
|
||||
|
||||
#### Scenario: 跨月时记录上月总量
|
||||
|
||||
- **WHEN** 检测到跨月,上月总流量为 400 MB
|
||||
- **THEN** 系统插入一条月度汇总记录(card_id、usage_mb=400、usage_type='monthly_summary'、recorded_at=上月最后一天)
|
||||
|
||||
#### Scenario: 历史记录插入失败
|
||||
|
||||
- **WHEN** 历史记录插入失败
|
||||
- **THEN** 系统记录警告日志,但任务仍视为成功(历史记录非关键路径)
|
||||
|
||||
### Requirement: 触发套餐检查
|
||||
|
||||
系统 SHALL 在流量更新后触发关联套餐的流量检查。
|
||||
|
||||
#### Scenario: 卡有关联套餐
|
||||
|
||||
- **WHEN** 卡A更新流量后,card_package_id 不为空
|
||||
- **THEN** 系统将该套餐加入 polling:manual:package 手动触发队列,优先检查套餐流量
|
||||
|
||||
#### Scenario: 卡无关联套餐
|
||||
|
||||
- **WHEN** 卡A更新流量后,card_package_id 为空
|
||||
- **THEN** 系统不触发套餐检查
|
||||
|
||||
#### Scenario: 卡关联设备级套餐
|
||||
|
||||
- **WHEN** 卡A属于设备D,设备D有套餐
|
||||
- **THEN** 系统将设备的套餐加入手动触发队列
|
||||
|
||||
### Requirement: 并发控制
|
||||
|
||||
系统 SHALL 使用 Redis 信号量控制流量检查的并发数。
|
||||
|
||||
#### Scenario: 获取并发信号量成功
|
||||
|
||||
- **WHEN** 流量检查任务开始执行,当前并发数为 30,配置的最大并发数为 50
|
||||
- **THEN** 系统使用 INCR 增加计数,获取信号量成功,执行任务
|
||||
|
||||
#### Scenario: 并发已满
|
||||
|
||||
- **WHEN** 流量检查任务开始执行,当前并发数为 50,配置的最大并发数为 50
|
||||
- **THEN** 系统 INCR 后发现超过限制,DECR 归还,任务返回 SkipRetry(不执行,等待下次调度)
|
||||
|
||||
#### Scenario: 任务完成释放信号量
|
||||
|
||||
- **WHEN** 流量检查任务完成(成功或失败)
|
||||
- **THEN** 系统使用 DECR 释放信号量
|
||||
|
||||
### Requirement: 重新入队
|
||||
|
||||
系统 SHALL 在检查完成后,将卡重新加入轮询队列。
|
||||
|
||||
#### Scenario: 计算下次检查时间
|
||||
|
||||
- **WHEN** 流量检查完成,当前配置的检查间隔为 1800 秒
|
||||
- **THEN** 系统计算 next_check_time = NOW() + 1800秒
|
||||
|
||||
#### Scenario: 加回队列
|
||||
|
||||
- **WHEN** 计算出下次检查时间
|
||||
- **THEN** 系统使用 ZADD 将卡ID和时间戳加入 polling:queue:carddata
|
||||
|
||||
#### Scenario: 任务失败也重新入队
|
||||
|
||||
- **WHEN** 任务因 Gateway 超时失败
|
||||
- **THEN** 系统仍然将卡重新入队(按原计划下次检查),不阻塞后续检查
|
||||
|
||||
### Requirement: 监控统计
|
||||
|
||||
系统 SHALL 记录流量检查的成功率和耗时。
|
||||
|
||||
#### Scenario: 记录成功统计
|
||||
|
||||
- **WHEN** 流量检查成功完成,耗时 234 毫秒
|
||||
- **THEN** 系统更新 Redis Hash(polling:stats:carddata),增加 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_usage_mb、new_usage_mb、increment_mb、duration_ms
|
||||
|
||||
#### Scenario: 记录失败日志
|
||||
|
||||
- **WHEN** 流量检查失败
|
||||
- **THEN** 系统记录 Error 日志,包含 card_id、iccid、error 详情
|
||||
|
||||
#### Scenario: 记录跨月日志
|
||||
|
||||
- **WHEN** 检测到跨月
|
||||
- **THEN** 系统记录 Info 日志,包含 card_id、old_month、new_month、last_month_total_mb
|
||||
Reference in New Issue
Block a user