audit full project flows and harden backend

This commit is contained in:
2026-08-03 16:03:44 +08:00
parent d8d62469d6
commit 95b6f2b69c
22 changed files with 505 additions and 890 deletions

View File

@@ -1,219 +1,65 @@
# 全项目业务流程测试报告
# 全项目流程审计与检查报告
测试日期2026-07-31
测试分支:`main`
测试环境:仓库当前远程开发 PostgreSQL / Redis、Mock 验证码、Mock 支付边界
审计日期2026-08-03
依据:`checking/03-系统与业务覆盖矩阵.md``checking/06-业务流程测试.md`
## 1. 结论
本轮门禁通过:
本轮是代码、契约和本地门禁审计不是远程环境端到端验收。BF-01BF-15 中没有任何流程具备“全链路通过”证据4 项为部分具备11 项因实现缺口或环境缺口阻塞。旧报告中的远程数据库、浏览器和 Flutter 成功结论不得沿用。
- 核心成功路径全部通过。
- 本轮发现的 P00 个。
- 本轮发现并修复的 P15 类。
- 修复后平台、气站、配送三套后台的 91 个鉴权及资源列表 HTTP 检查全部通过。
- 用户注册、服务关系、商城、钱包和提现闭环的 56 个流程断言全部通过。
- 三套 Web 已用真实浏览器完成登录和关键页面回归。
- 两个 Flutter 客户端的静态分析及现有测试全部通过。
状态定义:
当前没有阻止提交和推送的未修复 P0/P1
- `部分具备`:存在主要实现和局部自动化证据,但缺远程依赖、外部沙箱或跨端 E2E
- `阻塞`:关键业务步骤不存在,或必要环境/安全凭据未提供。
## 2. 测试范围与数据隔离
## 2. BF-01BF-15 检查矩阵
测试以 `BFT20260731...` 为唯一前缀创建气站、配送点、后台账号、用户、地址、商品、钱包、银行卡、商城订单和提现申请。未使用或修改既有业务主数据。
| 流程 | 状态 | 本轮证据 | 主要缺口 |
| --- | --- | --- | --- |
| BF-01 邀请注册与服务关系 | 阻塞 | 管理端与 API 可构建 | 无本轮远程 DB/Redis、二维码安全及跨端 E2E。 |
| BF-02 设备绑定与远程关阀 | 阻塞 | 内部命令/Outbox/Gateway/协议代码存在 | 无设备绑定、用户归属鉴权和公开远控入口。 |
| BF-03 高风险遥测与整改 | 阻塞 | 上行消息可持久化 | 无规则事件、自动关阀、通知、整改复检及开阀拦截闭环。 |
| BF-04 共享、群控与报修 | 阻塞 | 无充分证据 | 共享权限、群控结果聚合及撤权服务端校验未形成闭环。 |
| BF-05 下单、支付与任务 | 部分具备 | 支付回调一致性已修复并有单测 | 无远程事务、支付沙箱、商户/appid 显式校验和跨端任务 E2E。 |
| BF-06 超时关单与迟到回调 | 部分具备 | Worker 调度与 API 关单代码可构建 | Worker 无测试;无渠道超时、竞争和重启实测。 |
| BF-07 订单项退款入钱包 | 部分具备 | 退款模型/逻辑可静态检查 | 无远程并发、审核隔离、钱包守恒和渠道对账证据。 |
| BF-08 气瓶配送与回收 | 阻塞 | 三套后台可构建 | 无库存并发、轨迹、扫码取证、押金及结算 E2E。 |
| BF-09 人员准入与离线补传 | 阻塞 | App 代码存在 | Flutter 未验证;服务端强制前置和离线完整性无全链路证据。 |
| BF-10 安装/维修闭环 | 阻塞 | 需求与页面资源存在 | 缺服务端顺序状态机和强制证据集成测试。 |
| BF-11 钱包提现审核 | 部分具备 | 资金逻辑已有历史单测,后端门禁通过 | 本轮未连接远程 DB/Redis未执行并发、重复审批和打款回执 E2E。 |
| BF-12 组织隔离与轨迹访问 | 阻塞 | 鉴权框架和后台资源存在 | 无多组织远程数据、分页/导出/缓存串租户及敏感访问审计 E2E。 |
| BF-13 IoT 上行与 ACK | 阻塞 | 协议、Gateway、Topic 身份单测通过 | ACK 包号复用风险;无 MQTT Broker/真设备联调。 |
| BF-14 IoT Outbox 恢复 | 阻塞 | Outbox/Worker 投递代码存在 | 无双 Worker、崩溃窗口、重复投递、积压和死信测试。 |
| BF-15 审计与灾备 | 阻塞 | 部分事实有审计字段 | 未执行 PostgreSQL WAL 恢复、Redis 故障恢复和 RPO/RTO 测量。 |
测试结束后:
## 3. AC-01AC-25 汇总
- 已驳回仍处于待处理状态的并发测试提现,恢复其预扣余额。
- 已归档本轮创建的气站、配送点、后台账号、用户、地址、服务关系、商品和分类等可变主数据。
- 钱包流水、提现申请、审核记录等不可变资金与审计证据按约定保留,并可通过测试前缀追踪。
- Redis 验证码使用短 TTL未修改既有键本轮测试键会自动过期。
| 状态 | AC |
| --- | --- |
| 局部代码证据 | AC-02、AC-07、AC-08、AC-24、AC-25 |
| 关键实现或 E2E 阻塞 | AC-01、AC-03AC-06、AC-09AC-23 |
## 3. 核心流程结果
“局部代码证据”不代表验收通过。AC-02/AC-25 仍受 ACK 关联和 Broker 联调阻塞AC-07 仍受支付沙箱和商户身份检查阻塞AC-08 仍受远程并发验证阻塞AC-24 仍需数据库 Schema 全量核查。
### 3.1 鉴权与数据域
## 4. 本轮实际验证
- 平台 root 正确密码登录成功,错误密码被拒绝。
- 未携带令牌访问受保护接口被拒绝。
- 气站管理员、配送管理员分别登录成功。
- 气站令牌访问平台后台、用户端接口均被拒绝。
- 用户完成验证码注册、密码登录、资料及地址查询。
- 用户注册时正确建立气站和配送点服务关系。
| 范围 | 结果 |
| --- | --- |
| API | `go test ./...``go vet ./...`、build 通过 |
| Worker | test/vet/build 通过,但无测试文件 |
| IoT Gateway | test/vet/build 通过 |
| IoT Server | test/vet/build 通过,含 Topic 身份回归测试 |
| platform_admin | type、contract、build 通过lint 有 111 warnings/12 infos |
| gas_admin、delivery_admin | type、lint、build 命令通过;存在非阻断 lint 告警 |
| site | build 通过4/4 托管测试通过 |
| Flutter Apps | 命令启动超时,未取得结果 |
| 远程 PostgreSQL/Redis | API YAML 已恢复远程配置,但本轮未执行集成流程 |
### 3.2 商城与幂等
## 5. 远程执行前置清单
- 创建并启用商城分类和商品
- 用户按服务端价格创建 80 元订单
- 相同 `request_no` 重复创建只返回同一订单,未重复扣减库存
- 钱包支付成功后订单进入已支付状态
- 已支付订单重复支付被拒绝
### 3.3 钱包资金闭环
以人工充值 100 元且可提现为起点:
1. 充值后:`balance=10000``withdrawal_balance=10000`
2. 重复充值请求保持幂等,未重复入账。
3. 商城消费 80 元后:`balance=2000``withdrawal_balance=2000`
4. 再申请提现 100 元被拒绝,余额保持不变。
5. 申请提现 10 元后立即预扣:两类余额均从 2000 降至 1000。
6. 重复提交同一提现申请未重复预扣。
7. 平台驳回后两类余额均恢复至 2000。
8. 重复驳回未重复返还,余额没有凭空增加。
9. 未审核提现直接完成被拒绝。
10. 审核通过并完成提现 10 元后:`balance=1000``withdrawal_balance=1000`
11. 相同交易号重复完成保持幂等。
12. 两个并发 15 元提现请求面对 20 元余额时仅一个成功,最终余额为 5 元,没有透支。
以上结果确认此前的重复支出、驳回增发和完成不扣总余额问题已闭环。
### 3.4 全资源 HTTP 扫描
修复后独立执行 91 个回归检查,结果 `91/91`
- 平台总后台47 个资源列表。
- 气站后台20 个资源列表。
- 配送后台18 个资源列表。
- 另含三端登录、平台错误密码和未认证访问检查。
所有接口均返回预期业务码,没有 404、500、跨端越权或 SQL 错误。
## 4. 发现并修复的问题
### P1-1 远程库迁移被历史非空数据阻断
`gasorder_track_point.received_at` 新增为非空字段时GORM 直接执行 `ADD NOT NULL`,已有轨迹行导致迁移失败。
修复:
- 迁移前先添加可空字段。
- 使用既有 `occurred_at`,再退化到 `created_at` / 当前时间回填。
- 回填完成后再设置非空。
- PostgreSQL 和 MySQL 分别使用对应 SQL。
- 已在远程开发库实际迁移成功。
### P1-2 联表资源查询的 `status` 字段歧义
气站后台的配送账号、人员资质等联表资源使用裸 `status <> 3`PostgreSQL 返回 `column reference "status" is ambiguous`
修复:
- `ActiveRecords` 统一使用 GORM 当前表限定 `"<table>"."status"`
- 气站配送账号查询抽取明确的数据域查询函数。
- 增加联表 SQL 回归测试。
- 修复后气站和配送全部资源列表通过。
### P1-3 配送钱包充值页面契约存在但 GET 路由缺失
配送前端声明 `/wallet_recharge` 资源页,后端仅有 POST 充值动作,列表页请求 GET 时直接 404。
修复:
- 增加配送点范围内的充值流水列表和详情接口。
- 仅返回当前配送点钱包中 `delivery_admin_recharge` 类型流水。
- 同步配送前端生成契约。
- 增加路由回归断言。
### P1-4 气站、配送后台登录令牌写入与读取使用不同键
登录 Store 将令牌写入 `token`HTTP 客户端却读取 `gas_admin_token` / `delivery_admin_token`。表现为提示“登录成功”后资料请求 401并退回登录页。
修复:
- HTTP 客户端统一调用各端 `getToken()`
- 气站端固定使用 `gas_admin_token`
- 配送端固定使用 `delivery_admin_token`
- 浏览器回归确认两端均能进入运营首页。
### P1-5 隐藏关联页被前端权限守卫错误送到 404
`hideInMenu` 的资质、地址、关联明细页复用已授权 `menuCode`,但权限守卫仍强制要求路由名存在于服务端菜单树。
修复:
- 三套后台统一允许“服务端菜单路由存在”或“当前路由 `menuCode` 已授权”。
- 后端 JWT、角色和数据域校验保持不变。
- 浏览器回归确认气站人员资质隐藏页可访问。
## 5. Web 浏览器验证
使用本地真实前端和远程开发 API
- 平台后台 `5173`root 登录、运营仪表盘数据、提现列表加载通过。
- 气站后台 `5175`:隔离气站管理员登录、首页数据、人员资质隐藏页加载通过。
- 配送后台 `5176`:隔离配送管理员登录、首页、钱包充值页加载通过。
- 关键页面控制台无 error / warning。
## 6. Flutter 客户端
### 用户 App
- `flutter analyze`:通过,无问题。
- `flutter test`2 项通过。
### 服务 App
- `flutter analyze`:通过,无问题。
- `flutter test`3 项通过。
设备级运行未覆盖,原因:
- 两个工程当前只包含 Android / iOS 目录,没有 macOS / Web 平台。
- 本机没有 Android SDK。
- Flutter 报两个工程未配置为可构建的 iOS 应用。
- 当前可见设备只有 macOS 和 Chrome无法承载现有工程。
因此客户端结论限定为“静态分析和自动化测试通过”,不声明真机/模拟器运行通过。
## 7. Worker、IoT 与外部边界
- Worker`go test ./...`、构建通过;当前为 Mock 边界。
- IoT`go test ./...`、构建通过;未连接真实 MQTT Broker 或设备。
- 未连接真实短信、支付渠道和 MQTT。
- 验证码与支付只验证当前 Mock 实现的状态机、幂等和资金记账。
## 8. 实际执行的回归命令
```bash
cd backend/api
go test ./...
go vet ./...
go build ./cmd/main/main.go
cd backend/worker
go test ./...
go build ./cmd/main/main.go
cd backend/iot
go test ./...
go build ./cmd/main/main.go
cd frontend/platform_admin
pnpm type:check
pnpm lint
pnpm contract:check
pnpm build
cd frontend/gas_admin
pnpm type:check
pnpm lint
pnpm contract:check
pnpm build
cd frontend/delivery_admin
pnpm contract:sync
pnpm type:check
pnpm lint
pnpm contract:check
pnpm build
cd apps/user_app
flutter analyze
flutter test
cd apps/service_app
flutter analyze
flutter test
```
补充说明:三套前端 lint 命令返回成功,但仍报告工程模板既有的未使用代码、单词组件名和 Node import 风格 warnings本轮未批量改写这些非业务告警。
1. 确认 YAML 配置指向隔离库,并为 Redis 使用独立 DB/前缀
2. 限制配置文件访问权限,不在命令、报告或日志中记录连接值
3. 明确测试数据前缀和保留策略;禁止清库和删除资金/审计事实
4. 启动支付沙箱、MQTT 模拟器和四后端进程,逐项记录 UI/API、数据库、Outbox、回执和审计证据
5. 先解决 P0/P1 实现缺口,再执行完整 BF 门禁;否则测试结果只能是预期阻塞

