---
phase: 01-p0
verified: 2026-03-28T00:00:00+08:00
status: passed
score: 5/5 must-haves verified
re_verification: false
---

# Phase 1: P0 紧急修复 Verification Report

**Phase Goal:** 修复所有已知的主链路断点，使"充值→自动购包→佣金计算→实名激活"的完整业务链路可端到端跑通，消除数据写入混乱（实名状态常量）和并发安全漏洞（提现重复创建）  
**Verified:** 2026-03-28  
**Status:** ✅ PASSED  
**Re-verification:** No — initial verification

---

## Goal Achievement

### Observable Truths (来自 ROADMAP Success Criteria)

| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | C 端用户完成实名后，实名状态正确写入 DB（值为常量 1，不再是 2），实名激活任务正确触发 | ✓ VERIFIED | `parseRealnameStatus` 返回 `constants.RealNameStatusVerified(=1)`；`isFirstRealname` 使用常量比较；`triggerFirstRealnameActivation` 使用 `asynqClient.EnqueueContext` 正确入队 |
| 2 | C 端用户钱包充值成功后，自动购包任务入队并被消费，佣金计算任务被正确触发 | ✓ VERIFIED | `HandlePaymentCallback` 在 `LinkedPackageIDs` 非空时调用 `queueClient.EnqueueTask(TaskTypeAutoPurchaseAfterRecharge)`；`AutoPurchaseHandler` 已注册；`ProcessTask` 事务外触发 `TaskTypeCommission` 入队 |
| 3 | 代理代购下级场景（场景 5）的订单金额正确使用 operatorCostPrice，佣金链计算正常 | ✓ VERIFIED | `sellerCostPrice = operatorCostPrice` 出现 3 次（普通/后台/H5 三个下单函数，各一处），三处均已修正 |
| 4 | 批量导入设备 Excel 后，设备记录中含 IMEI 字段，Gateway 调用不再因 IMEI 缺失而失败 | ✓ VERIFIED | `DeviceRow.IMEI` 字段存在；`buildDeviceColumnIndex` 支持 `imei/设备imei/imei号` 多别名；`device_import.go` 填充 `IMEI: row.IMEI` |
| 5 | 并发提交提现申请时，RowsAffected 检查生效，不会创建重复提现单 | ✓ VERIFIED | `result.RowsAffected == 0` 检查存在，返回 `errors.New(errors.CodeInsufficientBalance, ...)` |

**Score:** 5/5 truths verified

---

## Required Artifacts

### Plan 01 (CRITICAL-01/02)

| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `internal/task/polling_handler.go` | `parseRealnameStatus` 返回常量值，`isFirstRealname` 使用常量 | ✓ VERIFIED | 行 702: `return constants.RealNameStatusVerified`；行 704: `return constants.RealNameStatusNotVerified`；行 173-174: `isFirstRealname` 使用常量 |
| `internal/model/iot_card.go` | Model 字段注释统一 `0-未实名 1-已实名` | ✓ VERIFIED | 行 30: `comment:实名状态 0-未实名 1-已实名` |
| `internal/service/client_order/service.go` | 实名校验改为常量比较 | ✓ VERIFIED | 行 115: `assetInfo.RealNameStatus != constants.RealNameStatusVerified` |

**额外修复（超出 PLAN 范围，SUMMARY 已记录）：**

| Artifact | Status | Details |
|----------|--------|---------|
| `internal/model/dto/asset_dto.go` | ✓ VERIFIED | DTO description 更新为 `0未实名 1已实名`（旧三态 `1实名中 2已实名` 已消除） |
| `internal/polling/scheduler.go` | ✓ VERIFIED | `getCardCondition` 改为 `!= constants.RealNameStatusVerified`（旧硬编码 0/1/2 三态） |
| `internal/service/iot_card/service.go` | ✓ VERIFIED | `parseGatewayRealnameStatus` 返回 `constants.RealNameStatusVerified`（旧 `return 2` 已消除） |
| `internal/handler/app/client_realname.go` | ✓ VERIFIED | 行 128: `== constants.RealNameStatusVerified`（旧 `== 1` 已消除） |

### Plan 02 (CRITICAL-03)

| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `internal/task/polling_handler.go` | `PollingHandler` 注入 `asynq.Client`，`triggerFirstRealnameActivation` 正确入队 | ✓ VERIFIED | 行 33: `asynqClient *asynq.Client` 字段；行 1103: `h.asynqClient.EnqueueContext(ctx, task)` |
| `pkg/queue/handler.go` | `registerPollingHandlers` 传入 `h.asynqClient` | ✓ VERIFIED | 行 155: `h.asynqClient` 作为第三参数传入 `NewPollingHandler` |

### Plan 03 (CRITICAL-04/05)

| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `internal/service/recharge/service.go` | `queueClient` 注入，`HandlePaymentCallback` 触发自动购包 | ✓ VERIFIED | 行 52: `queueClient *queue.Client` 字段；行 394-404: `EnqueueTask` 触发自动购包 |
| `pkg/queue/handler.go` | `registerAutoPurchaseHandler` 方法和注册调用 | ✓ VERIFIED | 行 74: `h.registerAutoPurchaseHandler()`；行 208-221: `HandleFunc` 注册 |
| `internal/task/auto_purchase.go` | `asynqClient` 注入，`ProcessTask` 触发佣金 | ✓ VERIFIED | 行 34: `asynqClient *asynq.Client` 字段；行 204-218: 事务外 `EnqueueContext(TaskTypeCommission)` |
| `internal/bootstrap/services.go` | `recharge.New()` 传入 `queueClient` | ✓ VERIFIED | 行 173: `rechargeSvc.New(..., deps.QueueClient)` |

### Plan 04 (CRITICAL-06)

| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `internal/service/order/service.go` | 场景 5 `sellerCostPrice` 修正 | ✓ VERIFIED | 行 264, 551, 813: 三处 `sellerCostPrice = operatorCostPrice`（三个下单函数各一处） |

### Plan 05 (CRITICAL-07/08)

| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `pkg/utils/excel.go` | `DeviceRow` 含 `IMEI` 字段，`ParseDeviceExcel` 列名映射新增 IMEI | ✓ VERIFIED | 行 38: `IMEI string` 字段；行 324-350: `buildDeviceColumnIndex` 支持 `imei/设备imei/imei号` |
| `internal/task/device_import.go` | 导入时填充 `IMEI` 字段 | ✓ VERIFIED | 行 267: `IMEI: row.IMEI` |
| `internal/service/my_commission/service.go` | `RowsAffected` 校验，并发冻结失败返回错误 | ✓ VERIFIED | 行 162-174: `result := tx...Updates()`，`result.Error` 和 `result.RowsAffected == 0` 双重检查 |

---

## Key Link Verification

| From | To | Via | Status | Details |
|------|-----|-----|--------|---------|
| `internal/task/polling_handler.go` | `pkg/constants/iot.go` | `constants.RealNameStatusVerified` | ✓ WIRED | 行 702, 704, 173-174, 986 均使用常量 |
| `internal/service/client_order/service.go` | `pkg/constants/iot.go` | `constants.RealNameStatusVerified` | ✓ WIRED | 行 115 使用常量 |
| `pkg/queue/handler.go` | `internal/task/polling_handler.go` | `NewPollingHandler(h.db, h.redis, h.asynqClient, ...)` | ✓ WIRED | 行 152-157 传入 `h.asynqClient` |
| `internal/task/polling_handler.go` | `asynq.Client` | `h.asynqClient.EnqueueContext(ctx, task)` | ✓ WIRED | 行 1103 |
| `internal/service/recharge/service.go` | `pkg/queue/handler.go` | `queueClient.EnqueueTask(TaskTypeAutoPurchaseAfterRecharge)` | ✓ WIRED | 行 394-404 入队；`pkg/queue/handler.go` 行 74/208-221 注册消费 |
| `internal/task/auto_purchase.go` | `asynq.Client` | `h.asynqClient.EnqueueContext(ctx, commissionTask)` | ✓ WIRED | 行 215，事务外触发 |
| `internal/bootstrap/services.go` | `internal/service/recharge/service.go` | `rechargeSvc.New(..., deps.QueueClient)` | ✓ WIRED | 行 173 |
| `pkg/utils/excel.go` | `internal/task/device_import.go` | `DeviceRow.IMEI → model.Device.IMEI` | ✓ WIRED | `device_import.go` 行 267 填充 |
| `internal/service/my_commission/service.go` | `model.AgentWallet` | `result.RowsAffected == 0 → 返回余额不足错误` | ✓ WIRED | 行 162-174 |

