Files
junhong_cmp_fiber/openspec/specs/polling-card-status-task/spec.md
huang 8bf1282378
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m24s
归档: add-polling-card-status-with-verbose-log,同步主规范
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>
2026-04-15 12:23:05 +08:00

3.9 KiB
Raw Blame History

Requirement: 注册卡状态轮询任务类型常量

系统 SHALL 在 pkg/constants/constants.go 中定义常量 TaskTypePollingCardStatus = "polling:card_status",与其他轮询类型常量在同一常量块内。

Scenario: 常量可被引用

  • WHEN 其他包引用 constants.TaskTypePollingCardStatus
  • THEN 编译通过,值为 "polling:card_status"

Requirement: 轮询配置支持卡状态检查间隔

PollingConfig modeltb_polling_configSHALL 包含 CardStatusCheckInterval *int 字段GORM column: card_status_check_intervalNULL 表示该配置不启用卡状态轮询。

Scenario: 配置间隔为正整数时卡状态轮询启用

  • WHEN PollingConfig.CardStatusCheckInterval 不为 NULL 且值 > 0
  • THEN getEnabledTaskTypes 返回的列表中包含 TaskTypePollingCardStatus

Scenario: 配置间隔为 NULL 时卡状态轮询不启用

  • WHEN PollingConfig.CardStatusCheckInterval 为 NULL
  • THEN getEnabledTaskTypes 返回的列表中不包含 TaskTypePollingCardStatus

Requirement: IotCard 记录最后一次卡状态检查时间

IotCard modeltb_iot_cardSHALL 包含 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 = 1NetworkStatusOnline

Scenario: Gateway 返回"停机"时卡 network_status 为 0

  • WHEN gateway.QueryCardStatus 返回 CardStatus = "停机"
  • THEN newNetworkStatus = 0NetworkStatusOffline

Scenario: 状态未变化时不触发停复机评估

  • WHEN Gateway 返回的 newNetworkStatuscard.NetworkStatus 相同
  • THEN 不调用 stopResumeSvc.EvaluateAndAct

Scenario: 状态发生变化时更新 DB + 缓存 + 触发评估

  • WHEN Gateway 返回的 newNetworkStatuscard.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: 卡状态轮询类型纳入队列管理

allTaskTypesqueue_manager.goSHALL 包含 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 对应分片队列中有条目