View File

@@ -1,640 +1,72 @@
# 全项目代码审计报告
审计日期2026-07-31
审计日期2026-08-03
审计对象`apps/*``backend/*``frontend/*`
审计范围`apps/*``frontend/*``backend/{api,worker,iot-gateway,iot-server}`
审计方式:只读代码审计、静态检查、单元测试和本地构建
依据:`checking/*``docs/01``docs/12`、仓库实现与测试结果
审计基线:仅以实际代码、路由、数据模型、客户端调用和测试为依据,未将 `docs/*` 作为需求或验收依据
## 1. 发布结论
## 1. 结论
当前版本**不满足生产发布条件**。本轮确认并修复 4 类高风险实现问题,但设备绑定/安全整改等核心业务尚未形成可执行闭环,远程 PostgreSQL/Redis 集成、真实支付沙箱、MQTT Broker 级联调和灾备恢复尚无本轮证据。
当前代码**不具备生产发布条件**
风险统计未修复项P0 2 项、P1 6 项、P2 4 项。代码修复不等于对应跨端流程已验收
本次确认:
## 2. 本轮已修复
- P02 项
- P17 项
- P28 项
- P33 项
| 编号 | 等级 | 问题 | 修复与验证 |
| --- | --- | --- | --- |
| FIX-02 | P0 | 通用迁移会直接删除旧支付/退款表,破坏资金及审计事实 | 改为检测旧表并中止迁移,要求专用保留迁移;新增 sqlmock 回归测试。 |
| FIX-03 | P1 | 支付回调可能在目标业务记录不存在时仍把支付单标记成功 | 业务状态推进强制恰好更新一行;补充 0 行、1 行和数据库错误测试;重复回调校验渠道流水号。 |
| FIX-04 | P1 | IoT Server 信任帧内设备号,可利用他人 Topic 伪造 ACK | 设备身份取自已认证 Topic帧内设备号不一致时不转发解码 ACK补充 Topic 单元测试。 |
| FIX-05 | P2 | Worker/IoT Server 使用无明确超时的 HTTP 客户端;内部令牌普通比较 | 增加 12/15 秒超时;支付 Worker 令牌改为常量时间比较。 |
最先需要阻断发布的事项是:
## 3. 未修复发布阻断项
1. 仓库历史中存在明文的外部数据库、Redis 等敏感配置;固定验证码又可直接进入注册、登录和重置密码流程。
2. 钱包的 `balance``withdrawal_balance` 记账口径不闭合,可产生重复支出、拒绝提现后余额凭空增加、提现完成后总余额不减少等资金错误。
3. Worker、IoT 和上传存储仍明确是 Mock实际异步与设备链路不存在。
4. 工作人员“作业前置检查”仅用于展示,业务接口没有服务端强制执行。
### P0
因此,测试和构建全部通过并不能推导出业务可上线;现有测试主要验证路由、模型和少量纯函数,没有覆盖上述关键资金、鉴权、幂等和跨端流程
1. **核心设备安全闭环未实现。** 当前 API 只有内部 IoT 命令、Outbox 和设备消息路由,没有面向用户的设备绑定/归属鉴权、远控动作、高风险开阀拦截、安全事件、整改和复检闭环。AC-01AC-06、AC-12、AC-15、AC-22 不能验收
2. **真实远程依赖及资金链路无本轮证据。** API 已按要求恢复 YAML 中的远程 PostgreSQL/Redis 配置但本轮没有执行远程数据库事务、Redis 并发/TTL、支付宝/微信沙箱回调及退款对账。BF-05BF-07、BF-11 不能作为通过。
### 等级定义
### P1
| 等级 | 含义 |
|---|---|
| P0 | 可直接造成账户接管、资金损失、核心数据破坏或必须立即处置的密钥泄露 |
| P1 | 核心流程不可用、可绕过关键业务控制或上线即产生严重错误 |
| P2 | 在异常、并发、重试或特定数据下产生错误,或形成明显维护/兼容风险 |
| P3 | 低风险冗余、局部质量或性能问题 |
1. IoT ACK 仅按 `device_id + uint16 packet_number + 活跃状态` 批量更新;包号重启后可复用,且没有约束同设备同包号仅一个活跃命令,存在误确认风险。
2. Worker 没有自动化测试关单扫描、Outbox 重试、双 Worker 认领、进程重启和坏响应均无回归保护。
3. 微信回调依赖 SDK 验签解密但业务代码未显式比对回调商户号、appid 和币种与本地配置/支付单一致。
4. IoT Server TLS、设备认证和 Topic ACL 未以生产模式启动门禁强制Broker 级伪造、越权订阅和断线重连未验证。
5. 服务人员准入、离线补传强制字段、安装/维修/安检完成条件尚无完整服务端闭环证据。
6. PostgreSQL 全量加 WAL 恢复、Redis 故障恢复及实际 RPO/RTO 未演练。
置信度“高”表示从当前代码可以完整证明;“中”表示代码证据明确,但删除字段或迁移前仍需核对线上数据及外部消费者。
### P2
## 2. P0发布阻断
1. 支付创建采用“先查询再插入”,并发相同幂等请求可能向渠道重复建单后在本地唯一键失败;需数据库预留事实或冲突回读设计。
2. IoT Outbox 没有明确死信上限和人工恢复状态;持续失败会无限重试。
3. 三套管理端 lint 仍有约百条告警,规则目前不会阻断质量门禁。
4. Flutter 工具在本机启动超时,两个 App 的 analyze/test 未得到本轮结果。
### P0-01 仓库内存在敏感连接配置,固定验证码可形成账户接管链路
## 4. 模块审计结论
置信度:高
| 模块 | 结论 | 主要证据/缺口 |
| --- | --- | --- |
| API | 有条件通过静态门禁 | 测试、vet、build 通过;支付新增回归通过;核心设备/安全/服务闭环缺失。 |
| Worker | 构建通过、业务门禁不通过 | test/vet/build 通过,但无测试文件,远程依赖与故障恢复未执行。 |
| IoT Gateway | 局部门禁通过 | test/vet/build 通过;缺真实 Gateway→IoT Server 故障注入。 |
| IoT Server | 局部门禁通过 | 协议及 Topic 测试、vet、build 通过;缺 Broker ACL/TLS/真设备验证。 |
| platform_admin | 构建通过 | type、contract、build 通过lint 111 warnings/12 infos。 |
| gas_admin / delivery_admin | 构建通过 | type、lint、build 命令通过lint 仍有大量非阻断告警。 |
| site | 通过本地站点门禁 | 先 build 后执行 4 个托管路由/打包测试,全部通过。 |
| user_app / service_app | 未验证 | Flutter 命令超时;不能沿用历史结果。 |
**证据**
## 5. 敏感配置与迁移要求
- `backend/api/etc/platform_dev.yaml:7` 包含公网 PostgreSQL 连接地址、账号和明文密码
- `backend/api/etc/platform_dev.yaml:10` 包含公网 Redis 地址和明文凭据
- `backend/api/etc/platform_dev.yaml:12,18,24` 分别包含固定 JWT 密钥/固定验证码/字段加密密钥性质的配置
- 该文件由 Git 跟踪,且至少出现在多个历史提交中,单纯修改当前文件不能消除历史泄露。
- `backend/api/internal/logic/common/client_auth.go:60-67` 将全局固定验证码写入 Redis。
- `backend/api/internal/logic/common/client_auth.go:73-85` 使用该固定值完成验证和消费。
- 用户与工作人员的注册、验证码登录、重置密码、支付密码设置均复用该验证能力。
- API 按当前项目约定从 YAML 读取远程连接;该文件含敏感配置,应限制仓库和主机访问,并检查 Git 历史、CI 日志及共享记录
- 报告、命令输出和日志不得记录远程连接值
- 旧资金表迁移需单独方案,至少包含数据盘点、不可变备份、字段映射、对账、回滚和审计批准
**影响**
## 6. 建议修复顺序
- 外部数据库和 Redis 可能被直接访问、篡改或拖库
- 已知固定验证码的人可为目标手机号申请新的 `request_identity`,随后尝试注册、验证码登录或重置密码
- 字段加密密钥一旦与密文数据同时泄露,敏感字段的静态加密失去保护
**根因**
- 开发配置被当作可提交配置管理。
- Mock 验证码能力直接复用了真实身份流程,没有环境级硬隔离。
**最小整改**
1. 立即轮换数据库、Redis、JWT、字段加密等所有已提交凭据先吊销旧值再更新部署。
2. 核查相关服务的访问日志、异常登录、密码重置和数据导出记录。
3. 将敏感值迁移到环境变量或密钥管理系统,仓库只保留无效示例。
4. 清理 Git 历史中的敏感内容,并要求所有已有克隆重新同步。
5. Mock 验证码只允许在不可访问生产数据的本地环境启动;生产启动时发现 Mock 开关或固定码应直接失败。
**迁移风险**
- 字段加密密钥轮换需要双密钥读取或批量重加密方案,不能直接替换后让历史数据失效。
**复测**
- 对仓库当前树和完整 Git 历史执行密钥扫描。
- 在非本地环境验证 Mock 验证码无法启动。
- 使用旧密钥、旧数据库凭据和旧 Redis 凭据验证均已失效。
- 覆盖目标手机号的验证码登录和重置密码攻击用例。
### P0-02 钱包双余额账本不闭合,可重复支出并制造余额
置信度:高
**证据**
1. 平台可提现充值同时增加两列:
- `backend/api/internal/logic/platform/wallet/wallet.go:232-239`
2. 商城支付只扣 `balance`,不扣 `withdrawal_balance`
- `backend/api/internal/logic/client/user/shop.go:167-189`
3. 用户提现申请只扣 `withdrawal_balance`,不扣 `balance`
- `backend/api/internal/logic/common/client_wallet.go:372-398`
4. 提现完成只更新申请状态和外部交易号,不扣 `balance`
- `backend/api/internal/logic/platform/wallet/wallet.go:336-366`
5. 配送后台创建提现申请时不预扣 `withdrawal_balance`,仅在查询时减去待处理申请:
- `backend/api/internal/logic/delivery/finance.go:145-188`
6. 平台驳回任何提现申请都会把申请金额加回 `withdrawal_balance`
- `backend/api/internal/logic/platform/wallet/wallet.go:313-323`
**可复现场景**
- 钱包充值 100 元且标记可提现后:`balance=100``withdrawal_balance=100`
- 商城消费 80 元后:`balance=20``withdrawal_balance=100`
- 再申请提现 100 元会通过,形成 80 元消费加 100 元提现。
- 配送后台申请提现时未预扣;若平台驳回,现有代码仍加回金额,可将 100 元可提现余额变成 200 元。
- 提现最终完成也不会减少总余额,余额和实际资金继续背离。
**影响**
- 直接资金损失。
- 钱包余额、可提现余额、提现申请和流水无法对账。
- 客户可在正常 API 流程内触发,不需要数据库权限。
**根因**
- `withdrawal_balance``balance` 的可提现子集,但各交易没有在同一事务内维护该不变量。
- 用户提现和配送后台提现采用了两套互不兼容的预扣策略。
- 提现没有完整的冻结额、解冻、出账和不可变流水模型。
**最小整改**
1. 立即关闭充值、余额支付和提现写入口,先冻结风险窗口。
2. 明确统一不变量,例如 `0 <= available_withdrawable <= available_balance`,并增加“冻结余额/冻结可提现余额”。
3. 支付、提现申请、驳回、完成必须在事务和行锁内同时更新余额、冻结额、流水。
4. 合并用户与配送后台提现逻辑,禁止各自实现不同扣减策略。
5. 对历史钱包、流水、提现、商城订单做全量对账和差异修复。
**迁移风险**
- 不能只改代码;历史余额已经可能不可信。
- 修复前需以外部支付记录、订单、提现回执和不可变流水重建余额,避免把现有错误余额作为初始事实。
**复测**
- 覆盖充值→支付→提现、提现→驳回、提现→完成、重复请求、并发支付/提现。
- 对每一步断言余额、可提现余额、冻结额和流水守恒。
- 增加基于随机交易序列的账本不变量测试。
## 3. P1核心流程和安全控制
### P1-01 Worker 与 IoT 仅为阻塞式 Mock 进程
置信度:高
**证据**
- `backend/worker/cmd/main/main.go:14-20` 只初始化后等待退出信号,并明确输出 Redis Streams consumer 未启用。
- `backend/iot/cmd/main/main.go:14-20` 只初始化后等待退出信号,并明确输出 MQTT Broker 未连接。
- 两模块合计约 150 行 Go 代码没有消费者、重试、Outbox 投递、MQTT 会话、命令回执或业务测试。
**影响**
- 异步通知、对账、超时处理等依赖 Worker 的流程不会执行。
- 设备遥测、远程命令和回执链路不存在。
- 进程可以成功构建和启动,但只会制造“服务在线”的假象。
**最小整改**
- 发布清单中明确排除这两个能力,或在发布前实现真实适配、健康检查、失败重试、幂等和可观测性。
- 健康检查必须区分“进程存活”和“已连接 Redis Streams/MQTT”。
### P1-02 关闭 Mock 后验证码功能全部失效,代码中没有真实发送适配器
置信度:高
**证据**
- `backend/api/internal/logic/common/client_auth.go:60-63` 无条件保存 `MockVerificationCode`,没有生成随机码或调用短信渠道。
- `backend/api/internal/logic/common/client_auth.go:73-76``MockVerificationEnabled=false` 时直接拒绝所有验证码。
- 未发现短信发送接口、供应商适配器或发送结果处理。
**影响**
- 为安全而关闭 Mock 后,验证码登录、注册、密码重置和支付密码相关流程全部不可用。
**最小整改**
- 建立真实验证码生成、散列保存、发送、频率限制、失败处理和审计链路。
- Mock 与真实实现应通过依赖注入隔离,禁止在业务函数内用全局开关混用。
### P1-03 工作人员作业预检可以绕过
置信度:高
**证据**
- `backend/api/internal/logic/client/staff/auth.go:67-108` 计算机构归属、资质有效性、在岗状态和 `can_work`
- `backend/api/internal/logic/client/staff/auth.go:20-52` 的登录实现只校验账户状态、角色和密码,注释所称“资质有效”未被执行。
- `backend/api/internal/logic/client/staff/work.go:72-85``backend/api/internal/logic/client/staff/delivery.go:55-230` 的开始、轨迹、到达、异常、恢复、签收等接口没有校验同一套预检条件。
- `apps/service_app/lib/app/router.dart:12-98` 只按是否有 Token 路由;可直接进入 `/work``/tasks/:identity`,没有强制 `can_work`
**影响**
- 离岗、资质过期或缺少机构归属的人员仍可绕过页面直接调用作业 API。
**最小整改**
- 将作业资格校验提取为服务端中间件/领域守卫,挂载在所有会改变工单、配送、轨迹、证据的接口上。
- 客户端预检仅负责展示,不作为可信控制。
### P1-04 三套支付事实互相割裂,真实支付不会进入看板支付统计
置信度:高
**证据**
- 重叠模型:
- `backend/api/internal/models/fin_payment.go:8-16`
- `backend/api/internal/models/wallet_payment.go:5-20`
- `backend/api/internal/models/gasorder_payment.go:5-12`
- 除 Mock seed 外,未发现业务代码创建 `FinPayment``WalletPayment``GasorderPayment`
- 商城实际支付只更新 `EcOrder` 并创建 `WalletRecord`
- `backend/api/internal/logic/client/user/shop.go:180-191`
- 平台支付金额和渠道统计读取 `WalletPayment`
- `backend/api/internal/logic/platform/dashboard/statistics.go:106-132`
- 气体订单的金额调整又以 `GasorderPayment` 是否存在作为“已支付”判断:
- `backend/api/internal/logic/delivery/order.go:369-375`
**影响**
- 真实商城支付成功后,看板支付金额仍可能为零。
- 支付页面、财务支付、钱包支付、气体订单支付显示不同事实。
- 气体订单代码具备“已支付后禁止改价”判断,但当前流程没有形成对应支付记录。
**最小整改**
- 选定唯一支付主事实和订单支付关联模型。
- 所有支付渠道在同一事务/事件链路写入统一支付事实和钱包流水。
- 看板、财务、气站、配送后台统一读取同一事实或受控聚合。
**迁移风险**
- 三张表不能直接删;需先核对线上数据和外部消费者,建立字段映射与去重规则。
### P1-05 用户 App 的商城流程在“请前往订单页支付”后中断
置信度:高
**证据**
- 下单成功明确提示前往订单页支付:
- `apps/user_app/lib/ui/features/shop/shop_page.dart:59-70`
- `ClientRepository` 只有创建和列表方法,没有调用后端已有的支付、取消、确认收货接口:
- `apps/user_app/lib/data/repositories/client_repository.dart:43-117`
- 订单页只是三个只读列表:
- `apps/user_app/lib/ui/features/orders/orders_page.dart:7-47`
- 后端实际提供 `/shop/orders/:identity/pay``cancel``confirm-receipt`
- `backend/api/internal/routers/client.go:47-51`
**影响**
- 用户可以下单并占用库存,但不能在客户端完成支付、取消或确认收货。
**最小整改**
- 增加订单详情及基于服务端状态的支付/取消/确认动作。
- 支付请求必须持久化并复用幂等号,不能每次点击生成新值。
### P1-06 冻结或停用的钱包仍可在客户端执行资金操作
置信度:高
**证据**
- 平台允许把钱包改为启用、停用或冻结:
- `backend/api/internal/logic/platform/wallet/wallet.go:191-200`
- 客户端 `ensureWallet` 对已存在钱包直接返回,不检查状态:
- `backend/api/internal/logic/common/client_wallet.go:41-57`
- 商城支付和用户提现查询钱包时也未限制 `status`
- `backend/api/internal/logic/client/user/shop.go:167-177`
- `backend/api/internal/logic/common/client_wallet.go:366-398`
**影响**
- 风控冻结不能阻止支付和提现。
**最小整改**
- 所有资金写操作在事务内按 `status=enable` 锁定钱包;冻结后禁止新交易,仅允许受控冲正/退款。
### P1-07 上传能力仍是本地 Mock且只按扩展名判定文件类型
置信度:高
**证据**
- `backend/api/internal/logic/upload/upload.go:36-37,90-95` 明确为本地 Mock 存储。
- `backend/api/internal/logic/upload/upload.go:45-49` 只检查文件名扩展名和大小。
- `backend/api/internal/logic/upload/upload.go:79-83` 将客户端声明的 Content-Type 原样返回,没有校验文件签名或实际 MIME。
**影响**
- 伪装成图片/PDF/视频的任意内容可进入存储。
- 单机本地目录无法支持多实例、一致备份、受控下载或恶意文件隔离。
**最小整改**
- 校验魔数和解码结果,重编码图片,对视频/PDF进行独立扫描。
- 使用私有对象存储、短期授权访问、病毒扫描、审计和生命周期策略。
## 4. P2一致性、重试与维护风险
### P2-01 多处“幂等”只处理唯一键冲突,没有验证请求归属和载荷
置信度:高
**证据**
- 签收回执按全局 `request_no` 返回任意既有确认记录,没有校验订单和当前工作人员:
- `backend/api/internal/logic/client/staff/delivery.go:226-231`
- 商城支付成功后使用相同 `request_no` 重试,会先因订单状态不再是待支付而失败,无法返回原结果:
- `backend/api/internal/logic/client/user/shop.go:161-195`
- 创建工单遇到重复 `request_no` 直接返回数据库错误:
- `backend/api/internal/logic/client/user/address_ticket.go:98-109`
- 打卡重复请求返回时,响应中的 `work_status` 根据本次请求重新计算,而不是根据既有记录:
- `backend/api/internal/logic/client/staff/work.go:46-69`
**影响**
- 网络超时后的安全重试可能变成失败、误报成功,甚至返回另一订单的结果。
**最小整改**
- 幂等记录至少绑定:主体、资源、动作、请求载荷摘要和最终响应。
- 相同幂等键但载荷不同必须返回稳定冲突错误;相同载荷返回已保存结果。
### P2-02 服务 App 保存了签收幂等号,但提交时重新生成
置信度:高
**证据**
- 草稿保存 `request_no`
- `apps/service_app/lib/ui/features/work/work_detail_page.dart:114-123`
- 页面没有把该值传给仓库:
- `apps/service_app/lib/ui/features/work/work_detail_page.dart:124-136`
- 仓库在每次提交时重新生成 UUID
- `apps/service_app/lib/data/repositories/service_repository.dart:92-108`
**影响**
- 上传或请求响应丢失后,用户重试会使用新幂等键,可能重复形成签收动作。
**最小整改**
- `submitDeliveryReceipt` 必须接收并复用草稿中的 `requestNo`;删除草稿前保留服务端最终结果。
### P2-03 商城订单的组织字段在真实创建流程中永远为零
置信度:高
**证据**
- `EcOrder` 声明 `gas_station_id``delivery_point_id`
- `backend/api/internal/models/ec_order.go:15-16`
- 真实下单创建 `EcOrder` 时未赋值:
- `backend/api/internal/logic/client/user/shop.go:54-59`
- 仅 Mock seed 为这两个字段赋值。
**影响**
- 按气站/配送点统计、权限范围、履约分派或结算会得到空组织。
**最小整改**
- 若商城订单必须归属机构,应在服务端从服务关系或商品归属中确定并写入快照。
- 若业务确实为平台统一商城,应迁移后移除这两个误导字段及相关索引/展示。
### P2-04 三个 Web 管理端以复制方式维护,且配送端包含整段不可达配置
置信度:高
**证据**
- 三个 `src + scripts` 各约 1.9 万行。
- 平台端与气站端有 80 个逐字节相同文件,约 18,280 行;平台端与配送端有 79 个相同文件,约 18,199 行。
- 三端的 `CrudListPage.vue` 均为 1,175 行且内容相同。
- `frontend/delivery_admin/src/api/resources.ts:287-384` 复制了完整平台资源定义。
- `frontend/delivery_admin/src/api/resources.ts:386-426` 定义 `gasOverrides`,但导出只使用 `deliveryOverrides`
- `frontend/delivery_admin/src/api/resources.ts:428-470`
**影响**
- 一个通用缺陷需要在三处同步修复,极易发生漂移。
- 配送包携带与本端无关的大量资源、字段和动作配置。
**最小整改**
- 将 API 客户端、会话、通用 CRUD、字段渲染、权限和契约检查提取到工作区共享包。
- 各管理端只维护入口、主题和本端资源覆盖。
- 直接删除前先用引用检查确认不可达;当前 `gasOverrides` 已可由导出链证明不可达。
### P2-05 数据库自增 `id` 被作为公共 API 字段并在三端展示
置信度:高
**证据**
- 公共实体把内部主键序列化为 `id`
- `backend/api/internal/models/entity.go:10-16`
- 公共资源响应显式保留记录自身 `id`
- `backend/api/internal/logic/common/resource.go:450-486`
- 三端通用列表固定显示 `ID` 列:
- `frontend/platform_admin/src/views/shared/CrudListPage.vue:23-26`
**影响**
- `id``identity` 成为两个公开身份字段,增加前端误用和外部耦合。
- 连续主键还会泄露记录规模和创建顺序。
**最小整改**
- 数据库 `id` 本身不是冗余字段,不应删除;应从 HTTP 响应和前端移除。
- 迁移前检查外部调用方是否仍使用 `id`,提供兼容窗口。
### P2-06 两个 Flutter API 客户端缺少超时和统一的 401 会话失效处理
置信度:高
**证据**
- `apps/user_app/lib/data/services/api_client.dart:47-76`
- `apps/service_app/lib/data/services/api_client.dart:41-80`
- 请求直接等待 `_client.send`/上传结果,没有连接、读取或总超时。
- 401 只抛异常,没有清理安全存储中的 Token。
- 两个路由都仅以“Token 字符串非空”判断已登录:
- `apps/user_app/lib/app/router.dart:14-21`
- `apps/service_app/lib/app/router.dart:12-19`
**影响**
- 弱网下页面可能长期挂起。
- Token 过期或被撤销后,用户会停留在已登录路由并持续收到请求错误。
**最小整改**
- 建立共享客户端层统一超时、有限重试、取消、401 清会话和重新登录。
- 资金写操作只能依赖幂等键重试,不能盲目重放。
### P2-07 前后端契约检查只比较资源名、模式和路由字符串
置信度:高
**证据**
- `frontend/platform_admin/scripts/check-backend-contract.mjs:12-37` 通过正则读取 `define(name, mode)`,再检查资源路径和路由源码是否包含字符串。
- 未校验 HTTP 方法、动作路径、请求字段、必填项、枚举、响应字段和错误码。
- 气站、配送端脚本采用同类实现。
**影响**
- `contract:check` 通过不能发现支付动作缺失、字段漂移或错误 HTTP 方法。
**最小整改**
- 使用结构化 OpenAPI/Schema 生成客户端与字段类型。
- 至少对每个动作校验方法、路径、请求/响应结构,并增加真实路由契约测试。
### P2-08 钱包创建存在并发竞态,银行卡敏感字段加密错误被忽略
置信度:高
**证据**
- `ensureWallet` 采用先查后创建,唯一键冲突时不回查已有钱包:
- `backend/api/internal/logic/common/client_wallet.go:41-57`
- 首次并发请求时,一个请求可能收到数据库唯一约束错误。
- 绑卡时只处理卡号加密错误,身份证和手机号加密错误被丢弃:
- `backend/api/internal/logic/common/client_wallet.go:287-300`
**影响**
- 新用户并发访问钱包时出现偶发失败。
- 加密异常可能生成空密文但仍写入银行卡记录,造成不可恢复的数据缺失。
**最小整改**
- 使用 `ON CONFLICT DO NOTHING` 后回查,或在事务中锁定所有者。
- 每个敏感字段的加密错误都必须中止事务。
## 5. P3已证实或疑似冗余
### P3-01 钱包第三方账号字段只有 Mock 数据写入
置信度:中
**证据**
- `backend/api/internal/models/wallet_basic.go:11-14` 定义支付宝、微信账号及姓名四个字段。
- 生产业务中未发现写入入口;仅 seed 使用。
- Web 资源仍展示这些字段。
**判断**
- 当前属于“疑似冗余/未完成字段”,不能仅凭静态引用直接删除。
**建议**
- 核对线上非空率、导出消费者和未来渠道设计;确认不用后再做带回滚方案的迁移。
### P3-02 Web 与状态 Store 保留多组本端不可达能力和模板字段
置信度:中
**证据**
- 气站/配送端的通用平台 API 保留角色创建、角色菜单、平台账号等调用,但本端路由和资源未暴露这些页面。
- 三端用户 Store 保留 `job``organization``location``email``introduction``personalWebsite` 等模板字段,实际登录资料只填充账号、名称、头像、角色和菜单。
**建议**
- 先用 TypeScript 引用分析和运行时埋点确认不可达,再删除本端无关方法和状态字段。
- 共享 Store 只保留跨端最小会话模型,本端扩展单独声明。
### P3-03 离线草稿读取重复解析同一加密文件
置信度:高
**证据**
- `apps/service_app/lib/data/offline/encrypted_draft_store.dart:69-77` 先读取并 `jsonDecode` 外层载荷,随后再次读取文件并在 `_decrypt` 内再次解析。
**影响**
- 每次读草稿多一次文件读取和 JSON 解析,且前后两次读取理论上可能看到不同内容。
**最小整改**
- 读取一次字符串,完成结构校验后把同一值传给解密函数。
## 6. 字段与表冗余结论
| 对象 | 结论 | 依据 | 处理方式 |
|---|---|---|---|
| 数据库 `id` | 数据库内部必需,公共 API/UI 冗余 | API 同时提供 `identity`UI仍显示 `id` | 保留数据库列;移除公开序列化和 UI 展示 |
| `FinPayment` / `WalletPayment` / `GasorderPayment` | 已证实事实重叠,但不能直接删表 | 实际支付不写三表,看板却读其中一表 | 先确定唯一支付事实并迁移、对账 |
| `EcOrder.GasStationID/DeliveryPointID` | 当前生产流程未填充 | 仅 seed 写入 | 明确归属规则后补写,或迁移删除 |
| `WalletBasic` 支付宝/微信四字段 | 疑似冗余 | 仅 seed 写入 | 查线上非空率及外部消费者后决定 |
| 配送端 `gasOverrides` | 已证实代码冗余 | 定义后未进入导出链 | 可删除,并用静态检查防回归 |
| Web 会话模板字段 | 疑似冗余 | 未进入实际资料映射 | 引用和运行时确认后删除 |
没有将“只在响应、展示或 seed 中出现”的字段直接判定为可删。数据库删字段、删表前必须补充:
1. 线上非空率和取值分布;
2. API 网关/访问日志中的字段消费者;
3. 报表、导出、脚本和第三方集成引用;
4. 双写/回填/回滚方案。
## 7. 测试、静态检查与构建结果
已实际执行:
```text
backend/api:
go test ./... 通过
go vet ./... 通过
go build ./cmd/main/main.go 通过
backend/worker:
go test ./... 通过(无测试文件)
go build ./cmd/main/main.go 通过
backend/iot:
go test ./... 通过(无测试文件)
go build ./cmd/main/main.go 通过
apps/user_app:
flutter analyze 通过
flutter test 通过2 个测试)
apps/service_app:
flutter analyze 通过
flutter test 通过3 个测试)
frontend/platform_admin:
pnpm type:check 通过
pnpm lint 退出码 0112 warnings / 12 infos
pnpm contract:check 通过47 个资源)
pnpm build 通过
frontend/gas_admin:
pnpm type:check 通过
pnpm lint 退出码 0105 warnings / 12 infos
pnpm contract:check 通过20 个资源)
pnpm build 通过
frontend/delivery_admin:
pnpm type:check 通过
pnpm lint 退出码 0106 warnings / 12 infos
pnpm contract:check 通过18 个资源)
pnpm build 通过
```
说明:
- Web lint 的相当一部分告警来自工具无法识别 Vue 模板对 `script setup` 变量的使用,不能全部视为真实死代码;本报告仅列出能够从导出/调用链证明的冗余。
- 后端虽有若干测试文件,但没有覆盖用户钱包支付、用户提现、配送提现、验证码完整流程、工作人员预检强制执行等高风险路径。
- 两个 Flutter App 的测试主要覆盖模型和登录页面不覆盖商城支付、离线签收重试、401 会话失效或完整作业流程。
## 8. 建议整改顺序
### 立即处置
1. 关闭相关公网凭据并轮换全部已泄露密钥。
2. 关闭生产资金写入口,审计历史余额和提现。
3. 禁止生产环境启用固定验证码和 Mock 支付。
### 第一阶段:资金与身份
1. 重建钱包不变量、冻结额、统一流水和提现状态机。
2. 合并支付事实,完成历史数据对账。
3. 接入真实验证码并增加限流、审计和攻击测试。
4. 强制服务端工作人员作业资格守卫。
### 第二阶段:闭环与可靠性
1. 完成用户 App 支付/取消/收货闭环。
2. 修复所有幂等键的归属和载荷校验。
3. 修复服务 App 草稿幂等号传递、401 会话和网络超时。
4. 上线真实 Worker、IoT、对象存储及健康检查。
### 第三阶段:去重与契约
1. 抽取三个 Web 端共享包,删除不可达配置。
2. 用结构化契约替换正则和源码字符串检查。
3. 在确认线上数据和消费者后迁移疑似冗余字段/表。
## 9. 审计限制
- 未连接公网数据库、Redis、短信、支付、MQTT 或对象存储,未验证仓库中凭据是否仍有效。
- 未启动依赖外部数据库的完整 E2E 流程,结论来自当前代码可证明的控制流、数据写入和客户端调用链。
- 未使用 `docs/*` 推导功能缺口或业务要求。
- 未修改任何业务源代码;只新增本报告。
1. 确认 YAML 中的远程凭据仅指向隔离测试环境,并限制文件访问权限
2. 实现设备绑定、归属鉴权、安全事件、自动关阀、整改复检和开阀拦截
3. 为 ACK 增加持久化包号分配/命令相关标识及唯一活跃约束
4. 补齐 Worker、支付并发、Broker ACL/TLS 和跨进程故障恢复测试。
5. 完成 BF-01BF-15 的远程集成、真浏览器/Flutter E2E、支付沙箱和灾备演练后再评估发布。