---

## Data-Flow Trace (Level 4)

所有 Phase 1 修复均为业务逻辑链路修复（非前端渲染组件），Level 4 关注任务链路数据流的完整性。

| 链路段 | 数据变量 | 数据源 | 产生真实数据 | Status |
|--------|----------|--------|------------|--------|
| 实名状态写入 | `newRealnameStatus` | Gateway 返回 `RealStatus bool` → `parseRealnameStatus()` | ✓ 非静态（依赖真实 Gateway 响应） | ✓ FLOWING |
| 实名激活任务入队 | `task payload (cardID, packageUsageIDs)` | `triggerFirstRealnameActivation` 从 DB 查询真实套餐 | ✓ 非空（前置条件：待激活套餐存在） | ✓ FLOWING |
| 自动购包入队 | `AutoPurchasePayload{RechargeRecordID}` | `recharge.LinkedPackageIDs` 来自 DB 充值记录 | ✓ 非静态 | ✓ FLOWING |
| 自动购包消费 | `createdOrderID` | 事务内 `tx.Create(order)` 生成真实订单 | ✓ DB 写入 | ✓ FLOWING |
| 佣金任务入队 | `{order_id: createdOrderID}` | 事务提交后 `createdOrderID > 0` 才触发 | ✓ 有效订单 ID | ✓ FLOWING |
| sellerCostPrice | `operatorCostPrice` | `getCostPrice(ctx, operatorShopID, ...)` 查 DB | ✓ 非静态（真实成本价） | ✓ FLOWING |
| IMEI 写入 | `DeviceRow.IMEI` | Excel 文件列解析 | ✓ 非静态（依赖 Excel 数据） | ✓ FLOWING |
| 提现冻结 | `result.RowsAffected` | GORM 条件更新返回影响行数 | ✓ 数据库真实返回 | ✓ FLOWING |

---

## Behavioral Spot-Checks

| Behavior | Command | Result | Status |
|----------|---------|--------|--------|
| 编译通过（无错误） | `go build ./...` | 无输出，无错误 | ✓ PASS |
| `parseRealnameStatus(true)` 不再返回 2 | `grep "return 2" internal/task/polling_handler.go` | 无匹配 | ✓ PASS |
| 无遗留实名硬编码 2 | `grep -rn "RealNameStatus == 2\|newRealnameStatus == 2\|return 2" internal/` | `client_order/service.go` 的 `return 2` 为 `PaymentStatusCancelled`（与实名无关，见 `orderStatusToClientStatus` 函数） | ✓ PASS |
| `_ = task` 已删除 | `grep "_ = task" internal/task/polling_handler.go` | 无匹配 | ✓ PASS |
| `registerAutoPurchaseHandler` 已注册 | `grep "registerAutoPurchaseHandler" pkg/queue/handler.go` | 行 74: 在 `RegisterHandlers` 中调用；行 208: 方法定义 | ✓ PASS |
| `sellerCostPrice = operatorCostPrice` 出现 3 次 | `grep -n "sellerCostPrice = operatorCostPrice" internal/service/order/service.go` | 行 264, 551, 813 — 共 3 处 | ✓ PASS |
| `result.RowsAffected == 0` 校验存在 | `grep "RowsAffected" internal/service/my_commission/service.go` | 行 173: `if result.RowsAffected == 0` | ✓ PASS |
| `IMEI: row.IMEI` 存在 | `grep "IMEI: row.IMEI" internal/task/device_import.go` | 行 267 | ✓ PASS |
| 所有 commits 存在 | `git log --oneline 1fd0072 da50a35 20b4b6d 9a4d87a 030425b f74b7da 809cb2b db16680` | 全部 8 个 commit 存在 | ✓ PASS |

---

## Requirements Coverage

