fix: 资产钱包自动创建机制 — 修复C端购买时钱包不存在报错
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m8s

- client_order: 新增 getOrCreateWallet 兜底,钱包不存在时自动创建
- device_import: 设备导入事务内同步创建设备钱包
- iot_card_import: IoT卡批量导入后批量创建卡钱包
- queue/handler: 传递 AssetWalletStore 给两个导入 handler
- migration 000098: 为存量IoT卡和设备补建资产钱包
This commit is contained in:
2026-03-30 11:37:41 +08:00
parent 40809d11c5
commit f339fb1987
36 changed files with 1811 additions and 26 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-28

View File

@@ -0,0 +1,85 @@
## Context
修正业务方案 v5 审查后,确认 5 处代码质量问题仍存在。这些问题分散在不同模块互不依赖可以并行修复。每个修复点改动面小1-3 个文件),无需 DB 迁移,无需新增依赖。
当前代码现状:
- `internal/task/sim.go`:废弃的 SIM 状态同步任务,消费者核心是 `time.Sleep`,生产者全局无调用
- `internal/service/asset/service.go`:至少 2 处 `cards, _ :=` 吞掉错误
- `internal/handler/admin/order.go`钱包支付未校验用户类型Service 层有校验但 Handler 层没有
- `internal/bootstrap/services.go``SetStopResumeCallback` / `SetResumeCallback` 未被调用
- `internal/service/client_order/service.go``orderStatusToClientStatus()` 做了不必要的枚举映射
## Goals / Non-Goals
**Goals:**
- 清除已确认的废弃代码F-2
- 修复错误吞没提升可观测性F-3
- 统一 Handler/Service 权限校验F-5
- 激活停复机回调链路F-6
- 统一 C端/管理端支付状态枚举J-4
- 所有修改通过 `go build ./...`
**Non-Goals:**
- 不做任何业务逻辑变更
- 不新增自动化测试(遵循项目测试禁令)
- 不触碰已正常工作的代码路径
- 不做 DB 迁移
## Decisions
### 1. F-2 废弃代码删除顺序
**决策**:按依赖倒序删除——先删注册行,再删常量,最后删文件。
**原因**:如果先删文件,`go build` 会在注册行报错。倒序删除确保每步都能编译通过。
删除清单:
1. `pkg/queue/handler.go` — 删除 `HandleSIMStatusSync` 注册行和相关 import
2. `pkg/constants/constants.go` — 删除 `TaskTypeSIMStatusSync` 常量
3. `internal/task/sim.go` — 删除整个文件
4. `internal/service/sync/service.go` — 检查是否存在,存在则删除
### 2. F-3 错误处理策略
**决策**:记录 Warn 日志但不中断主流程。
**原因**:这些查询是补充信息(获取绑定卡详情),失败不应阻断资产查询主链路。但吞掉错误会导致排查困难,日志记录是最小侵入的改进。
```go
// 修改前
cards, _ := s.iotCardStore.GetByIDs(ctx, cardIDs)
// 修改后
cards, err := s.iotCardStore.GetByIDs(ctx, cardIDs)
if err != nil {
s.logger.Warn("查询绑定卡信息失败,结果可能不完整",
zap.Uints("card_ids", cardIDs),
zap.Error(err))
}
```
### 3. F-5 Handler 层权限校验位置
**决策**:在 Handler 的 `BodyParser` 之后、调用 Service 之前添加校验。
**原因**:保持 Handler 层"参数验证 + 权限前置"的职责边界,与项目其他 Handler 一致。
### 4. F-6 回调注入时机
**决策**:在 `bootstrap/services.go` 所有 Service 初始化完成后,统一执行回调注入。
**原因**:回调注入需要 `usageService``stopResumeService``activationService` 都已创建。放在初始化最后一步最安全。
### 5. J-4 枚举统一方向
**决策**C端直接使用管理端枚举1/2/3/4删除映射函数。
**替代方案**:管理端改用 C端枚举0/1/2 — 拒绝,因为管理端已有大量数据使用 1/2/3/4改动面更大。
**前端影响**C端前端需更新状态解析这是 BREAKING CHANGE需通知前端团队。
## Risks / Trade-offs
- **[F-2 遗漏引用]** 删除代码后可能有隐藏引用 → 通过 `go build ./...``rg TaskTypeSIMStatusSync` 双重验证
- **[J-4 前端不同步]** C端前端未及时更新枚举 → 需在发布前通知前端,建议同版本发布
- **[F-6 回调循环依赖]** Service 之间互相注入回调可能产生循环 → 当前 `SetXxxCallback` 是单向注入UsageService → StopResumeService不存在循环

View File

