Files
junhong_cmp_fiber/.planning/phases/01-p0/01-05-SUMMARY.md
huang bfcea1c18f 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 为完成
2026-03-27 22:52:27 +08:00

6.0 KiB
Raw Blame History

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics, requirements
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics requirements
01-p0 05 device-import, my-commission
bug-fix
critical
imei
concurrent-safety
requires provides affects
CRITICAL-07-fixed
CRITICAL-08-fixed
pkg/utils/excel.go
internal/task/device_import.go
internal/service/my_commission/service.go
added patterns
conditional-update
rows-affected-check
created modified
pkg/utils/excel.go
internal/task/device_import.go
internal/service/my_commission/service.go
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 >= ? 条件不满足仍创建提现单
duration completed_date tasks_completed files_modified
~8min 2026-03-27 2 3
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.gointernal/task/device_import.go
提交: 809cb2b

pkg/utils/excel.go 改动

DeviceRow struct 新增 IMEI 字段:

// 修改前
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设备imeiimei号(不区分大小写,因为 strings.ToLower 已预处理)

ParseDeviceExcel 数据行提取 row.IMEI

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

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 行):

// 修改前:只检查 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 完成状态

  • DeviceRow struct 含 IMEI string 字段
  • ParseDeviceExcel 支持 imei/IMEI/设备IMEI/IMEI号 列名(不区分大小写)
  • buildDeviceColumnIndex 已初始化 imei 键并处理多别名
  • device_import.go processBatch 填充 IMEI: row.IMEI
  • go build ./... 编译通过

CRITICAL-08 完成状态

  • my_commission/service.go 冻结余额使用 result := tx...Updates()
  • 检查 result.Error处理数据库错误
  • 检查 result.RowsAffected == 0返回 errors.New(CodeInsufficientBalance, ...)
  • 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

  • pkg/utils/excel.go 存在且包含 IMEI 字段
  • internal/task/device_import.go 存在且包含 IMEI: row.IMEI
  • internal/service/my_commission/service.go 存在且包含 result.RowsAffected == 0
  • commit 809cb2b 存在CRITICAL-07
  • commit db16680 存在CRITICAL-08
  • go build ./... 编译通过