| Requirement | Source Plan | Description | Status | Evidence |
|-------------|------------|-------------|--------|----------|
| CRITICAL-01 | 01-01-PLAN.md | 实名状态常量统一（`parseRealnameStatus` 写入 1，Model 注释统一） | ✓ SATISFIED | `polling_handler.go` 行 702-704 使用常量；`iot_card.go` 行 30 注释正确 |
| CRITICAL-02 | 01-01-PLAN.md | C 端实名校验修复（`client_order/service.go` 改用常量） | ✓ SATISFIED | `client_order/service.go` 行 115 使用 `constants.RealNameStatusVerified` |
| CRITICAL-03 | 01-02-PLAN.md | 实名激活任务断链修复（`PollingHandler` 注入 `asynq.Client`，删除 RPush 降级） | ✓ SATISFIED | `PollingHandler` 有 `asynqClient` 字段；`EnqueueContext` 调用存在；RPush 降级和 `_ = task` 已消除 |
| CRITICAL-04 | 01-03-PLAN.md | 充值回调后自动购包触发（`recharge/service.go` 入队 `TaskTypeAutoPurchaseAfterRecharge`；注册 `AutoPurchaseHandler`） | ✓ SATISFIED | `recharge/service.go` 行 394-404 入队；`pkg/queue/handler.go` 行 208-221 注册 |
| CRITICAL-05 | 01-03-PLAN.md | 自动购包佣金链补全（`AutoPurchaseHandler.ProcessTask` 事务提交成功后入队 `TaskTypeCommission`） | ✓ SATISFIED | `auto_purchase.go` 行 204-218 事务外入队佣金任务 |
| CRITICAL-06 | 01-04-PLAN.md | 代购订单金额修正（场景 5 `sellerCostPrice` 使用 `operatorCostPrice`） | ✓ SATISFIED | `order/service.go` 行 264, 551, 813 三处均已修正 |
| CRITICAL-07 | 01-05-PLAN.md | 设备导入补充 IMEI 字段 | ✓ SATISFIED | `excel.go` 行 38 字段定义，行 342-349 列名映射；`device_import.go` 行 267 填充 |
| CRITICAL-08 | 01-05-PLAN.md | 提现冻结并发校验（`RowsAffected == 0` 检查） | ✓ SATISFIED | `my_commission/service.go` 行 162-174 双重校验 |

**所有 8 个 CRITICAL 需求均已满足，REQUIREMENTS.md 中已标记为 `[x]` 完成状态。**

**孤立需求检查：** REQUIREMENTS.md 中 Phase 1 映射的需求仅为 CRITICAL-01 ~ CRITICAL-08，与 PLANs 声明完全一致，无孤立需求。

---

## Anti-Patterns Found

| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| `internal/service/recharge/service.go` | 277 | `TODO: 按 payment_config_id 加载配置验签（当前留桩，验签由外层处理）` | ℹ️ Info | 预存 TODO，属于 PAY 系列（Phase 4），不影响 Phase 1 目标 |
| `internal/service/order/service.go` | 2360 | `TODO: 实现富友支付发起逻辑` | ℹ️ Info | 预存 TODO，属于 PAY-01（Phase 4），不影响 Phase 1 目标 |
| `internal/service/order/service.go` | 2366 | `TODO: 实现富友小程序支付发起逻辑` | ℹ️ Info | 预存 TODO，属于 PAY-02（Phase 4），不影响 Phase 1 目标 |

> **注意：** `internal/service/client_order/service.go` 行 659/672 的 `return 2` 及 `internal/handler/app/client_order.go` 行 383 的 `return 2` 均属于 `orderStatusToClientStatus` 函数中 `PaymentStatusCancelled` 的映射，与实名状态常量无关，**不是 CRITICAL-01 遗留问题**。

**无阻塞性 Anti-Pattern。**

---

## Human Verification Required

### 1. 实名激活端到端验证

**Test:** 模拟 IoT 卡实名成功回调：触发轮询后，Gateway 返回 `RealStatus=true`  
**Expected:** `tb_iot_card.real_name_status = 1`（不是 2）；Asynq 队列中出现 `task:package:first_activation` 任务；待激活套餐（`pending_realname_activation=true`）被处理  
**Why human:** 需要真实 Gateway 连接或 Mock 环境才能端到端验证

