Files
junhong_cmp_fiber/openspec/changes/archive/2026-03-31-fix-stop-resume-lifecycle-engine/tasks.md

81 lines
5.4 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.
# Tasks: fix-stop-resume-lifecycle-engine
## 阶段 1基础层修复常量 + 停机写入)
> 优先级最高,其他所有阶段的复机逻辑都依赖 stop_reason 写入正确。
- [x] **1.1** `pkg/constants/iot.go`:新增 `StopReasonProtectPeriod = "protect_period"` 常量
- [x] **1.2** `pkg/constants/redis.go`:新增 `RedisCardResumeLockKey(cardID uint) string` 函数Key 格式 `card:resume:lock:{cardID}`
- [x] **1.3** `internal/task/polling_handler.go``stopCards` 函数 DB 更新中补充 `"stop_reason": constants.StopReasonTrafficExhausted``"stopped_at": now`
- [x] **1.4** `internal/task/polling_handler.go``HandleProtectConsistencyCheck` 中的 `"stop_reason": "保护期一致性检查自动停机"` 替换为 `constants.StopReasonProtectPeriod`
---
## 阶段 2ResumeCardIfStopped 核心重构
> 扩展签名 + 加未实名检查 + 加并发锁 + 加 device 支持。其他阶段依赖此函数签名。
- [x] **2.1** `internal/service/iot_card/stop_resume_service.go``ResumeCallback` 接口方法签名改为 `ResumeCardIfStopped(ctx context.Context, carrierType string, carrierID uint) error`
- [x] **2.2** `internal/service/iot_card/stop_resume_service.go``ResumeCardIfStopped` 实现:
- 参数改为 `(ctx, carrierType, carrierID)`
- `carrierType == "iot_card"` 时:读取单卡信息 → 检查实名 → 检查 stop_reason → 加 Redis 锁 → 调 Gateway → 更新 DB
- `carrierType == "device"` 时:查询设备绑定的所有已绑定卡(`bind_status=bound` → 逐一执行 iot_card 复机逻辑(含实名检查)
- Gateway 成功但 DB 失败时记录 ERROR 日志(含卡 ID、ICCID不返回 error 阻断流程
- [x] **2.3** `internal/service/package/activation_service.go`:更新 `ResumeCallback` 接口定义(与 2.1 一致)
- [x] **2.4** `internal/service/package/activation_service.go`:两处 `resumeCallback.ResumeCardIfStopped` 调用点更新传参(传入 `carrierType``carrierID`
- [x] **2.5** 编译检查:`lsp_diagnostics` 确认无编译错误
---
## 阶段 3购买套餐后复机order service
- [x] **3.1** `internal/service/order/service.go``Service` 结构体增加 `resumeCallback ResumeCallback` 字段(复用 `activation_service.go` 中定义的接口)
- [x] **3.2** `internal/service/order/service.go`:新增 `SetResumeCallback(callback ResumeCallback)` 方法
- [x] **3.3** `internal/service/order/service.go``HandlePaymentCallback` 在事务成功后(`if err != nil { return err }` 之后),根据订单的 `carrierType``carrierID` 调用 `s.resumeCallback.ResumeCardIfStopped`(检查 nil异步 goroutine 调用)
- 仅当 `activateMainPackage` 创建了 `status=1` 的套餐时才触发(即 `!hasActiveMain` 分支)
- 传递载体信息:从 order 中读取 `IotCardID``DeviceID`
---
## 阶段 4Depleted 套餐到期处理
- [x] **4.1** `internal/polling/package_activation_handler.go``findExpiredMainPackages` 查询条件改为 `WHERE status IN (1,2) AND expires_at <= now AND master_usage_id IS NULL`
- [x] **4.2** 验证:`processExpiredPackage` 的状态更新 `tx.Model(pkg).Update("status", constants.PackageUsageStatusExpired)``status=2` 的套餐同样生效GORM 无状态前置条件,直接更新,逻辑正确无需修改)
---
## 阶段 5流量重置后复机reset service
- [x] **5.1** `internal/service/package/reset_service.go``ResetService` 结构体增加 `resumeCallback ResumeCallback` 字段
- [x] **5.2** `internal/service/package/reset_service.go`:新增 `SetResumeCallback(callback ResumeCallback)` 方法
- [x] **5.3** `internal/service/package/reset_service.go``resetDailyUsageWithDB``resetMonthlyUsageWithDB``resetYearlyUsageWithDB` 三个方法在批量重置完成后,遍历被重置的套餐列表,对每个套餐的 `IotCardID` 异步调用 `resumeCallback.ResumeCardIfStopped(ctx, "iot_card", cardID)`device 类型套餐传 `"device"``DeviceID`nil 检查)
---
## 阶段 6Bootstrap 注入
- [x] **6.1** `internal/bootstrap/worker_services.go``stopResumeService` 创建后,调用:
- `orderService.SetResumeCallback(stopResumeService)` (注意:`orderService` 已在 bootstrap 中创建)
- `resetService.SetResumeCallback(stopResumeService)`
- `usageService.SetStopResumeCallback(stopResumeService)`(已有,确认保留)
- `activationService.SetResumeCallback(stopResumeService)`(已有,确认保留)
---
## 阶段 7知识库更新
- [x] **7.1** 更新知识库 `卡管业务整理/11-停机与复机流程.md`:在"这条链路的问题"表中标注已修复项;补充"自动停复机触发点"说明
- [x] **7.2** 更新知识库 `卡管业务整理/08-套餐使用生命周期.md`:补充"Depleted 套餐到期处理"流程节点
- [x] **7.3** 更新知识库 `修正业务/修正业务总览.md`:将本次修复的 Bug 从待修复移到已修复,新增企业设备停复机的独立追踪条目
---
## 验证清单
- [x] **V1** 编译通过:`go build ./...` 无错误
- [ ] **V2** DB 查询验证:停机卡买套餐后 `network_status=1``stop_reason` 清空
- [ ] **V3** DB 查询验证:流量耗尽的 3 个月套餐在模拟重置后复机
- [ ] **V4** DB 查询验证Depleted 主套餐到期后,下一个 Pending 套餐激活为 status=1
- [ ] **V5** 日志验证:未实名卡触发复机时出现 WARN 日志而非 ERROR
- [ ] **V6** 日志验证:`stopCards` 停机后 `stop_reason=traffic_exhausted` 写入 DB