# 洗车订单僵死问题 — 三道防线方案 ## 问题描述 用户点击「结算」后,服务器通过阿里云 IoT RRpc 向设备发送 `close_order` 命令。正常情况下,设备关机后会通过 AMQP 推送 `order_close` 事件,服务器收到后完成扣款和状态更新。 **异常场景**:`order_close` 事件未到达平台时: - 订单一直处于「开机 + 未支付」状态 - 设备一直处于「忙碌」状态 - 其他用户无法使用该设备 ## 方案概览 三道防线逐层递进,确保订单最终能被结算、设备最终能被释放: | 防线 | 触发时机 | 策略 | |------|----------|------| | 防线一 | 用户点击结算时 | RRpc 失败后重试 + 主动查询设备回退 | | 防线二 | 定时任务(每5分钟) | 扫描超时订单,查询设备兜底结算 | | 防线三 | 用户启动设备时 | 检查设备是否有僵死订单,有则先清理 | ## 防线一:closeOrder 增强 **文件**: `WashOrderServiceImpl.closeOrder()` ``` 用户点击结算 ├─ 发送 close_order RRpc ├─ 失败 → 重试 1 次 └─ 重试仍失败 → queryOrder() 主动查询 ├─ 设备已关闭 → 直接结算 ├─ 设备仍在运行 → forceCloseOrder() 强制关闭 └─ 设备不可达 → 提示用户稍后重试 ``` ## 防线二:僵死订单定时对账 **文件**: `StaleOrderReconciliationJob.java` **模块**: car-wash-admin **执行频率**: 每 5 分钟 **阈值配置**: `kym.stale-order-threshold-seconds`(默认 1800 秒 = 30 分钟) 扫描条件: - `order_status = 0`(开机) - `pay_status = 0`(未支付) - `start_time < now - threshold` 处理策略: 1. **查询设备订单状态** — 调用 `queryOrder()` RRpc - 设备返回已关闭 → 用返回数据执行结算 - 设备返回仍在运行 → 跳过(正常进行中) 2. **设备不可达,检查 DB 中设备状态** - 设备已空闲 → 用最后一次 `order_update` 保存的数据兜底结算,`close_type` 标记为 `reconcile` 3. **无法自动处理** → 标记 `stopReason` 为异常,等待人工介入 ## 防线三:启动前设备检查 **文件**: `WashOrderServiceImpl.createOrder()` → `checkAndResolveStaleOrder()` 在创建新订单前: 1. 查询该设备是否有未完结订单 2. 如有,调用 `queryOrder()` 确认设备实际状态 3. 设备实际已关闭 → 先执行结算,再创建新订单 ## 新增/修改文件清单 | 文件 | 操作 | 说明 | |------|------|------| | `car-wash-service/.../OrderSettlementService.java` | 新增 | 结算服务接口 | | `car-wash-service/.../OrderSettlementServiceImpl.java` | 新增 | 结算逻辑提取(从 OrderCloseEventHandler 抽取) | | `car-wash-service/.../OrderCloseEventHandler.java` | 修改 | 简化为委托 OrderSettlementService | | `car-wash-service/.../AwoaraEventHandlerFactory.java` | 修改 | 适配新构造参数 | | `car-wash-service/.../WashOrderServiceImpl.java` | 修改 | closeOrder 增强 + createOrder 预检 | | `car-wash-admin/.../StaleOrderReconciliationJob.java` | 新增 | 僵死订单定时对账任务 | ## 配置项 ```yaml # application.yml(可选,默认 1800 秒 = 30 分钟) kym: stale-order-threshold-seconds: 1800 ``` 建议设置为设备 `operationTimeout`(默认 3600 秒)的一半,既不会误伤正常订单,也能及时处理异常。