### 2. 自动购包完整链路验证

**Test:** C 端用户充值，支付回调携带含 `LinkedPackageIDs` 的充值记录  
**Expected:** `tb_asset_recharge_record.auto_purchase_status=success` → `tb_order` 生成订单 → `tb_package_usage` 激活套餐 → `tb_commission_record` 生成佣金记录  
**Why human:** 需要完整 Worker 运行环境和真实支付回调数据

### 3. 代购场景佣金差额验证

**Test:** 代理代购下级代理客户套餐  
**Expected:** `tb_order.seller_cost_price` = 操作代理自己的成本价（非下级成本价），佣金差额 > 0  
**Why human:** 需要特定账号体系（代理+下级代理）的测试数据

### 4. 设备导入 IMEI 字段验证

**Test:** 上传含 IMEI 列的设备导入 Excel  
**Expected:** `SELECT id, virtual_no, imei FROM tb_device ORDER BY id DESC LIMIT 20;` — `imei` 字段非空  
**Why human:** 需要数据库直接查询验证（PostgreSQL MCP 工具）

### 5. 并发提现防重复验证

**Test:** 并发发起多笔提现申请（余额仅够一笔）  
**Expected:** 只有一笔提现单创建成功，其余返回 `CodeInsufficientBalance` 错误，无重复提现单  
**Why human:** 需要并发压测工具触发竞争条件

---

## Gaps Summary

**无 Gap。** 所有 5 个 Success Criteria、所有 8 个 CRITICAL 需求均通过代码层面验证。

Phase 1 目标（"充值→自动购包→佣金计算→实名激活" 主链路端到端可跑通）在代码层面全部完成：

1. **实名状态一致性**：全链路（轮询写入、轮询激活判断、C 端校验、DTO、scheduler、iot_card service、handler）统一使用 `constants.RealNameStatusVerified(=1)`，旧错误值 `2` 彻底消除。
2. **实名激活任务链路**：`PollingHandler` 正确注入 `asynq.Client`，`triggerFirstRealnameActivation` 从 RPush 降级方案改为 `EnqueueContext`，任务可被 Worker 消费。
3. **充值→购包→佣金链路**：`recharge.HandlePaymentCallback` → `AutoPurchaseHandler.ProcessTask`（注册并消费）→ 事务外 `EnqueueContext(TaskTypeCommission)`，三段链路均已打通。
4. **代购佣金差额**：三个下单函数（普通/后台/H5）的场景 5 均使用 `operatorCostPrice` 作为 `sellerCostPrice`，佣金差额计算基准已修正。
5. **并发安全**：提现冻结余额 `RowsAffected == 0` 校验防止并发重复创建提现单。
6. **IMEI 字段**：设备导入链路（Excel 解析 → device 创建）IMEI 字段完整传递。

---

## Commit Traceability

| Plan | Commit | Description | Files Changed |
|------|--------|-------------|---------------|
| 01-01 (CRITICAL-01) | `1fd0072` | 统一实名状态常量 | polling_handler, iot_card, asset_dto, scheduler, iot_card/service |
| 01-01 (CRITICAL-02) | `da50a35` | C 端实名校验改用常量 | client_order/service, client_realname |
| 01-02 (CRITICAL-03) | `20b4b6d` | PollingHandler 注入 asynq.Client | polling_handler, queue/handler |
| 01-03 (CRITICAL-04) | `9a4d87a` | 充值回调触发自动购包入队 | recharge/service, bootstrap/services |
| 01-03 (CRITICAL-04/05) | `030425b` | AutoPurchaseHandler 注入 asynqClient + 佣金触发 | auto_purchase, queue/handler |
| 01-04 (CRITICAL-06) | `f74b7da` | sellerCostPrice 三处修正 | order/service |
| 01-05 (CRITICAL-07) | `809cb2b` | 设备导入 IMEI 字段 | excel, device_import |
| 01-05 (CRITICAL-08) | `db16680` | 提现冻结 RowsAffected 校验 | my_commission/service |

所有 8 个 commit 已通过 `git log` 验证存在。

---

_Verified: 2026-03-28_  
_Verifier: the agent (gsd-verifier)_
