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

5.4 KiB
Raw Blame History

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.gostopCards 函数 DB 更新中补充 "stop_reason": constants.StopReasonTrafficExhausted"stopped_at": now
  • 1.4 internal/task/polling_handler.goHandleProtectConsistencyCheck 中的 "stop_reason": "保护期一致性检查自动停机" 替换为 constants.StopReasonProtectPeriod

阶段 2ResumeCardIfStopped 核心重构

扩展签名 + 加未实名检查 + 加并发锁 + 加 device 支持。其他阶段依赖此函数签名。

  • 2.1 internal/service/iot_card/stop_resume_service.goResumeCallback 接口方法签名改为 ResumeCardIfStopped(ctx context.Context, carrierType string, carrierID uint) error
  • 2.2 internal/service/iot_card/stop_resume_service.goResumeCardIfStopped 实现:
    • 参数改为 (ctx, carrierType, carrierID)
    • carrierType == "iot_card" 时:读取单卡信息 → 检查实名 → 检查 stop_reason → 加 Redis 锁 → 调 Gateway → 更新 DB
    • carrierType == "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 调用点更新传参(传入 carrierTypecarrierID
  • 2.5 编译检查:lsp_diagnostics 确认无编译错误

阶段 3购买套餐后复机order service

  • 3.1 internal/service/order/service.goService 结构体增加 resumeCallback ResumeCallback 字段(复用 activation_service.go 中定义的接口)
  • 3.2 internal/service/order/service.go:新增 SetResumeCallback(callback ResumeCallback) 方法
  • 3.3 internal/service/order/service.goHandlePaymentCallback 在事务成功后(if err != nil { return err } 之后),根据订单的 carrierTypecarrierID 调用 s.resumeCallback.ResumeCardIfStopped(检查 nil异步 goroutine 调用)
    • 仅当 activateMainPackage 创建了 status=1 的套餐时才触发(即 !hasActiveMain 分支)
    • 传递载体信息:从 order 中读取 IotCardIDDeviceID

阶段 4Depleted 套餐到期处理

  • 4.1 internal/polling/package_activation_handler.gofindExpiredMainPackages 查询条件改为 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.goResetService 结构体增加 resumeCallback ResumeCallback 字段
  • 5.2 internal/service/package/reset_service.go:新增 SetResumeCallback(callback ResumeCallback) 方法
  • 5.3 internal/service/package/reset_service.goresetDailyUsageWithDBresetMonthlyUsageWithDBresetYearlyUsageWithDB 三个方法在批量重置完成后,遍历被重置的套餐列表,对每个套餐的 IotCardID 异步调用 resumeCallback.ResumeCardIfStopped(ctx, "iot_card", cardID)device 类型套餐传 "device"DeviceIDnil 检查)

阶段 6Bootstrap 注入

  • 6.1 internal/bootstrap/worker_services.gostopResumeService 创建后,调用:
    • 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=1stop_reason 清空
  • V3 DB 查询验证:流量耗尽的 3 个月套餐在模拟重置后复机
  • V4 DB 查询验证Depleted 主套餐到期后,下一个 Pending 套餐激活为 status=1
  • V5 日志验证:未实名卡触发复机时出现 WARN 日志而非 ERROR
  • V6 日志验证:stopCards 停机后 stop_reason=traffic_exhausted 写入 DB