ADMIN_MINI_ENGINEERING_REVIEW.md 11 KB

后台与小程序工程审查

2026-10-08 修复进展:本文保留原审查证据。交易快照、套餐生命周期、财务来源、展示/刷新、高德节流及相关回归已修复;新增的配送任务关联缺陷也已修复。完整结果与仍需确定的业务规则见 修复记录。

审查日期:2026-10-08。

范围:供水后台、套餐、购物车、待支付订单、企业余额、收款与权益、身份授权、道路规划、构建发布。采用代码阅读、实际公开接口查询和独立事务回滚复现。未调用微信支付或修改正式业务数据。

总体判断

现有系统已经有值得保留的基础:后台与小程序共享供水模型;小程序身份与后台账号分开验证;服务端校验企业/司机/代理归属;金额使用整数分;写命令有事务、幂等和权限复核;供水权益与实物库存分别核算。

当前主要不足是业务规则在前端、后台、支付服务之间没有形成统一契约。尤其是订单快照、套餐变更、支付来源与余额入账,需要作为一个完整交易模型设计,而不能仅复用现有 CRUD 和线下购水登记。

优先级:P1 为资金/履约一致性和正式运营前的必要闭环;P2 为用户正确理解、权限维护与可靠性;P3 为发布与规模增长所需的工程改善。能力缺口和设计风险不等同于已发生的故障。

1. P1:支付扣款与权益发放读取不同版本的套餐——已复现

证据:app/miniapi/service/CheckoutService.php:76 保存报价快照;:188 支付使用订单金额扣余额;:229 再调用 WaterBusiness::purchase(),后者在 plugin/saiadmin/app/service/WaterBusiness.php:150 读取当前套餐。

复现结果:

项目 数值
下单套餐 30 桶、699 元
后台下单后修改 60 桶、799 元
实际扣余额 699 元
实际生成收款记录 799 元
实际发放权益 60 桶
订单结果 paid

这不是展示差异,而是账务和权益不一致。套餐 SKU 同样可修改,支付路径没有按订单快照核对其一致性。

建议:确定订单创建后锁价/锁权益的业务规则。订单保存价格、SKU、桶数和套餐版本,支付及权益发放共用该快照;如业务要求使用最新套餐,则先拒绝变更订单并要求用户重新报价确认。增加断言:余额扣减 = 支付流水 = 对应收款金额之和,发放 SKU/桶数 = 已确认订单明细。

2. P1:套餐删除和停用未与待支付订单形成生命周期规则——删除后支付失败已复现

证据:WaterBusiness.php:137 物理删除套餐;支付仍通过套餐 ID 读取当前记录。

复现:先创建待支付订单,再删除套餐,余额支付返回“记录不存在”,订单仍为 pending。已支付历史订单和已发权益保留,但这不能保证待支付订单仍可履约。套餐停用时同一调用路径也会拒绝支付,需要明确产品规则。

建议:区分商品可售状态、归档状态与订单有效状态。优先以停用/归档控制新销售;针对已下单订单明确继续履约或主动失效的政策,并在后台确认框展示受影响待支付订单数量。物理删除仅适用于符合业务删除条件的记录。

3. P1:余额支付被财务归类为线下收款——代码与格式化路径已核实

证据:支付调用 WaterBusiness::purchase();其审计备注固定为“已核实线下收款,登记套餐权益”;WaterFinanceDashboard.php:35 固定返回 type_label='线下购水登记'。

影响:财务不能通过结构化字段可靠地区分线下登记和余额消费。以后接入充值,会混淆资金入账与余额消费;是否计收入还需要明确会计口径。

建议:记录业务来源、支付渠道、checkout ID、支付流水 ID、收款凭据及操作主体类型。充值入账、余额消费、线下购水和退款应分别可追踪与对账,不仅通过备注或编号推断。

4. P1:企业余额已有消费,但正式入账/退款流程未闭环——能力缺口

证据:小程序消费读取 sa_water_mini_enterprise_balance(app/miniapi/model/MiniEnterpriseBalance.php:9);现有后台用户充值修改 sa_rental_user.wallet_balance/card_gift_balance(plugin/saiadmin/app/logic/user/RentalUserLogic.php:116),属于旧租赁个人账户;供水路由没有对应企业余额充值/退款业务入口,财务能力明确 refunds=false。

这两类账户应保持清晰的领域边界;不能假设旧后台充值会增加小程序企业余额,也不能仅为让页面金额一致而直接合并钱包。

建议:为供水企业余额定义正式入账来源、凭据、审核/回调、幂等、流水和对账流程;为退款定义未使用/已冻结/已履约权益的处理规则。不能把已配送实物库存通过简单余额退款自动还原。

5. P2:小程序存在没有业务实现支撑的明确承诺——已核实

证据:water-mini/src/pages/packages/cart.vue:182 写着“两个套餐将分别生成水卡,配送时优先配送最早到期的同规格套餐”;实际 EnterpriseQuery.php:205 是企业+SKU 汇总账户,:208 明确 expiry_deduction=false。页面还展示“增值税电子普通发票”,提交却始终 invoice:null(cart.vue:106),也没有真实开票能力。

