Files
junhong_cmp_fiber/openspec/changes/fix-realname-activation-logic/tasks.md
huang 716e9f5971
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m15s
chore: 更新 fix-realname-activation-logic OpenSpec 任务清单
标记全部 13 个任务为已完成

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-18 09:30:56 +08:00

5.5 KiB
Raw Blame History

1. 修复购买时实名状态检查order/service.go

  • 1.1 在 activateMainPackage 的 REALNAME-02 决策块中,扩展卡信息查询:将 tx.Select("card_category") 改为 tx.Select("card_category", "real_name_status"),并从结果读取 currentlyRealnamed 标志
  • 1.2 在 REALNAME-02 决策块中,新增 device 载体的实名状态检查:当 carrierType == "device" 时,通过 GORM 子查询统计绑定卡(bind_status=1)中 real_name_status=1 的数量,count > 0currentlyRealnamed=true;注意:使用 GORM 查询时软删除过滤(deleted_at IS NULL)由 GORM 自动注入,不需要手动添加 .Where("deleted_at IS NULL")
  • 1.3 修改 expiry_base != "from_purchase" 分支的决策逻辑:若 currentlyRealnamed=true 则保持 status=Active(不切换为 pending记录 Info 日志"购买时载体已实名,直接激活套餐";若 currentlyRealnamed=false 则保持原有 pending_realname_activation=true 逻辑;边界情况:若 1.1 的卡查询失败(err != nil)或 1.2 的设备子查询出错,currentlyRealnamed 应保守默认为 false(走 pending 路径),不应阻断整个购买流程
  • 1.4 运行 lsp_diagnostics 确认 internal/service/order/service.go 无编译错误

2. 扩展 PollingRealnameHandlerpolling_realname_handler.go

  • 2.1 在 PollingRealnameHandler 结构体中新增字段 deviceSimBindingStore *postgres.DeviceSimBindingStore
  • 2.2 更新 NewPollingRealnameHandler 构造函数签名,在参数列表末尾新增 deviceSimBindingStore *postgres.DeviceSimBindingStore,并在函数体中赋值到结构体字段
  • 2.3 新增 triggerDeviceRealnameActivation(ctx context.Context, cardID uint) 函数:调用 h.deviceSimBindingStore.GetActiveBindingByCardID 获取设备绑定;判断无绑定使用同包内已有的 isNotFound(err) 工具函数(定义于 internal/task/polling_utils.go),命中则静默返回;若有绑定则提交 carrier_type="device", carrier_id=binding.DeviceIDTaskTypePackageFirstActivation Asynq 任务(与卡级任务相同的 MaxRetry/Timeout/Queue 配置,参照 triggerFirstRealnameActivation 实现);任务提交失败记录 Warn 日志,日志字段需同时包含 card_iddevice_id 以便关联排查,不中断流程
  • 2.4 在 isFirstRealname 分支中,在调用 triggerFirstRealnameActivation 之后追加调用 triggerDeviceRealnameActivation(ctx, cardID)
  • 2.5 运行 lsp_diagnostics 确认 internal/task/polling_realname_handler.go 无编译错误

3. 更新 Worker 构造调用pkg/queue/handler.go

  • 3.1 在 registerPollingHandlers 中,将 task.NewPollingRealnameHandler(...) 调用末尾追加参数 h.workerResult.Stores.DeviceSimBinding
  • 3.2 运行 lsp_diagnostics 确认 pkg/queue/handler.go 无编译错误

4. 整体编译验证与存量修复

  • 4.1 执行 go build ./... 确认整个项目编译通过,零错误零警告

  • 4.2 通过 psql 或任意数据库客户端手动执行存量修复,分三步:

    步骤 0 — 确认受影响范围(只读,先跑)

    SELECT pu.id, pu.device_id, pu.iot_card_id,
           p.calendar_type, p.duration_months, p.duration_days, p.data_reset_cycle
    FROM tb_package_usage pu
    JOIN tb_package p ON p.id = pu.package_id
    WHERE pu.status = 0 AND pu.pending_realname_activation = true;
    

    预期:仅返回 id=1device_id=2natural_month12 个月monthly 重置)。若有其他记录,核实后再执行步骤 1。

    步骤 1 — 批量修复(写操作,建议在事务中执行)

    BEGIN;
    
    UPDATE tb_package_usage pu
    SET
        status                     = 1,
        pending_realname_activation = false,
        activated_at               = NOW(),
        expires_at = CASE p.calendar_type
            WHEN 'natural_month' THEN
                DATE_TRUNC('month', NOW())
                    + ((p.duration_months + 1)::text || ' months')::interval
                    - INTERVAL '1 second'
            ELSE
                DATE_TRUNC('day', NOW())
                    + (p.duration_days::text || ' days')::interval
                    + INTERVAL '86399 seconds'
        END,
        next_reset_at = CASE p.data_reset_cycle
            WHEN 'monthly' THEN
                CASE p.calendar_type
                    WHEN 'natural_month' THEN DATE_TRUNC('month', NOW()) + INTERVAL '1 month'
                    ELSE DATE_TRUNC('day', NOW()) + INTERVAL '30 days'
                END
            WHEN 'daily'  THEN DATE_TRUNC('day', NOW()) + INTERVAL '1 day'
            WHEN 'yearly' THEN DATE_TRUNC('year', NOW() + INTERVAL '1 year')
            ELSE NULL
        END,
        updated_at = NOW()
    FROM tb_package p
    WHERE pu.package_id = p.id
      AND pu.status = 0
      AND pu.pending_realname_activation = true
      AND pu.device_id > 0
      AND pu.device_id IN (
          SELECT DISTINCT dsb.device_id
          FROM tb_device_sim_binding dsb
          JOIN tb_iot_card ic ON ic.id = dsb.iot_card_id
          WHERE dsb.bind_status = 1 AND dsb.deleted_at IS NULL
            AND ic.real_name_status = 1 AND ic.deleted_at IS NULL
      );
    
    -- 确认影响行数为预期值后再 COMMIT否则 ROLLBACK
    COMMIT;
    

    步骤 2 — 验证结果

    SELECT id, status, pending_realname_activation, activated_at, expires_at, next_reset_at
    FROM tb_package_usage
    WHERE id = 1;
    

    预期:status=1pending_realname_activation=falseexpires_at 为当月 +12 个月月末 23:59:59next_reset_at 为下月 1 日 00:00:00。