@@ -0,0 +1,32 @@
## Why
修正业务方案审查后,发现 5 处代码质量问题仍未修复废弃代码残留、错误被吞、权限前置校验缺失、停复机回调未注入、C端状态枚举映射冗余。这些问题不影响核心链路运行但会导致排查困难错误被吞、文档/代码不一致(废弃代码)、潜在越权(权限缺失)、联动功能失效(回调未注入)。趁当前无大功能并行,集中清理。
## What Changes
- **F-2 删除废弃 SIM 状态同步代码**:删除 `internal/task/sim.go` 整个文件、`pkg/queue/handler.go` 中的注册行、`pkg/constants/constants.go` 中的 `TaskTypeSIMStatusSync` 常量。该功能已被轮询系统完全覆盖,消费者核心是 `time.Sleep`,生产者全局零调用。
- **F-3 修复资产服务错误吞没**`internal/service/asset/service.go` 中至少 2 处 `cards, _ := s.iotCardStore.GetByIDs(...)` 吞掉了数据库错误,改为记录日志但不中断主流程。
- **F-5 后台订单 Handler 钱包权限补齐**`internal/handler/admin/order.go` 创建订单时,钱包支付缺少"仅代理可用"的前置校验,与 Service 层的校验不一致,补齐 Handler 层拦截。
- **F-6 停复机回调注入**`internal/bootstrap/` 初始化时未调用 `SetStopResumeCallback` / `SetResumeCallback`,导致停复机后套餐联动回调不触发。在 bootstrap 初始化完成后补充调用。
- **J-4 删除 C端支付状态映射函数**`internal/service/client_order/service.go``orderStatusToClientStatus()` 将管理端 1/2/3/4 映射为 C端 0/1/2造成前后端枚举不一致。删除该函数C端直接使用管理端枚举。**BREAKING**C端前端需同步更新状态枚举解析1=待支付, 2=已支付, 3=已取消, 4=已退款)。
## Capabilities
### New Capabilities
_无新增能力,全部为现有代码的修正和清理。_
### Modified Capabilities
- `asset-queries`: 修复错误吞没,查询失败时记录日志
- `asset-suspend-resume`: 注入停复机回调,停复机后套餐联动生效
- `admin-order-creation`: Handler 层补齐钱包支付权限前置校验
- `client-order-purchase`: 移除 C端支付状态映射统一枚举
## Impact
- **代码删除**`internal/task/sim.go``internal/service/sync/service.go`(如存在)、相关常量和注册行
- **修改文件**`internal/service/asset/service.go``internal/handler/admin/order.go``internal/bootstrap/services.go``internal/service/client_order/service.go``pkg/queue/handler.go``pkg/constants/constants.go`
- **前端影响**C端需将支付状态枚举从 0/1/2 改为 1/2/3/4J-4BREAKING
- **无 DB 迁移**
- **无新增依赖**

View File

@@ -0,0 +1,12 @@
## MODIFIED Requirements
### Requirement: 后台订单钱包支付权限校验
后台创建订单时若支付方式为钱包支付Handler 层 SHALL 校验当前操作人为代理账号(`user_type = agent`)。非代理账号使用钱包支付 SHALL 在 Handler 层即被拦截,返回参数错误。
#### Scenario: 非代理账号使用钱包支付被拦截
- **WHEN** 平台用户或企业用户提交订单且 `payment_method = wallet`
- **THEN** Handler SHALL 返回 `CodeInvalidParam` 错误,提示"仅代理账号可使用钱包支付",请求不会到达 Service 层
#### Scenario: 代理账号使用钱包支付正常通过
- **WHEN** 代理账号提交订单且 `payment_method = wallet`
- **THEN** Handler SHALL 放行,由 Service 层继续处理钱包扣款逻辑

View File