影响:用户理解的权益和交付内容与系统实际行为不同;水卡数量文案还被固定为“两”份。

建议:先让文案准确描述当前能力。是否增加独立水卡、权益有效期、到期优先核销、发票申请与开票,应作为明确业务需求分别实现,能力未开放时给出准确状态。

6. P2:套餐分类、有效期和设备字段缺少完整管理模型——已核实

证据:Catalog.php:33 至 :35 返回 category、valid_months、includes_device 为 null;小程序 packages/index.vue:33 通过名称推断分类;packages/detail.vue:50 将 null 当成 false,显示“不含设备”。

后台状态并没有失效:当前查询要求套餐和 SKU 均启用,这部分规则正确。问题是其他展示字段没有真正对应后台配置。

建议:建立正式分类和字段定义;分类由后台配置并由服务端分页筛选。没有配置的字段显示“未配置/待确认”,不要将未知设备信息直接变成“否”。有效期字段不能只增加展示,必须同时明确权益核销规则。

7. P2:小程序目录、购物车与金额展示存在不同步——已核实

证据:packages/index.vue:56 仅空列表时在 onShow 加载;购物车持有旧 Package 对象,服务端报价不会自动替换这些对象;PackageCard.vue:77、cart.vue:136/:195 使用 money(...,0),将整数分金额四舍五入为整数元。

影响:后台停用、删除或改价后,旧页面可能仍显示可选套餐;购物车明细金额与服务端总报价可能不同;699.50 元可显示为 700 元。

建议:返回套餐页按合理策略刷新/检查版本,购物车结算前校验可售状态与最新快照,失效套餐明确标记;统一使用准确到分的价格展示,估算单价则明确标注参考含义。

8. P2:后台与小程序架构规范执行不一致——已核实;权限粒度属于设计限制

证据:WaterController.php:31 在控制器中查询角色和权限,:265 通过一个大型 action 分支处理多个业务;小程序已有 Access、命令服务和独立司机处理器。后台编辑和删除套餐都使用 packages:write,身份绑定在 IdentityManagement.php:19 固定仅 ID 1 可操作。

影响:增加新动作需要修改通用控制器,权限语义与业务流程耦合,多人运营难以独立授权删除或身份管理。这里不代表已确认存在越权漏洞。

建议:将后台资源授权迁至集中服务,按业务模块拆分命令处理;按运营需求独立定义套餐编辑、删除、身份管理等权限。后台与小程序可保留不同身份体系,但应共享业务规则。

9. P2:地图调用缺少按配额设计的可靠性机制——已有超限反馈,代码已核实

证据:saiadmin-artd/src/utils/waterAmap.ts:144 逐对采集道路成本;缓存仅在单次计算内;没有统一 QPS 控制、跨次缓存及超限退避。请求规模随站点数量近似平方增长,即使单次串行请求也不能保证多个页面共享 Key 时不超限。

建议:按 Key 的实际配额建立限速、缓存、退避与可取消队列,失败展示明确的重试状态;业务规模增长后考虑集中管理道路查询。优化失败应保留人工调度能力,不能填造道路数据。

10. P2/P3:测试与发布缺少围绕交易规则的统一验收流程——流程风险

现有测试覆盖正常下单、余额支付、幂等、权限和库存事务,但本次复现表明“下单后改套餐”没有被阻止。此前源码修改完成但构建失败,4173 预览仍显示旧文件,也实际暴露了源码/构建/运行版本不一致。

water-mini/scripts/publish-preview.mjs:8 直接递归覆盖发布目录,旧哈希资源持续保留;未看到该入口包含类型检查、业务测试、版本标记及切换/回退步骤。后台和小程序 API 地址也有本机/LAN 地址默认值,真机和正式环境需要明确环境配置。

建议:定义一个可重复的验证入口,执行两端类型检查、构建、核心后端回滚测试及真实 HTTP 验证;构建产物标记版本,发布时验证页面和接口版本。增加改价、改桶数、换 SKU、停用/删除套餐、重复支付与退款规则回归。发布采用版本目录或可回退的切换机制;旧资源按缓存兼容期保留和清理。

其他财务数据建模风险

WaterOrder.php:13 通过凭据 TEST- 前缀或套餐名包含“测试”识别模拟数据。名称和备注不应决定资金记录性质,否则改名或正式商品包含该词会造成分类错误。建议使用明确的数据来源/模拟标记,并隔离测试入账。

尚需进一步验证的并发风险

余额支付先锁企业再进入 package→SKU 的购买路径;线下购买先锁 package→SKU→企业。这两条路径的锁顺序不一致,有形成死锁的条件。本次未执行多连接并发复现,不能标记为已发生故障。应统一锁顺序,并用并发测试验证锁等待、回滚和受控重试。

建议实施顺序

  1. 订单快照与支付/权益/收款一致性;套餐变更与删除规则。
  2. 支付来源、企业余额入账及退款对账;补交易生命周期回归。
  3. 修正小程序水卡/发票/未知字段文案,刷新购物车并统一金额精度。
  4. 后台商品模型与权限边界、地图限速、统一构建发布流程。

本次复现只创建独立 fixture,外层事务最终回滚,用户、订单、套餐及收款数量恢复。审查没有修改业务实现。