All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m33s
核心变更: 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>
5.6 KiB
5.6 KiB
1. 数据库迁移与历史数据清洗
- 1.1 创建迁移文件,清洗
tb_package_usage中status=0且activated_at < '2000-01-01'的零值记录(将activated_at和expires_at更新为 NULL) - 1.2 创建迁移文件,对
tb_package_usage.activated_at执行ALTER COLUMN ... DROP NOT NULL - 1.3 创建迁移文件,对
tb_package_usage.expires_at执行ALTER COLUMN ... DROP NOT NULL - 1.4 在本地执行迁移,用 PostgreSQL MCP 验证:查询
tb_package_usage确认列已 nullable、零值记录已清洗(应为 9 条)
2. Model 层修改
- 2.1 修改
internal/model/package.go:PackageUsage.ActivatedAt改为*time.Time,更新 gorm tag 去除not null - 2.2 修改
internal/model/package.go:PackageUsage.ExpiresAt改为*time.Time,更新 gorm tag 去除not null - 2.3 修改
internal/model/personal_customer_device.go:LastUsedAt改为*time.Time - 2.4 修改
internal/model/personal_customer_iccid.go:LastUsedAt改为*time.Time - 2.5 修改
internal/model/personal_customer_phone.go:VerifiedAt改为*time.Time - 2.6 执行
go build ./...,修复所有因指针类型变更导致的编译错误(nil 判断、解引用、比较等) - 2.7 检查并修复受影响的 DTO 文件中对
ActivatedAt/ExpiresAt/LastUsedAt/VerifiedAt的引用:internal/model/dto/package_dto.go(PackageUsageItemResponse.ExpiresAt/ActivatedAt为string类型,其映射赋值代码需增加 nil 判断,nil 时输出空字符串或"-");asset_dto.go、client_asset_dto.go、device_dto.go、iot_card_dto.go中对应字段已是*time.Time,确认无需改动
3. 赋值逻辑修正
- 3.1 修改
internal/service/order/service.go(activateMainPackage函数):待生效路径(status=0)中,将activatedAt = time.Time{}和expiresAt = time.Time{}改为不赋值(保持 nil),去除后续对usage.ActivatedAt/ExpiresAt的条件赋值中对零值的依赖 - 3.2 修改
internal/task/auto_purchase.go(activateMainPackage函数):同上,确保待生效套餐的ActivatedAt/ExpiresAt在创建时为 nil - 3.3 检查
internal/service/package/activation_service.go中所有对ExpiresAt的读取(如mainPackage.ExpiresAt传给加油包),确认指针解引用安全 - 3.4 修复
internal/service/package/activation_service.go中activateNextMainPackage函数的 ExpiryBase 处理:当前该函数直接使用now作为activatedAt,与ActivateByRealname的from_purchase逻辑不一致;改为参照ActivateByRealname的实现,根据pkg.ExpiryBase判断激活时间基准("from_purchase"时用usage.CreatedAt,否则用time.Now())
4. 激活服务新增 ActivateSpecificPackage
- 4.1 在
internal/service/package/activation_service.go新增ActivateSpecificPackage(ctx context.Context, packageUsageID uint) error方法 - 4.2 实现:加载 PackageUsage → 幂等检查(status != Pending 直接返回 nil)→ 获取分布式锁(
RedisPackageActivationLockKey)→ 加载关联 Package → 根据pkg.ExpiryBase决定激活时间基准("from_purchase"时用usage.CreatedAt,否则用time.Now();与ActivateByRealname保持一致)→ 计算 activatedAt/expiresAt/nextResetAt → 事务内 Updates → 调用syncCarrierStatusActivated→ 异步resumeCallback.ResumeCardIfStopped - 4.3 确认分布式锁在函数退出时被
defer释放
5. 修复 HandlePackageQueueActivation
- 5.1 修改
internal/polling/package_activation_handler.go中的HandlePackageQueueActivation:将调用h.activationService.ActivateQueuedPackage(ctx, payload.CarrierType, payload.CarrierID)替换为调用h.activationService.ActivateSpecificPackage(ctx, payload.PackageUsageID) - 5.2 修改
HandlePackageQueueActivation中activationService == nil的降级分支:不删除该分支,改为记录 Error 日志("激活服务未注入,无法执行套餐激活")并返回错误,防止静默失败(原降级分支直接 Updates 缺少 expires_at 计算,逻辑不完整,改为报错更安全) - 5.3 用 PostgreSQL MCP 手动验证:触发一次过期检测(或手动将某套餐 expires_at 设为过去),确认排队套餐被成功激活(status: 0 → 1)
6. 新增孤儿套餐恢复扫描
- 6.1 在
internal/polling/package_activation_handler.go新增findAndActivateOrphanPackages(ctx context.Context) error函数:查询无 status=1 主套餐但有 status=0(且pending_realname_activation=false)主套餐的载体,单次上限 100 个 - 6.2 对每个孤儿载体,查询其 priority 最小的 status=0 主套餐,调用
h.enqueueActivationTask提交激活任务 - 6.3 在
HandlePackageActivationCheck末尾追加调用h.findAndActivateOrphanPackages(ctx),日志记录触发的孤儿数量 - 6.4 用 PostgreSQL MCP 验证当前 TEST5G 设备(device_id=1)的 3 条待生效套餐(id=2,3,6):确认孤儿扫描触发后它们被逐步激活
7. 收尾验证
- 7.1 执行
go build ./...确认全量编译零错误 - 7.2 用 PostgreSQL MCP 查询
tb_package_usage验证:无activated_at < '2000-01-01'的记录 - 7.3 用 PostgreSQL MCP 查询验证:
personal_customer_device.last_used_at、personal_customer_iccid.last_used_at、tb_personal_customer_phone.verified_at中不存在< '2000-01-01'的值(如有,补一条清洗 SQL) - 7.4 检查并更新 DTO 中所有展示
ActivatedAt/ExpiresAt的字段,确认 nil 时的 JSON 输出为null(而非"0001-01-01T00:00:00Z")