管理端开发日记④:三种异常处置流程的实现

异常工单真正难的地方,从来不是点一下“处理”。取消、重派和补发看起来都是解决问题,背后修改的数据却完全不同:有的终止原任务,有的保留原订单并生成新链路,有的还涉及退款。
1. 统一入口,不强行统一业务
三种流程共用 ExceptionProcessPage 和 useExceptionProcess.js,但不会为了复用而把所有字段揉成一团。
const PROCESS_TYPES = { CANCEL: '取消退款', REDISPATCH: '重新派送', RESHIPMENT: '补发订单'}公用部分包括异常概览、原订单信息、凭证、处理日志和底部操作区;差异部分由 processType 决定请求、表单和提交动作。
2. 页面阶段:登记与跟踪
处置页并非只有“未处理/已处理”。重派和补发在创建派生订单后还需要继续跟踪,因此定义两个页面阶段:
const stage = computed(() => { return ticket.value?.resultOrderId ? 'tracking' : 'registration'})registration:确认方案、填写原因、创建后续任务;tracking:展示新订单、新飞行任务和合并后的流程日志。
这个阶段模型让刷新页面后仍能根据服务端数据还原视图,不需要依赖浏览器内存中的“刚刚提交成功”。
3. 取消退款流程
取消流程需要先判断当前订单和飞行任务是否允许取消。如果任务仍在 WAIT_TAKEOFF,先停止任务,再调用取消退款接口,最后更新异常工单状态和用户消息。
读取原订单与退款信息 ↓校验订单是否仍可取消 ↓取消待起飞任务(如存在) ↓提交取消/退款 ↓更新工单与通知这一顺序不能随意交换。如果先把工单关闭,再取消飞行任务失败,就会出现“页面显示处理完毕,但无人机任务仍在”的数据裂缝。
4. 重新派送流程
重派适用于原货物仍可继续配送,但需要重新安排飞行任务的情况。它通过专用接口建立新的调度关系,同时保留原订单作为问题来源。
重派编号会带有可辨识前缀和序号:
function buildRedispatchNo(sourceOrderNo, sequence) { return `RP${sourceOrderNo}-${String(sequence).padStart(2, '0')}`}这样客服从编号就能判断它不是独立的新订单,而是某张订单的再次派送。
5. 补发流程与子订单
补发意味着需要创建一张派生订单。新订单复制必要的收货、货品和服务信息,但必须清空自身主键、当前任务等运行态字段:
function createDerivedOrder(original, sequence) { return { ...original, orderId: undefined, currentTaskId: undefined, parentOrderId: original.orderId, orderNo: `${original.orderNo}-B${sequence}`, orderStatus: 'WAIT_DISPATCH', deliveryFee: 0 }}这里最容易出错的是“复制得太完整”。如果把原订单 ID 或已完成任务一起提交,新订单就会继承不属于自己的状态。派生订单应复制业务事实,不复制数据库身份和运行过程。
补发完成后,还要分别写入原订单和子订单的流程日志,并把子订单 ID 写回异常工单的 resultOrderId。这三个动作建立了可追踪的关系链:
异常工单 ── resultOrderId ──> 补发子订单 │ │ └── orderId ──> 原订单 <── parentOrderId6. 合并流程日志
进入跟踪阶段后,页面同时加载原订单和派生订单日志。合并时按 processId 去重,再按操作时间排序:
const logMap = new Map()
originalLogs.concat(childLogs).forEach(log => { logMap.set(String(log.processId), log)})
const mergedLogs = [...logMap.values()].sort((a, b) => { return new Date(a.operateTime) - new Date(b.operateTime)})用户看到的是一条连续故事,而不是两张互不相干的日志表。
7. 异步加载的容错
处置页会并行读取订单、凭证、日志、退款、子订单和飞行任务。非核心数据使用 Promise.allSettled,关键动作则必须串行并在失败时中止。简单说就是:展示数据可以“尽量多拿”,写业务数据必须“每一步都算数”。
8. 本篇小结
一致性与恢复能力
三种流程都包含多个写动作,理想情况下应由后端事务或专用业务接口保证一致性。前端不能通过连续调用几个 CRUD 就假装拥有事务能力。若接口暂时拆分,页面至少要做到:前一步失败不执行后一步、错误信息明确、重新进入页面能根据工单与子订单数据恢复阶段。
页面内存不是流程凭证工单状态 + resultOrderId + 流程日志才是恢复依据重复点击“补发”时,后端需要检查是否已有 resultOrderId;重复取消时检查订单和退款状态;重派接口则要避免生成两条有效任务。前端的 submitting 只拦住当前设备上的连点,真正的幂等仍在服务端。
为什么保留原订单
重派和补发都不直接覆写原订单,因为原订单包含用户最初提交的事实、审核意见和失败过程。派生对象记录后续补救,父子关系负责追溯。若直接把原订单从“失败”改成“待调度”,历史看起来会像失败从未发生,报表口径也会被悄悄改写。
三种异常流程让我真正感受到状态机不是画在文档里的箭头,而是数据之间不能走错的路。取消要收尾,重派要续跑,补发要另起一条支线。把关系理顺以后,复杂流程就不再像一锅面条——至少能看清哪根是自己的。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!





























