change 目录归档至 openspec/changes/archive/2026-04-15-add-polling-card-status-with-verbose-log/ 新建主规范 openspec/specs/polling-card-status-task/spec.md(6个需求) 追加 verbose log 需求至 openspec/specs/polling-task-handlers/spec.md(4个新需求) protect handler 规范更新字段名:network_status → network_status_at_check + action_taken Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
3.9 KiB
Requirement: 注册卡状态轮询任务类型常量
系统 SHALL 在 pkg/constants/constants.go 中定义常量 TaskTypePollingCardStatus = "polling:card_status",与其他轮询类型常量在同一常量块内。
Scenario: 常量可被引用
- WHEN 其他包引用
constants.TaskTypePollingCardStatus - THEN 编译通过,值为
"polling:card_status"
Requirement: 轮询配置支持卡状态检查间隔
PollingConfig model(tb_polling_config)SHALL 包含 CardStatusCheckInterval *int 字段(GORM column: card_status_check_interval),NULL 表示该配置不启用卡状态轮询。
Scenario: 配置间隔为正整数时卡状态轮询启用
- WHEN
PollingConfig.CardStatusCheckInterval不为 NULL 且值 > 0 - THEN
getEnabledTaskTypes返回的列表中包含TaskTypePollingCardStatus
Scenario: 配置间隔为 NULL 时卡状态轮询不启用
- WHEN
PollingConfig.CardStatusCheckInterval为 NULL - THEN
getEnabledTaskTypes返回的列表中不包含TaskTypePollingCardStatus
Requirement: IotCard 记录最后一次卡状态检查时间
IotCard model(tb_iot_card)SHALL 包含 LastCardStatusCheckAt *time.Time 字段(GORM column: last_card_status_check_at),每次卡状态轮询成功执行后更新。
Scenario: 每次执行卡状态轮询后更新时间戳
- WHEN
PollingCardStatusHandler.Handle成功执行(无论状态是否变化) - THEN
tb_iot_card.last_card_status_check_at更新为当前时间
Requirement: PollingCardStatusHandler 查询 Gateway 卡状态并同步 DB
系统 SHALL 实现 PollingCardStatusHandler,遵循与 PollingRealnameHandler 相同的结构:获取并发信号量 → 从缓存/DB 获取卡信息 → 调用 gateway.QueryCardStatus → 解析状态 → 更新 DB → 触发停复机评估 → 更新统计 → 重入队。
Scenario: Gateway 返回"正常"时卡 network_status 为 1
- WHEN
gateway.QueryCardStatus返回CardStatus = "正常"或"准备" - THEN
newNetworkStatus = 1(NetworkStatusOnline)
Scenario: Gateway 返回"停机"时卡 network_status 为 0
- WHEN
gateway.QueryCardStatus返回CardStatus = "停机" - THEN
newNetworkStatus = 0(NetworkStatusOffline)
Scenario: 状态未变化时不触发停复机评估
- WHEN Gateway 返回的
newNetworkStatus与card.NetworkStatus相同 - THEN 不调用
stopResumeSvc.EvaluateAndAct
Scenario: 状态发生变化时更新 DB + 缓存 + 触发评估
- WHEN Gateway 返回的
newNetworkStatus与card.NetworkStatus不同 - THEN
tb_iot_card.network_status更新为新值,Redis 缓存同步更新,调用stopResumeSvc.EvaluateAndAct
Scenario: Gateway 查询失败时重入队不更新状态
- WHEN
gateway.QueryCardStatus返回 error - THEN 记录 Warn 日志,更新失败统计,重入队后返回,不修改 DB
Scenario: 并发已满时重新入队
- WHEN
acquireConcurrency返回 false - THEN 调用
requeueCard后返回,不执行 Gateway 查询
Requirement: 卡状态轮询类型纳入队列管理
allTaskTypes(queue_manager.go)SHALL 包含 TaskTypePollingCardStatus,使得 RemoveFromAllQueues、调度器出队、初始化器均覆盖卡状态队列。
Scenario: 卡删除时清理卡状态队列
- WHEN 调用
RemoveFromAllQueues(ctx, cardID) - THEN 卡 ID 从
polling:card_status的所有分片队列中移除
Requirement: 初始化器写入卡状态队列
PollingInitializer SHALL 在批量初始化时,为每张卡的 polling:card_status 分片 Sorted Set 写入初始条目(与 realname/carddata/package/protect 保持一致)。
Scenario: Worker 启动时卡状态队列被初始化
- WHEN
PollingInitializer.StartBackground完成批量加载 - THEN 每张启用轮询且匹配配置的卡在
polling:card_status对应分片队列中有条目