Files
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.0 KiB
Raw Blame History

Spec: 时间字段 Nullable 修复

背景

多个 Model 将语义上可为空的时间字段定义为非指针 time.Time,导致 GORM 在未赋值时将 Go 零值(0001-01-01 00:00:00)写入数据库。受影响字段跨 4 个 Model、5 个字段,其中 tb_package_usage 的两个字段在 DB 层也是 NOT NULL需同步迁移。

ADDED Requirements

Requirement: PackageUsage 时间字段可为 NULL

PackageUsage.ActivatedAtExpiresAt SHALL 定义为 *time.Time,数据库列 activated_atexpires_at SHALL 允许 NULL。

当套餐处于待生效状态(status=0)时,activated_atexpires_at SHALL 为 NULL不得写入零值。

Scenario: 创建排队套餐时时间字段为 NULL

  • WHEN 购买主套餐时已有生效中主套餐,新套餐被创建为 status=0待生效
  • THEN tb_package_usage 中该记录的 activated_atexpires_at 均为 NULL
  • AND 不写入 0001-01-01 或任何零值时间

Scenario: 套餐激活后时间字段被正确赋值

  • WHEN 待生效套餐status=0被成功激活
  • THEN activated_at 被更新为激活时刻的实际时间
  • AND expires_at 被更新为根据套餐 CalendarType/Duration 计算出的到期时间
  • AND 两个字段均不为 NULL

Scenario: 历史零值数据被清洗

  • WHEN 数据库迁移执行完成
  • THEN tb_package_usage 中所有 status=0activated_at < '2000-01-01' 的记录,其 activated_atexpires_at 均被更新为 NULL

Requirement: 个人客户绑定时间字段可为 NULL

PersonalCustomerDevice.LastUsedAtPersonalCustomerICCID.LastUsedAtPersonalCustomerPhone.VerifiedAt SHALL 定义为 *time.Time

设备/ICCID 首次绑定时 LastUsedAt SHALL 为 NULL尚未使用。手机号绑定未经验证时 VerifiedAt SHALL 为 NULL。

Scenario: 设备首次绑定时 LastUsedAt 为 NULL

  • WHEN 个人客户首次绑定设备(personal_customer_device 记录被创建)
  • THEN last_used_at 字段为 NULL
  • AND 不写入 0001-01-01 零值

Scenario: ICCID 首次绑定时 LastUsedAt 为 NULL

  • WHEN 个人客户首次绑定 ICCIDpersonal_customer_iccid 记录被创建)
  • THEN last_used_at 字段为 NULL

Scenario: 手机号绑定但未验证时 VerifiedAt 为 NULL

  • WHEN 个人客户绑定手机号但尚未完成验证流程
  • THEN tb_personal_customer_phone.verified_at 字段为 NULL

Requirement: 全量编译无类型错误

Model 改为指针类型后,所有引用 ActivatedAtExpiresAtLastUsedAtVerifiedAt 的代码 SHALL 正确处理指针nil 判断或解引用),go build ./... 必须零错误通过。

Scenario: 读取 ActivatedAt 时安全处理 nil

  • WHEN 代码读取 PackageUsage.ActivatedAt(如展示给前端、计算剩余时长)
  • THEN 必须先判断指针是否为 nilnil 时使用零值时间或跳过该字段,不得直接解引用导致 panic