@@ -0,0 +1,8 @@
## MODIFIED Requirements
### Requirement: 资产查询错误处理
资产查询 Service 在获取绑定卡信息时,数据库查询错误 SHALL 被记录到日志而非被静默忽略。查询失败 SHALL NOT 中断主查询流程,但 SHALL 记录 Warn 级别日志,包含失败的卡 ID 列表和错误详情。
#### Scenario: 绑定卡查询失败时记录日志
- **WHEN** `iotCardStore.GetByIDs()` 返回错误
- **THEN** 系统 SHALL 记录 Warn 日志(含 card_ids 和 error继续返回已有数据不返回错误给调用方

View File

@@ -0,0 +1,12 @@
## MODIFIED Requirements
### Requirement: 停复机回调注入
系统启动时 SHALL 在 bootstrap 阶段注入停复机回调,确保停机/复机操作完成后自动触发套餐联动逻辑(暂停/恢复套餐计时)。
#### Scenario: 系统启动后回调已注入
- **WHEN** API 或 Worker 服务完成 bootstrap 初始化
- **THEN** `usageService.SetStopResumeCallback(stopResumeService)``activationService.SetResumeCallback(stopResumeService)` SHALL 已被调用
#### Scenario: 停机触发套餐暂停
- **WHEN** 管理员对卡执行停机操作且回调已注入
- **THEN** 停机成功后 SHALL 自动触发套餐暂停联动逻辑

View File

@@ -0,0 +1,14 @@
## MODIFIED Requirements
### Requirement: C端支付状态枚举统一
C端订单查询接口返回的 `payment_status` SHALL 直接使用管理端统一枚举值1=待支付, 2=已支付, 3=已取消, 4=已退款),不再通过映射函数转换为 0/1/2。
#### Scenario: C端查询订单返回统一枚举
- **WHEN** C端客户查询订单列表或订单详情
- **THEN** 返回的 `payment_status` SHALL 为 1/2/3/4 之一,与管理端一致
## REMOVED Requirements
### Requirement: C端支付状态映射
**Reason**: `orderStatusToClientStatus()` 函数将管理端 1/2/3/4 映射为 C端 0/1/2造成前后端枚举不一致增加维护负担。
**Migration**: C端前端需更新支付状态枚举解析1=待支付, 2=已支付, 3=已取消, 4=已退款。

View File

@@ -0,0 +1,48 @@
# 任务清单fix-code-quality-cleanup
> 5 个独立修复项,互不依赖,可并行执行。每项完成后独立验证。
## 任务组 1F-2 删除废弃 SIM 状态同步代码
- [x] 1.1 删除 `pkg/queue/handler.go``HandleSIMStatusSync` 的注册行和相关日志行
- [x] 1.2 删除 `pkg/queue/handler.go` 中因 1.1 产生的无用 import如有
- [x] 1.3 删除 `pkg/constants/constants.go``TaskTypeSIMStatusSync` 常量定义
- [x] 1.4 删除 `internal/task/sim.go` 整个文件
- [x] 1.5 检查 `internal/service/sync/service.go` 是否存在,存在则删除
- [x] 1.6 验证:`rg TaskTypeSIMStatusSync` 无残留引用,`rg HandleSIMStatusSync` 无残留引用
- [x] 1.7 验证:`go build ./...` 编译通过
## 任务组 2F-3 修复资产服务错误吞没
- [x] 2.1 修改 `internal/service/asset/service.go` 第 136 行附近:`cards, _ :=` 改为 `cards, err :=`,错误时记录 Warn 日志(含 `card_ids``error`),不中断主流程
- [x] 2.2 修改 `internal/service/asset/service.go` 第 288 行附近:同上处理
- [x] 2.3 全局搜索 `internal/service/asset/service.go` 中其他 `_, _ :=``, _ :=` 模式,确认无遗漏(额外修复了第 546 行 `packages, _ :=`
- [x] 2.4 验证:`lsp_diagnostics` 无新增错误,`go build ./...` 编译通过
## 任务组 3F-5 后台订单 Handler 钱包权限校验
- [x] 3.1 ~~新增钱包支付权限校验~~ **已回滚**提案描述有误平台可使用代理钱包帮代理客户购买套餐场景2原代码已正确无需修改
- [x] 3.2 验证:`go build ./...` 编译通过
## 任务组 4F-6 停复机回调注入
- [x] 4.1 在 `internal/bootstrap/services.go` 中定位 `usageService``stopResumeService``activationService` 的初始化位置
- [x] 4.2 在三者都初始化完成后,追加回调注入调用:
```go
usageService.SetStopResumeCallback(stopResumeService)
activationService.SetResumeCallback(stopResumeService)
```
- [x] 4.3 如果是 Worker 服务也有独立初始化(`worker_services.go`),同样补充回调注入
- [x] 4.4 验证:`rg SetStopResumeCallback internal/bootstrap/` 有命中,`go build ./...` 编译通过
## 任务组 5J-4 删除 C端支付状态映射函数
- [x] 5.1 在 `internal/service/client_order/service.go` 中找到 `orderStatusToClientStatus()` 函数,记录其所有调用点
- [x] 5.2 将所有调用点改为直接使用原始 `payment_status` 值(不再经过映射)
- [x] 5.3 删除 `orderStatusToClientStatus()` 函数定义
- [x] 5.4 验证:`rg orderStatusToClientStatus` 无残留引用,`go build ./...` 编译通过
## 收尾验证
- [x] 6.1 执行 `go build ./...`,确认全量编译通过
- [x] 6.2 执行 `rg "TaskTypeSIMStatusSync\|HandleSIMStatusSync\|orderStatusToClientStatus"` 确认无残留