归档: add-polling-card-status-with-verbose-log,同步主规范
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m24s

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>
This commit is contained in:
2026-04-15 12:23:05 +08:00
parent 854330a4b4
commit 8bf1282378
8 changed files with 188 additions and 31 deletions

View File

@@ -1,57 +1,57 @@
## 1. 数据库迁移
- [ ] 1.1 创建迁移文件 `000120_add_polling_card_status.up.sql``tb_polling_config` 新增 `card_status_check_interval INT NULL``tb_iot_card` 新增 `last_card_status_check_at TIMESTAMPTZ NULL`
- [ ] 1.2 创建对应回滚文件 `000120_add_polling_card_status.down.sql`DROP COLUMN 两列
- [ ] 1.3 执行 `make migrate-up` 并验证:`make migrate-version` 确认 dirty=false使用 PostgreSQL MCP 验证两列已创建
- [x] 1.1 创建迁移文件 `000120_add_polling_card_status.up.sql``tb_polling_config` 新增 `card_status_check_interval INT NULL``tb_iot_card` 新增 `last_card_status_check_at TIMESTAMPTZ NULL`
- [x] 1.2 创建对应回滚文件 `000120_add_polling_card_status.down.sql`DROP COLUMN 两列
- [x] 1.3 执行 `make migrate-up` 并验证:`make migrate-version` 确认 dirty=false使用 PostgreSQL MCP 验证两列已创建
## 2. 常量与配置
- [ ] 2.1 `pkg/constants/constants.go`:轮询任务类型常量块新增 `TaskTypePollingCardStatus = "polling:card_status"`
- [ ] 2.2 `pkg/config/config.go`:新增 `PollingConfig struct { VerboseLog bool }`,在 `Config` 中加入 `Polling PollingConfig` 字段
- [ ] 2.3 `pkg/config/defaults/config.yaml`:在 polling 配置区新增 `verbose_log: false`
- [ ] 2.4 验证:`LspDiagnostics` 检查 `pkg/constants/constants.go``pkg/config/config.go` 无报错
- [x] 2.1 `pkg/constants/constants.go`:轮询任务类型常量块新增 `TaskTypePollingCardStatus = "polling:card_status"`
- [x] 2.2 `pkg/config/config.go`:新增 `PollingConfig struct { VerboseLog bool }`,在 `Config` 中加入 `Polling PollingConfig` 字段
- [x] 2.3 `pkg/config/defaults/config.yaml`:在 polling 配置区新增 `verbose_log: false`
- [x] 2.4 验证:`LspDiagnostics` 检查 `pkg/constants/constants.go``pkg/config/config.go` 无报错
## 3. 数据模型更新
- [ ] 3.1 `internal/model/polling.go``PollingConfig` 结构体新增 `CardStatusCheckInterval *int`GORM column: `card_status_check_interval`
- [ ] 3.2 `internal/model/iot_card.go``IotCard` 结构体新增 `LastCardStatusCheckAt *time.Time`GORM column: `last_card_status_check_at`
- [ ] 3.3 验证:`LspDiagnostics` 检查两个 model 文件无报错
- [x] 3.1 `internal/model/polling.go``PollingConfig` 结构体新增 `CardStatusCheckInterval *int`GORM column: `card_status_check_interval`
- [x] 3.2 `internal/model/iot_card.go``IotCard` 结构体新增 `LastCardStatusCheckAt *time.Time`GORM column: `last_card_status_check_at`
- [x] 3.3 验证:`LspDiagnostics` 检查两个 model 文件无报错
## 4. PollingBase 新增 verboseLog 支持
- [ ] 4.1 `internal/task/polling_base.go``PollingBase` 结构体新增 `verboseLog bool` 字段;`NewPollingBase` 函数签名新增 `verboseLog bool` 参数并赋值
- [ ] 4.2 `cmd/worker/main.go``task.NewPollingBase(...)` 调用新增 `cfg.Polling.VerboseLog` 参数
- [ ] 4.3 验证:`LspDiagnostics` 检查 `polling_base.go``main.go` 无报错
- [x] 4.1 `internal/task/polling_base.go``PollingBase` 结构体新增 `verboseLog bool` 字段;`NewPollingBase` 函数签名新增 `verboseLog bool` 参数并赋值
- [x] 4.2 `cmd/worker/main.go``task.NewPollingBase(...)` 调用新增 `cfg.Polling.VerboseLog` 参数
- [x] 4.3 验证:`LspDiagnostics` 检查 `polling_base.go``main.go` 无报错
## 5. 现有 Handler 添加 verbose 日志
- [ ] 5.1 `internal/task/polling_realname_handler.go`:在 `QueryRealnameStatus` 成功返回后,判断 `h.base.verboseLog` 为 true 时输出 Info 日志card_id、iccid、real_status、new_status、changed
- [ ] 5.2 `internal/task/polling_carddata_handler.go`:在 `QueryFlow` 成功返回后,判断 `h.base.verboseLog` 为 true 时输出 Info 日志card_id、iccid、gateway_flow_mb、increment_mb、is_cross_month
- [ ] 5.3 `internal/task/polling_package_handler.go`:在 `freshCard` 加载成功后、调用 `EvaluateAndAct` 之前,判断 `h.base.verboseLog` 为 true 时输出 Info 日志card_id、iccid、network_status、stop_reason
- [ ] 5.4 `internal/task/polling_protect_handler.go`:在 switch 块执行完毕、`UpdateFields(last_protect_check_at)` 之前,判断 `h.base.verboseLog` 为 true 时输出 Info 日志card_id、iccid、stop_protect、start_protect、network_status
- [ ] 5.5 验证:`LspDiagnostics` 检查上述 4 个 handler 文件无报错
- [x] 5.1 `internal/task/polling_realname_handler.go`:在 `QueryRealnameStatus` 成功返回后,判断 `h.base.verboseLog` 为 true 时输出 Info 日志card_id、iccid、real_status、new_status、changed
- [x] 5.2 `internal/task/polling_carddata_handler.go`:在 `QueryFlow` 成功返回后,判断 `h.base.verboseLog` 为 true 时输出 Info 日志card_id、iccid、gateway_flow_mb、increment_mb、is_cross_month
- [x] 5.3 `internal/task/polling_package_handler.go`:在 `freshCard` 加载成功后、调用 `EvaluateAndAct` 之前,判断 `h.base.verboseLog` 为 true 时输出 Info 日志card_id、iccid、network_status、stop_reason
- [x] 5.4 `internal/task/polling_protect_handler.go`:在 switch 块执行完毕、`UpdateFields(last_protect_check_at)` 之前,判断 `h.base.verboseLog` 为 true 时输出 Info 日志card_id、iccid、stop_protect、start_protect、network_status
- [x] 5.5 验证:`LspDiagnostics` 检查上述 4 个 handler 文件无报错
## 6. 卡状态轮询 Handler 实现
- [ ] 6.1 新建 `internal/task/polling_cardstatus_handler.go`:实现 `PollingCardStatusHandler`字段base、gateway、iotCardStore、stopResumeSvc完整实现 `Handle` 方法
- [ ] 6.2 `Handle` 方法逻辑:获取并发信号量 → 获取卡信息(带缓存)→ 调用 `gateway.QueryCardStatus` → 映射状态("停机"→0其他→1→ verbose log 判断 → 比较状态是否变化 → 写 DB`last_card_status_check_at` + 可选 `network_status`)→ 状态变化时更新缓存并调用 `EvaluateAndAct` → 更新统计 → 重入队
- [ ] 6.3 验证:`LspDiagnostics` 检查新文件无报错
- [x] 6.1 新建 `internal/task/polling_cardstatus_handler.go`:实现 `PollingCardStatusHandler`字段base、gateway、iotCardStore、stopResumeSvc完整实现 `Handle` 方法
- [x] 6.2 `Handle` 方法逻辑:获取并发信号量 → 获取卡信息(带缓存)→ 调用 `gateway.QueryCardStatus` → 映射状态("停机"→0其他→1→ verbose log 判断 → 比较状态是否变化 → 写 DB`last_card_status_check_at` + 可选 `network_status`)→ 状态变化时更新缓存并调用 `EvaluateAndAct` → 更新统计 → 重入队
- [x] 6.3 验证:`LspDiagnostics` 检查新文件无报错
## 7. 轮询系统基础设施集成
- [ ] 7.1 `internal/task/polling_utils.go``getIntervalByTaskType` 新增 `case constants.TaskTypePollingCardStatus`,返回 `cfg.CardStatusCheckInterval`
- [ ] 7.2 `internal/polling/queue_manager.go``allTaskTypes` slice 追加 `constants.TaskTypePollingCardStatus`
- [ ] 7.3 `internal/polling/lifecycle_service.go``getEnabledTaskTypes` 新增 card_status 条件(`CardStatusCheckInterval != nil && *CardStatusCheckInterval > 0``calcInitialDelay` 新增对应 case
- [ ] 7.4 `internal/polling/initializer.go`:在 `initBatch()` 的 if-block 链中新增一段,当 `cfg.CardStatusCheckInterval != nil && *cfg.CardStatusCheckInterval > 0` 时,以 `card.LastCardStatusCheckAt`(新增字段)为基准调用 `calculateNextCheckTime`,向 `TaskTypePollingCardStatus` 分片队列写入 ZAdd 条目;位置紧跟 ProtectCheckInterval 块之后,格式与其他 4 种类型一致
- [ ] 7.5 验证:`LspDiagnostics` 检查上述 4 个文件无报错
- [x] 7.1 `internal/task/polling_utils.go``getIntervalByTaskType` 新增 `case constants.TaskTypePollingCardStatus`,返回 `cfg.CardStatusCheckInterval`
- [x] 7.2 `internal/polling/queue_manager.go``allTaskTypes` slice 追加 `constants.TaskTypePollingCardStatus`
- [x] 7.3 `internal/polling/lifecycle_service.go``getEnabledTaskTypes` 新增 card_status 条件(`CardStatusCheckInterval != nil && *CardStatusCheckInterval > 0``calcInitialDelay` 新增对应 case
- [x] 7.4 `internal/polling/initializer.go`:在 `initBatch()` 的 if-block 链中新增一段,当 `cfg.CardStatusCheckInterval != nil && *cfg.CardStatusCheckInterval > 0` 时,以 `card.LastCardStatusCheckAt`(新增字段)为基准调用 `calculateNextCheckTime`,向 `TaskTypePollingCardStatus` 分片队列写入 ZAdd 条目;位置紧跟 ProtectCheckInterval 块之后,格式与其他 4 种类型一致
- [x] 7.5 验证:`LspDiagnostics` 检查上述 4 个文件无报错
## 8. Worker Handler 注册
- [ ] 8.1 `pkg/queue/handler.go`:在 `RegisterHandlers` 中创建 `cardStatusHandler := task.NewPollingCardStatusHandler(...)` 并注册 `h.mux.HandleFunc(constants.TaskTypePollingCardStatus, cardStatusHandler.Handle)`
- [ ] 8.2 验证:`LspDiagnostics` 检查 `handler.go` 无报错;`go build ./cmd/worker/...` 成功编译
- [x] 8.1 `pkg/queue/handler.go`:在 `RegisterHandlers` 中创建 `cardStatusHandler := task.NewPollingCardStatusHandler(...)` 并注册 `h.mux.HandleFunc(constants.TaskTypePollingCardStatus, cardStatusHandler.Handle)`
- [x] 8.2 验证:`LspDiagnostics` 检查 `handler.go` 无报错;`go build ./cmd/worker/...` 成功编译
## 9. 整体验证
- [ ] 9.1 执行 `go build ./...` 确认整个项目编译通过
- [ ] 9.2 使用 PostgreSQL MCP 验证迁移列已正确创建类型、NULL 约束)
- [ ] 9.3 检查 `make migrate-down && make migrate-up` 回滚和重新迁移均成功
- [x] 9.1 执行 `go build ./...` 确认整个项目编译通过
- [x] 9.2 使用 PostgreSQL MCP 验证迁移列已正确创建类型、NULL 约束)
- [x] 9.3 检查 `make migrate-down && make migrate-up` 回滚和重新迁移均成功