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

90 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. 修复购买时实名状态检查order/service.go
- [x] 1.1 在 `activateMainPackage` 的 REALNAME-02 决策块中,扩展卡信息查询:将 `tx.Select("card_category")` 改为 `tx.Select("card_category", "real_name_status")`,并从结果读取 `currentlyRealnamed` 标志
- [x] 1.2 在 REALNAME-02 决策块中,新增 `device` 载体的实名状态检查:当 `carrierType == "device"` 时,通过 GORM 子查询统计绑定卡(`bind_status=1`)中 `real_name_status=1` 的数量,`count > 0``currentlyRealnamed=true`;注意:使用 GORM 查询时软删除过滤(`deleted_at IS NULL`)由 GORM 自动注入,**不需要**手动添加 `.Where("deleted_at IS NULL")`
- [x] 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 路径),不应阻断整个购买流程
- [x] 1.4 运行 `lsp_diagnostics` 确认 `internal/service/order/service.go` 无编译错误
## 2. 扩展 PollingRealnameHandlerpolling_realname_handler.go
- [x] 2.1 在 `PollingRealnameHandler` 结构体中新增字段 `deviceSimBindingStore *postgres.DeviceSimBindingStore`
- [x] 2.2 更新 `NewPollingRealnameHandler` 构造函数签名,在参数列表末尾新增 `deviceSimBindingStore *postgres.DeviceSimBindingStore`,并在函数体中赋值到结构体字段
- [x] 2.3 新增 `triggerDeviceRealnameActivation(ctx context.Context, cardID uint)` 函数:调用 `h.deviceSimBindingStore.GetActiveBindingByCardID` 获取设备绑定;判断无绑定使用同包内已有的 `isNotFound(err)` 工具函数(定义于 `internal/task/polling_utils.go`),命中则静默返回;若有绑定则提交 `carrier_type="device", carrier_id=binding.DeviceID``TaskTypePackageFirstActivation` Asynq 任务(与卡级任务相同的 MaxRetry/Timeout/Queue 配置,参照 `triggerFirstRealnameActivation` 实现);任务提交失败记录 Warn 日志,日志字段需同时包含 `card_id``device_id` 以便关联排查,不中断流程
- [x] 2.4 在 `isFirstRealname` 分支中,在调用 `triggerFirstRealnameActivation` 之后追加调用 `triggerDeviceRealnameActivation(ctx, cardID)`
- [x] 2.5 运行 `lsp_diagnostics` 确认 `internal/task/polling_realname_handler.go` 无编译错误
## 3. 更新 Worker 构造调用pkg/queue/handler.go
- [x] 3.1 在 `registerPollingHandlers` 中,将 `task.NewPollingRealnameHandler(...)` 调用末尾追加参数 `h.workerResult.Stores.DeviceSimBinding`
- [x] 3.2 运行 `lsp_diagnostics` 确认 `pkg/queue/handler.go` 无编译错误
## 4. 整体编译验证与存量修复
- [x] 4.1 执行 `go build ./...` 确认整个项目编译通过,零错误零警告
- [x] 4.2 通过 psql 或任意数据库客户端手动执行存量修复,分三步:
**步骤 0 — 确认受影响范围(只读,先跑)**
```sql
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=1`device_id=2natural_month12 个月monthly 重置)。若有其他记录,核实后再执行步骤 1。
**步骤 1 — 批量修复(写操作,建议在事务中执行)**
```sql
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 — 验证结果**
```sql
SELECT id, status, pending_realname_activation, activated_at, expires_at, next_reset_at
FROM tb_package_usage
WHERE id = 1;
```
预期:`status=1``pending_realname_activation=false``expires_at` 为当月 +12 个月月末 23:59:59`next_reset_at` 为下月 1 日 00:00:00。