docs(01-05): 完成 Plan 05 执行摘要,更新 STATE/ROADMAP/REQUIREMENTS

- 创建 01-05-SUMMARY.md(CRITICAL-07 + CRITICAL-08 修复记录)
- STATE.md 进度推进至 plan 2/5(20%)
- ROADMAP.md 更新 Phase 1 进度(1/5 SUMMARY 已完成)
- REQUIREMENTS.md 标记 CRITICAL-07、CRITICAL-08 为完成
This commit is contained in:
2026-03-27 22:52:27 +08:00
parent db166807c7
commit bfcea1c18f
4 changed files with 208 additions and 7 deletions

View File

@@ -16,8 +16,8 @@
- [ ] **CRITICAL-04**: 充值回调后自动购包触发 — `recharge/service.go` 在 HandlePaymentCallback 成功后入队 `TaskTypeAutoPurchaseAfterRecharge`;注册 `AutoPurchaseHandler`
- [ ] **CRITICAL-05**: 自动购包佣金链补全 — `AutoPurchaseHandler.ProcessTask()` 事务提交成功后入队 `TaskTypeCommission`
- [ ] **CRITICAL-06**: 代购订单金额修正 — 场景 5代理代购下级`sellerCostPrice` 全部 6 处逐一审查,确保使用 `operatorCostPrice`(非 `buyerCostPrice`
- [ ] **CRITICAL-07**: 设备导入补充 IMEI 字段 — `DeviceRow` 新增 IMEI`ParseDeviceExcel` 列名映射新增 IMEI`device_import.go` 导入时填充
- [ ] **CRITICAL-08**: 提现冻结并发校验 — `my_commission/service.go` 冻结余额后检查 `RowsAffected == 0`,余额不足并发场景返回错误而非静默继续
- [x] **CRITICAL-07**: 设备导入补充 IMEI 字段 — `DeviceRow` 新增 IMEI`ParseDeviceExcel` 列名映射新增 IMEI`device_import.go` 导入时填充
- [x] **CRITICAL-08**: 提现冻结并发校验 — `my_commission/service.go` 冻结余额后检查 `RowsAffected == 0`,余额不足并发场景返回错误而非静默继续
### PERM — 企业端权限完善(方案 B
@@ -126,8 +126,8 @@
| CRITICAL-04 | Phase 1 | Pending |
| CRITICAL-05 | Phase 1 | Pending |
| CRITICAL-06 | Phase 1 | Pending |
| CRITICAL-07 | Phase 1 | Pending |
| CRITICAL-08 | Phase 1 | Pending |
| CRITICAL-07 | Phase 1 | Complete |
| CRITICAL-08 | Phase 1 | Complete |
| PERM-01 | Phase 2 | Pending |
| PERM-02 | Phase 2 | Pending |
| FIN-01 | Phase 2 | Pending |

View File

@@ -38,7 +38,7 @@ Plans:
- [ ] 01-02-PLAN.md — CRITICAL-03实名激活任务断链PollingHandler 注入 asynqClientWave 2
- [ ] 01-03-PLAN.md — CRITICAL-04/05充值回调触发自动购包 + 佣金链补全Wave 2parallel
- [ ] 01-04-PLAN.md — CRITICAL-06代购订单 sellerCostPrice 6 处语义审查修正Wave 1parallel
- [ ] 01-05-PLAN.md — CRITICAL-07/08设备导入 IMEI + 提现冻结并发校验Wave 1parallel
- [x] 01-05-PLAN.md — CRITICAL-07/08设备导入 IMEI + 提现冻结并发校验Wave 1parallel
---
@@ -118,7 +118,7 @@ Plans:
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. P0 紧急修复 | 0/5 | Planning complete | - |
| 1. P0 紧急修复 | 1/5 | In Progress| |
| 2. 权限/财务/实名/轮询修复 | 0/1 | Not started | - |
| 3. 设备体系完善 | 0/1 | Not started | - |
| 4. 退款 + 支付 + 运营修复 | 0/1 | Not started | - |

View File

@@ -1,3 +1,16 @@
---
gsd_state_version: 1.0
milestone: v1.0
milestone_name: milestone
status: unknown
last_updated: "2026-03-27T14:51:42.219Z"
progress:
total_phases: 6
completed_phases: 0
total_plans: 5
completed_plans: 1
---
# Project State — 君鸿卡管系统
**Project:** 君鸿卡管系统 v1.0
@@ -8,6 +21,8 @@
## Current Position
Phase: 01 (p0) — EXECUTING
Plan: 2 of 5
| Field | Value |
|-------|-------|
| **Current Phase** | 1 — P0 紧急修复 |
@@ -27,7 +42,7 @@ Progress: [ 1 ]──[ 2 ]──[ 3 ]──[ 4 ]──[ 5 ]──[ 6 ]
**Core Value:** 业务链路完整可用——代理能采购分配卡/设备C端客户能充值购包激活套餐佣金能正确计算并可提现。这条主链路必须端到端跑通。
**Current Focus:** Phase 1 — 修复 8 个阻塞主链路的 P0 Bug实名常量、自动购包、佣金链、设备导入、提现并发
**Current Focus:** Phase 01 — p0
---
@@ -56,10 +71,12 @@ Progress: [ 1 ]──[ 2 ]──[ 3 ]──[ 4 ]──[ 5 ]──[ 6 ]
| Phases complete | 0/6 |
---
| Phase 01-p0 P05 | 8 | 2 tasks | 3 files |
## Accumulated Context
### Key Decisions
- TRAFFICPhase 6必须独立部署低峰期凌晨 2~5 点),涉及 DB 迁移 + Redis 架构变更
- REALNAME-01~05 是原子重构,必须在一个 plan 内完整执行,不可拆分
- REFUND 退款佣金回扣REFUND-03必须使用整数算术禁用 float64
@@ -68,12 +85,14 @@ Progress: [ 1 ]──[ 2 ]──[ 3 ]──[ 4 ]──[ 5 ]──[ 6 ]
- REFUND-02/03 依赖 REFUND-01数据模型先行
### Architecture Notes
- 双进程API + Worker依赖集中在 bootstrap 装配
- PollingHandler 缺 asynqClientCRITICAL-03 修复AutoPurchaseHandler 未注册CRITICAL-04 修复)
- StopResumeCallback / ResumeCallback 未注入CLEAN-06 修复)
- 流量体系当前用直接覆盖写Phase 6 改为增量累加 + 日粒度 DB
### Known Risks
- sellerCostPrice 错误赋值可能不止场景 5CRITICAL-06 需逐一检查 6 处)
- 分佣树形遍历无循环检测(不在当前 scopev2 考量)
- 店铺软删除无级联检查v2 考量)
@@ -85,6 +104,7 @@ Progress: [ 1 ]──[ 2 ]──[ 3 ]──[ 4 ]──[ 5 ]──[ 6 ]
**Next Action:** Run `/gsd-plan-phase 1` to create detailed plan for Phase 1 (P0 紧急修复)
**Context Files:**
- `.planning/ROADMAP.md` — 完整路线图和成功标准
- `.planning/REQUIREMENTS.md` — 46 个 v1 需求(含 Traceability 表)
- `.planning/PROJECT.md` — 项目背景和约束

