Files
junhong_cmp_fiber/openspec/changes/add-stop-resume-command-queue/tasks.md
邹进 934a3a27a1
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
feat: 停复机指令队列与设备维度判断
自动停复机统一经停复机指令队列执行(全局开关 polling.stop_resume.queue_enabled,默认关闭):
- 统一判断函数 decideCard,新旧路径共用;暂停轮询的卡在套餐到期、退款场景照常停机
- 同卡一条指令、版本号覆盖;领取校验单卡最小重试间隔;先于卡任务领取,不受 scheduler_mode 限制
- 执行器只调用一次网关,按错误分类退避与告警,结果未知在队列内确认
- 设备维度判断合并请求与场景,版本变化再判断,旧路径持设备锁时推后
- 套餐激活、重置、订单、流量扣减、套餐到期、退款、实名管控修改改为请求评估
- 监控增加停复机队列与近 1 小时执行计数,单卡轮询状态返回当前指令
- 迁移 000255 插入开关与告警、退避参数

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 00:36:26 +09:00

62 lines
8.2 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.
## 0. 前置确认
- [x] 0.1 确认第 1、4、5 个 Change 已上线。(第 1、4、5 个 Change 已部署测试环境;生产按第五部分第 3 节依赖顺序上线)
## 1. 迁移与配置
- [x] 1.1 新增成对迁移:`tb_system_config` 插入 `polling.stop_resume.queue_enabled`(默认 false)、结果未知告警阈值、停复机退避序列与告警阈值;注册受控系统配置项。(000255;carrierqueue.RegisterConfigDefinitions 注册 queue_enabled、unknown_alert_threshold、backoff_seconds、alert_fail_count,退避序列校验非空递增)
## 2. 统一判断与请求评估
- [x] 2.1 抽出统一判断函数(暂停轮询按场景 → 停机条件 → 复机条件 → 通道阈值锁),`EvaluateAndAct`、`resumeSingleCard`、`resumeDeviceCards`、`ResumeCardIfStopped` 改为调用它。(decideCard:EvaluateAndAct 新旧两条路径共用;暂停轮询的卡只在套餐到期、退款场景放行无套餐停机;通道阈值锁在复机写指令与执行前判断)
- [x] 2.2 新增"请求评估(卡或设备、场景、来源)"门面:开关开启时单卡生成指令、设备请求设备判断;关闭时走旧路径当场执行。(RequestAssetEvaluation 门面;EvaluateAndAct、ResumeCardIfStopped、CheckAndStopCard 签名不变,开关开启时改为写指令或请求设备判断,场景经 ctx 传入)
- [x] 2.3 指令写入 Lua:覆盖并 `HINCRBY ver`、方向相反清零失败计数、放入卡所属账户 `stop_resume`。(srWriteLua:HINCRBY ver、方向相反清零 fail/pending/unknown、ZADD LT;执行中只更新内容与版本)
## 3. 指令领取与执行
- [x] 3.1 分发器在卡任务之前领取 `stop_resume`:清理过期 `sr_running`,领取数 = min(剩余 QPS, `sr_inflight_limit` − 执行中),校验 `sr_last_call` 推后,写 `sr_claim` 并记版本;开关开启后对全部账户领取。(dispatchStopResume 在 dispatchCarrier 前执行,容量 = min(停机/复机所在限流组剩余, sr_inflight_limit, 消费能力);领取 Lua 校验 sr_last_call 推后;只看开关,不受 scheduler_mode 限制)
- [x] 3.2 NSQ `polling.stop_resume.{cid}` 专用消费者:核对 token、抢 `sr_exec`、续期。(polling.stop_resume.{cid} 专用 ConsumerManager,核对 token 抢 sr_exec;单次执行 ≤60 秒、租约 2 分钟,不续期)
- [x] 3.3 执行器:按场景重新判断 → 写 `sr_last_call` → 调用一次网关(限流等待模式)→ 成功沿用现有回写与停复机记录;收尾按版本移除或保留。(StopResumeExecutor:DecideCommand → MarkStopResumeCall → ExecuteCommand 单次调用(限流等待模式)→ 现有回写与停复机记录;srFinishLua 按版本移除或保留)
- [x] 3.4 结果未知:置待确认,确认延迟后先判断再查状态,按规则结束、重发或推后;查询失败不重发;连续未知达阈值告警。(超时、连接失败、MaybeAccepted、成功后回写失败 → pending;确认延迟后先判断再查状态,查询失败不重发;unknown 达阈值告警)
- [x] 3.5 失败分类处理:频率限制、终态(含自动关闭轮询)、已是目标状态、账户配置错误、临时故障与业务错误;停复机方向写入 `tb_polling_failure_alert`;指令不丢弃。(频率限制不计失败按 max(最小间隔, 首个退避) 推后;终态结束并告警,停机方向回写已停,自动关闭轮询沿用 TerminalPollingDisabler;已是目标状态按成功回写;account_config 首次告警;其余计失败按停复机退避序列;check_type=stop_resume 写入 tb_polling_failure_alert)
## 4. 设备维度判断
- [x] 4.1 请求设备判断 Lua:`INCR eval_ver`、`SADD eval_scenes`、`ZADD LT device_eval:queue`。(deviceEvalRequestLua:INCR eval_ver、SADD eval_scenes、ZADD LT)
- [x] 4.2 分发与消费:抢 `device_eval:dispatch_lock` 领取,NSQ `polling.device_eval`;消费者抢 `eval_lock`(检查旧路径设备锁),读最新数据判断,为各卡写指令(记录场景)。(device_eval 全局分发锁领取,polling.device_eval 消费者抢 eval_lock;旧路径设备锁存在时推后 5 秒;EvaluateDeviceCommands 逐卡统一判断并写指令)
- [x] 4.3 收尾:`SREM` 已处理场景,比较版本,变大则 score 设为当前时间再判断。(deviceEvalFinishLua:SREM 已处理场景,版本变大立即再判断)
- [x] 4.4 轮询路径不再使用 `RedisPollingDeviceOpLockKey`;人工设备停复机保持不变。(开关开启时轮询路径经设备判断,不再抢 RedisPollingDeviceOpLockKey;人工设备停复机不变)
## 5. 自动入口改造
- [x] 5.1 套餐步骤与 Outbox 三个卡观测消费者改为请求评估,不再同步调网关、不 sleep 重试。(套餐步骤经 EvaluateAndAct;Outbox 网络、实名、流量三个消费者标场景 network_observed / realname_observed / flow_observed)
- [x] 5.2 `resumeAfterActivation`(4 处)、`reset_service.go:400`、`order/service.go:2196` 去掉 goroutine,事务提交后请求评估。(stopresume.RunAfterCommit:开关开启时事务提交后同步请求评估、不起 goroutine;关闭时沿用 goroutine 当场执行;流量扣减后停机同样处理)
- [x] 5.3 `refund/service.go` `stopAsset` 改为请求评估,写入成功即标记 `asset_reset`;`RecoverRefundPostProcessing` 只补偿"请求未写入"。(开关开启时 stopAsset 以 refund 场景请求评估,写入成功即标记 asset_reset;关闭时沿用 ManualStopCard;补偿扫描只会补"请求未写入"的退款)
- [x] 5.4 `package_activation_handler.go` 到期失效处:无其他有效主套餐时以"套餐到期"场景请求评估;接续激活路径不变。(triggerStopAfterExpiry 以 package_expired 场景请求评估,暂停轮询的卡同样停机;接续激活路径不变)
- [x] 5.5 `carrier/service.go` 实名管控修改后按 0 → 1、1 → 0 筛选受影响卡批量请求评估(设备卡按设备)。(RequestRealnameControlEvaluation:0→1 在线未实名、1→0 停机未实名,设备卡按设备,分批 500;仅开关开启时执行,保存接口提交后异步触发)
## 6. 监控与接口
- [x] 6.1 `/polling-stats` 按账户增加停复机队列长度、执行中、成功、失败、重试、结果未知数。(/polling-stats/carrier-queues 增加停复机队列长度、到期、执行中、待确认,近 1 小时成功、失败、重试、结果未知)
- [x] 6.2 `/iot-cards/:id/polling-state` 返回当前指令(动作、原因、场景、版本、失败次数、待确认)。(返回 stop_resume_queue_enabled、stop_resume_command、last_stop_resume_call_at)
- [x] 6.3 重新生成接口文档。
## 7. 编译与规范校验
- [x] 7.1 `gofmt -w`;`go build ./cmd/api ./cmd/worker` 通过。
- [x] 7.2 `openspec validate add-stop-resume-command-queue` 与 `openspec doctor --json` 通过。(validate 通过;当前 openspec 无 doctor 命令)
## 8. 测试环境验证(自动化测试为 N/A,按 ENG-TEST-001)
- [ ] 8.1 开关关闭:各入口行为与上线前一致。
- [ ] 8.2 指令覆盖:停机执行中写入复机指令,收尾后复机保留并在最小间隔后执行;无覆盖时指令被移除。
- [ ] 8.3 单卡最小重试间隔:间隔内不调用上游,score 推后到可调用时间。
- [ ] 8.4 结果未知:超时后确认延迟查状态,已是目标状态结束、否则按间隔重发、查询失败不重发。
- [ ] 8.5 失败分类:频率限制不计失败、终态不重试并按标记关闭轮询、已是目标状态按成功、持续失败告警且指令不丢。
- [ ] 8.6 设备判断:判断期间新请求使版本变化后再判断;到期与普通请求合并后场景保留;跨账户设备下各卡最终状态一致。
- [ ] 8.7 暂停轮询的卡:套餐到期与退款后停机执行一次;流量用完、激活、重置不自动停复机。
- [ ] 8.8 退款:上游终态卡退款后不再反复补发停机;资产后处理标记完成。
- [ ] 8.9 激活、重置、订单后复机经指令队列;流量重置日集中复机按账户 QPS 平滑。
- [ ] 8.10 实名管控 0 → 1、1 → 0 后受影响卡被批量评估且平滑执行。
- [ ] 8.11 开关开、关、再开:无重复停复机与漏停;旧路径设备锁持有时设备判断推后。