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

76 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### 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` 对应分片队列中有条目