View File

@@ -0,0 +1,181 @@
---
phase: 01-p0
plan: 05
subsystem: device-import, my-commission
tags: [bug-fix, critical, imei, concurrent-safety]
dependency_graph:
requires: []
provides: [CRITICAL-07-fixed, CRITICAL-08-fixed]
affects: [pkg/utils/excel.go, internal/task/device_import.go, internal/service/my_commission/service.go]
tech_stack:
added: []
patterns: [conditional-update, rows-affected-check]
key_files:
created: []
modified:
- pkg/utils/excel.go
- internal/task/device_import.go
- internal/service/my_commission/service.go
decisions:
- "CRITICAL-07: DeviceRow 新增 IMEI 字段buildDeviceColumnIndex 以 switch-case 支持多列名别名imei/设备imei/imei号ParseDeviceExcel 数据行提取 row.IMEIdevice_import.go 创建 Device 时填充 IMEI: row.IMEI"
- "CRITICAL-08: 冻结余额改为 result := tx...Updates() 分离结果对象,先检 result.Error再检 result.RowsAffected == 0 返回 CodeInsufficientBalance防止并发时 WHERE balance >= ? 条件不满足仍创建提现单"
metrics:
duration: "~8min"
completed_date: "2026-03-27"
tasks_completed: 2
files_modified: 3
requirements:
- CRITICAL-07
- CRITICAL-08
---
# Phase 01 Plan 05: 设备导入 IMEI 字段 + 提现冻结并发校验 Summary
**一句话总结:** 修复设备导入时 IMEI 字段始终为空CRITICAL-07及并发提现场景下余额冻结缺少 RowsAffected 校验CRITICAL-08两个独立 Bug。
---
## 完成的任务
### Task 1: 设备导入补充 IMEI 字段CRITICAL-07
**修改文件:** `pkg/utils/excel.go``internal/task/device_import.go`
**提交:** `809cb2b`
#### pkg/utils/excel.go 改动
**DeviceRow struct 新增 IMEI 字段:**
```go
// 修改前
type DeviceRow struct {
Line int
VirtualNo string
// IMEI 字段缺失
DeviceName string
...
}
// 修改后
type DeviceRow struct {
Line int
VirtualNo string
IMEI string // 设备IMEI对应 Excel IMEI 列,用于 Gateway API 调用
DeviceName string
...
}
```
**buildDeviceColumnIndex 新增 IMEI 列名支持:**
-`index` map 中初始化 `"imei": -1`
- 表头遍历时用 switch-case 特殊处理 IMEI 的多种列名别名:`imei``设备imei``imei号`(不区分大小写,因为 `strings.ToLower` 已预处理)
**ParseDeviceExcel 数据行提取 row.IMEI**
```go
if idx := colIndex["imei"]; idx >= 0 && idx < len(record) {
row.IMEI = strings.TrimSpace(record[idx])
}
```
#### internal/task/device_import.go 改动
`processBatch` 函数创建 `model.Device` 时新增 `IMEI: row.IMEI`
```go
device := &model.Device{
VirtualNo: row.VirtualNo,
IMEI: row.IMEI, // 新增
DeviceName: row.DeviceName,
...
}
```
---
### Task 2: 提现冻结并发校验CRITICAL-08
**修改文件:** `internal/service/my_commission/service.go`
**提交:** `db16680`
**改动前后对比CreateWithdrawalRequest 第 162-170 行):**
```go
// 修改前:只检查 ErrorRowsAffected == 0 时无感知
if err := tx.WithContext(ctx).Model(&model.AgentWallet{}).
Where("id = ? AND balance >= ?", wallet.ID, req.Amount).
Updates(map[string]interface{}{
"balance": gorm.Expr("balance - ?", req.Amount),
"frozen_balance": gorm.Expr("frozen_balance + ?", req.Amount),
}).Error; err != nil {
return errors.Wrap(errors.CodeInternalError, err, "冻结余额失败")
}
// ← 缺少 RowsAffected == 0 的检查,并发场景下仍会继续创建提现单
// 修改后:分离 result 对象,同时检查 Error 和 RowsAffected
result := tx.WithContext(ctx).Model(&model.AgentWallet{}).
Where("id = ? AND balance >= ?", wallet.ID, req.Amount).
Updates(map[string]interface{}{
"balance": gorm.Expr("balance - ?", req.Amount),
"frozen_balance": gorm.Expr("frozen_balance + ?", req.Amount),
})
if result.Error != nil {
return errors.Wrap(errors.CodeInternalError, result.Error, "冻结余额失败")
}
// RowsAffected == 0 说明余额已不足(并发场景下被其他请求先行扣减)
if result.RowsAffected == 0 {
return errors.New(errors.CodeInsufficientBalance, "余额不足或并发冲突,请稍后重试")
}
```
**修复原理:** GORM 条件更新 `WHERE balance >= ?` 在余额不足时不更新任何行(返回 0 行),但不会返回 Error。原代码只检查 Error 故无法感知。修复后检查 RowsAffected == 0正确返回 `CodeInsufficientBalance` 错误,阻断后续提现单创建。
---
## 编译验证
```
go build ./... → 通过(无错误)
```
---
## CRITICAL-07 完成状态
- [x] DeviceRow struct 含 IMEI string 字段
- [x] ParseDeviceExcel 支持 imei/IMEI/设备IMEI/IMEI号 列名(不区分大小写)
- [x] buildDeviceColumnIndex 已初始化 imei 键并处理多别名
- [x] device_import.go processBatch 填充 IMEI: row.IMEI
- [x] go build ./... 编译通过
## CRITICAL-08 完成状态
- [x] my_commission/service.go 冻结余额使用 result := tx...Updates()
- [x] 检查 result.Error处理数据库错误
- [x] 检查 result.RowsAffected == 0返回 errors.New(CodeInsufficientBalance, ...)
- [x] go build ./... 编译通过
---
## 人工验收要求(生产环境)
- **CRITICAL-07** 上传含 IMEI 列的设备导入 Excel 后DBHub 查询 `SELECT id, virtual_no, imei FROM tb_device ORDER BY id DESC LIMIT 20;` 确认 IMEI 字段已填充
- **CRITICAL-08** 并发提现场景下DBHub 确认提现单数量与余额冻结成功次数一致(不存在重复提现单)
---
## Deviations from Plan
None - 计划按原样执行。两个修复完全独立,互无依赖。
---
## Self-Check: PASSED
- [x] `pkg/utils/excel.go` 存在且包含 IMEI 字段
- [x] `internal/task/device_import.go` 存在且包含 `IMEI: row.IMEI`
- [x] `internal/service/my_commission/service.go` 存在且包含 `result.RowsAffected == 0`
- [x] commit `809cb2b` 存在CRITICAL-07
- [x] commit `db16680` 存在CRITICAL-08
- [x] `go build ./...` 编译通过