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