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>
6.9 KiB
6.9 KiB
ADDED Requirements
Requirement: Worker 启动时快速初始化
系统 SHALL 在 Worker 进程启动时快速完成初始化(<10秒),不阻塞服务启动。
Scenario: 启动时只加载配置
- WHEN Worker 进程启动
- THEN 系统在 10 秒内完成配置加载(轮询配置、并发控制配置),启动调度器 Goroutine,Worker 进程可用
Scenario: 后台异步加载卡数据
- WHEN Worker 快速启动完成后
- THEN 系统在后台启动异步任务,分批加载卡数据到 Redis(每批 10万张,处理后 sleep 1秒)
Scenario: 记录初始化进度
- WHEN 后台初始化任务运行中
- THEN 系统在 Redis 存储初始化进度(已处理数量、总数量、百分比、预计完成时间),管理员可查询进度
Requirement: 渐进式卡数据加载
系统 SHALL 使用渐进式策略加载百万级卡数据到 Redis,避免打爆数据库。
Scenario: 分批读取卡数据
- WHEN 初始化任务从数据库读取卡数据
- THEN 系统使用游标(主键范围)分批读取,每批 10万张,使用
WHERE id > last_id ORDER BY id LIMIT 100000
Scenario: 批量写入 Redis
- WHEN 读取到一批卡数据后
- THEN 系统使用 Redis Pipeline 批量写入(HSET 卡信息、ZADD 队列、SADD 配置匹配关系),减少网络往返
Scenario: 限流保护数据库
- WHEN 每批卡数据处理完成后
- THEN 系统 sleep 1秒,避免连续高频查询打爆数据库
Scenario: 断点续传
- WHEN Worker 重启,初始化未完成
- THEN 系统从 Redis 读取上次进度,从上次最大ID继续加载,不重新开始
Requirement: 懒加载机制
系统 SHALL 支持懒加载机制,当卡未初始化但被访问时,实时加载到 Redis。
Scenario: 卡缓存未命中时加载
- WHEN 手动触发检查,卡ID为 123456,但 Redis 中没有该卡缓存
- THEN 系统从数据库读取卡信息,匹配配置,写入 Redis 缓存,加入轮询队列,继续执行检查
Scenario: 新卡创建时自动加载
- WHEN 用户创建新卡或批量导入卡
- THEN 系统在 Service 层调用 PollingService.OnCardCreated(),自动加载到 Redis
Scenario: 热点卡优先
- WHEN 初始化未完成,用户频繁访问某些卡
- THEN 这些卡通过懒加载机制优先初始化到 Redis
Requirement: 调度循环执行
系统 SHALL 每 10 秒执行一次调度循环,从 Redis 队列获取到期的卡并生成任务。
Scenario: 定时触发调度
- WHEN 调度器启动后
- THEN 系统每 10 秒执行一次调度循环(使用 time.Ticker)
Scenario: 获取到期的卡
- WHEN 调度循环执行,当前时间为 T
- THEN 系统从 Redis Sorted Set 使用 ZRANGEBYSCORE 获取 score <= T 的卡(最多 1000 张)
Scenario: 生成 Asynq 任务
- WHEN 获取到 500 张到期的卡
- THEN 系统为每张卡生成 Asynq 任务(TaskTypeIotRealNameCheck),入队到 iot_polling_realname 队列
Scenario: 从队列移除已调度的卡
- WHEN 任务生成完成
- THEN 系统使用 ZREM 从 Redis 队列移除这些卡(检查完成后会重新加入)
Requirement: 手动触发优先处理
系统 SHALL 优先处理手动触发队列中的卡,确保手动触发立即执行。
Scenario: 先处理手动触发队列
- WHEN 调度循环执行,手动触发队列(polling:manual:realname)有 10 张卡
- THEN 系统先从手动触发队列取出所有卡,生成高优先级任务(ProcessIn(0)),清空手动触发队列
Scenario: 再处理定时队列
- WHEN 手动触发队列处理完成后
- THEN 系统继续处理定时队列中到期的卡
Requirement: 配置匹配引擎
系统 SHALL 为每张卡匹配最合适的轮询配置,基于优先级选择。
Scenario: 按优先级匹配配置
- WHEN 卡A的状态为未实名(not_real_name)、运营商为移动(carrier_id=1)
- THEN 系统读取所有启用配置,按 priority ASC 排序,依次检查匹配条件,返回第一个匹配的配置
Scenario: 精确匹配优先
- WHEN 配置1(优先级10)匹配"未实名+移动",配置2(优先级20)匹配"未实名",卡A为"未实名+移动"
- THEN 系统匹配配置1(优先级更高)
Scenario: 无匹配配置
- WHEN 卡A的状态无法匹配任何启用的配置
- THEN 系统返回 nil,卡A不参与轮询
Scenario: 卡状态变化重新匹配
- WHEN 卡A从未实名变为已实名
- THEN 系统调用 OnCardStatusChanged(),重新匹配配置,更新 Redis 缓存和队列
Requirement: 下次检查时间计算
系统 SHALL 根据配置的检查间隔计算卡的下次检查时间。
Scenario: 首次加入队列
- WHEN 卡A首次加入轮询队列,配置的实名检查间隔为 60 秒
- THEN 系统计算 next_check_time = NOW() + 60秒,使用 ZADD 加入队列
Scenario: 检查完成后重新入队
- WHEN 卡A的实名检查完成,配置的检查间隔为 60 秒
- THEN 系统计算 next_check_time = NOW() + 60秒,使用 ZADD 重新加入队列
Scenario: 配置更新后重新计算
- WHEN 管理员将配置的检查间隔从 60 秒修改为 120 秒
- THEN 系统为所有匹配该配置的卡重新计算 next_check_time,更新队列
Requirement: 卡生命周期回调
系统 SHALL 在卡的生命周期事件发生时,自动同步到轮询系统。
Scenario: 新卡创建时加入轮询
- WHEN IotCardService.Create() 创建新卡,enable_polling=true
- THEN Service 调用 PollingService.OnCardCreated(),卡自动加入轮询系统
Scenario: 批量导入时加入轮询
- WHEN IotCardImportHandler 批量导入 10000 张卡
- THEN Handler 调用 PollingService.OnBatchCardsCreated(),使用 Pipeline 批量加入轮询系统
Scenario: 卡删除时移除轮询
- WHEN IotCardService.Delete() 软删除卡
- THEN Service 调用 PollingService.OnCardDeleted(),从所有队列移除,删除缓存
Scenario: 禁用轮询时移除队列
- WHEN IotCardService.Update() 更新卡,enable_polling=false
- THEN Service 调用 PollingService.OnCardDisabled(),从队列移除但保留缓存
Scenario: 启用轮询时加入队列
- WHEN IotCardService.Update() 更新卡,enable_polling=true
- THEN Service 调用 PollingService.OnCardEnabled(),重新匹配配置并加入队列
Requirement: 监控统计更新
系统 SHALL 在每次调度后更新监控统计数据。
Scenario: 记录调度信息
- WHEN 调度循环完成,处理了 500 张卡
- THEN 系统更新 Redis Hash(polling:stats:realname),记录 last_schedule_at 和 last_schedule_count
Scenario: 更新队列长度
- WHEN 任何时候查询队列状态
- THEN 系统使用 ZCARD 实时读取队列长度