## 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 实时读取队列长度