Files
junhong_cmp_fiber/openspec/changes/archive/2026-04-16-fix-package-activation-and-time-fields/proposal.md
huang 5065d925ad
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m33s
fix: 修正套餐激活和时间字段nullable问题
核心变更:
1. Model层时间字段改为*time.Time并设为nullable
   - PackageUsage.ActivatedAt/ExpiresAt
   - PersonalCustomerDevice/ICCID/Phone.LastUsedAt/VerifiedAt

2. 数据库迁移:
   - activated_at/expires_at列移除NOT NULL约束
   - 清洗零值记录(status=0且activated_at<'2000-01-01')

3. 新增ActivateSpecificPackage方法:精准激活指定套餐,
   修复HandlePackageQueueActivation从"查找过期包"改为直接激活payload指定套餐

4. 新增孤儿套餐恢复扫描:Worker启动或每次套餐检查时,
   自动发现并恢复无status=1主套餐的孤儿载体

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 11:14:39 +08:00

3.5 KiB
Raw Blame History

Why

套餐激活系统存在两类根本性缺陷:一是多个 Model 的时间字段定义为非指针 time.Time,导致"待生效"状态下零值(0001-01-01 00:00:43)被写入数据库,污染历史数据并造成查询误判;二是排队激活的核心执行链存在逻辑断链——HandlePackageQueueActivation 调用的 ActivateQueuedPackage(carrierType, carrierID) 方法设计为"发现过期包 → 标记过期 → 激活下一个"的完整循环,但轮询处理器(HandlePackageActivationCheck)已提前完成了"标记过期"步骤。当 Asynq 任务执行时,过期触发条件已消失,ActivateQueuedPackage 空转返回payload 中携带的 PackageUsageID 未被使用,导致所有排队中的套餐永久卡在 status=0,且系统对重启后的孤儿 pending 套餐完全无感知。当前已有至少 4 条套餐使用记录受影响TEST5G 设备 3 条 + 其他 6 条被零值污染)。

What Changes

  • Model 层:将 PackageUsage.ActivatedAtExpiresAt 改为 *time.Time(需同步数据库迁移去除 NOT NULL 约束);将 PersonalCustomerDevice.LastUsedAtPersonalCustomerICCID.LastUsedAtPersonalCustomerPhone.VerifiedAt 改为 *time.TimeDB 列已 nullable无需迁移
  • 数据修复:将现有 9 条 tb_package_usageactivated_at/expires_at = '0001-01-01' 的记录更新为 NULL
  • 激活逻辑修复:在 ActivationService 新增 ActivateSpecificPackage(packageUsageID) 方法,HandlePackageQueueActivation 改为直接调用该方法激活 payload 指定的具体套餐,而非重跑"查找过期包"流程
  • 孤儿恢复机制HandlePackageActivationCheck(每 10 秒)新增孤儿扫描:检测载体无 status=1 包但存在 status=0 AND pending_realname_activation=false 包的情形,直接触发激活任务,同时覆盖重启恢复和 Asynq 任务丢失两种场景

Capabilities

New Capabilities

  • time-field-nullable-fix:将语义上可为空的时间字段从 time.Time 改为 *time.Time,防止 GORM 将零值写入数据库;包含 Model 修改、数据库迁移、历史数据清洗、赋值代码修正(共 5 个字段,跨 4 个 Model

Modified Capabilities

  • package-queue-activation:修复排队激活执行链断链问题,新增孤儿套餐恢复机制;原 spec 定义的"主套餐过期后 20 秒内激活下一个"的 SLA 要求将真正得到保障,同时新增系统重启后的自动恢复保证

Impact

受影响文件:

  • internal/model/package.goPackageUsage 模型
  • internal/model/personal_customer_device.go — 个人客户设备绑定模型
  • internal/model/personal_customer_iccid.go — 个人客户 ICCID 绑定模型
  • internal/model/personal_customer_phone.go — 个人客户手机验证模型
  • internal/service/package/activation_service.go — 新增 ActivateSpecificPackage 方法
  • internal/polling/package_activation_handler.go — 修改 HandlePackageQueueActivation 逻辑 + 新增孤儿扫描
  • internal/service/order/service.go — 修正零值赋值
  • internal/task/auto_purchase.go — 修正零值赋值(同类问题)
  • 数据库迁移文件(新增)— tb_package_usage.activated_at/expires_at DROP NOT NULL

下游影响:

  • 所有读取 PackageUsage.ActivatedAt/ExpiresAt 的代码需适配指针类型nil 判断)
  • 现有 9 条脏数据将被修复为 NULL
  • 排队中的 TEST5G 套餐id=2,3,6将在修复部署后自动被孤儿扫描激活