背景:一个抽象场景
在现场任务系统里,移动端、Web 端和后端经常围绕同一批资源工作:移动端负责扫码和现场确认,Web 端负责查看进度和配置,后端负责状态流转、资源扣减、占用释放和历史记录。
这类系统最怕两件事:一是用户看不清状态,二是系统结束后没有把状态清干净。前者会造成误扫、误取、重复操作;后者会留下资源占用、设备绑定或临时分配记录,下一轮任务就会被脏数据影响。
这次实践可以抽象成一个问题:当一个流程跨移动端展示、后端计算和终态释放时,如何保证状态既能被人理解,也能被系统正确关闭?
问题拆解
1. 状态不可见会放大现场误操作
移动端现场页面通常空间有限,但它承载的信息密度很高。用户需要在很短时间内判断某个资源是否可取、是否已执行、是否缺少位置、是否已经进入执行节点。
如果页面只展示资源名称,不展示唯一标识、规格、当前位置或特殊状态,现场人员只能依赖记忆和经验做判断。流程一旦出现共用资源、拆分资源、非智能位置、已上机资源等情况,页面上的“待处理”就不再是真正可操作的待处理。
所以移动端状态展示不是装饰,而是业务规则的一部分。它应该把关键判断依据直接暴露出来:
- 唯一标识用于确认扫到的是哪一个对象。
- 规格和名称用于辅助人工核对。
- 位置状态用于判断是否能亮灯或需要人工查找。
- 特殊状态用于说明为什么它不能按普通流程处理。
2. 异常计算要按真实可用量来算
终态页面常常会做提交前异常提示,比如实际数量是否会超过可扣数量、是否存在未使用的补充资源、是否可能产生负数扣减。
一个常见问题是只读取当前数量字段,忽略不同类型资源的数量语义。有些资源当前数量可能为空,但仍有可分配数量;有些资源虽然有关联记录,却并没有真正参与执行。如果异常提示直接用一个字段加总,就会把“展示数据”误当成“可扣事实”。
更稳的方式是为异常提示定义专门的可用量解析函数:
function resolveAvailableQty(item: AllocationView): number { if (!item) return 0
const currentQty = Number(item.currentQty) if (Number.isFinite(currentQty) && currentQty > 0) { return currentQty }
if (item.kind === "initial") { const reservedQty = Number(item.reservedQty) return Number.isFinite(reservedQty) && reservedQty > 0 ? reservedQty : 0 }
return 0}这个函数的价值不是代码本身,而是把“页面如何解释数量”集中起来。以后新增状态时,也能知道异常提示到底依据哪个数量口径。
3. 终态清理不能只改主状态
很多流程的“结束”并不是单表状态更新。它还可能牵涉设备占用、绑定关系、位置槽位、资源占用、临时明细和后续任务推进。
如果结束流程只把任务状态改成已完成,却没有释放相关绑定,下一次任务就会遇到这些问题:
- 设备仍显示被上一个任务占用。
- 移动端列表显示资源仍在执行位置。
- 位置槽位仍挂着旧标签。
- 共享或临时分配记录继续影响后续分配。
这类问题的表现往往很分散,但根因通常是终态清理边界不完整。
方案设计
移动端:让状态标签和操作规则保持同源
移动端可以把复杂状态拆成几个小判断函数,而不是在模板里堆条件。比如“是否缺少位置”“是否已在执行节点”“是否不适合亮灯”都可以成为独立判断。
function isUnlocatedResource(item: ResourceItem): boolean { return Boolean( item.uniqueCode && !item.warehouseId && !item.slotId && !item.warehouseName && !item.location, )}
function formatLocation(item: ResourceItem): string { if (item.empty) return "无需处理" if (item.executing) return "执行中" if (isUnlocatedResource(item)) return "暂无位置,请扫码确认" return item.locationText || "-"}这样做有两个好处:模板只负责展示,状态函数负责规则;点击、亮灯、扫码、列表展示也可以复用同一套判断,避免“列表显示可取,但点击又说不可取”的割裂。
后端:用快速路径和兜底路径定位当前记录
现场扫码时,后端经常需要定位“当前正在进行的记录”。理想情况下,主记录上已经有直接索引字段,可以快速查询。但历史数据或多端写入不一定总是完整,因此可以保留一个受控的兜底路径。
private ProcessRecord findActiveRecord(Long nodeId) { ProcessRecord record = recordMapper.selectActiveByNodeId(nodeId); if (record != null) { return record; } return recordMapper.selectActiveByJoin(nodeId);}这里的关键是“兜底要可控”。不要把所有进行中记录拉到内存里再循环查询明细;如果确实需要反查,优先让数据库用 JOIN 完成定位,并且限定状态和数量。
终态:把清理动作列成事务内清单
终态确认应该像一个小型状态机,而不是一个简单的 update status。可以把它拆成清晰的步骤:
@Transactionalpublic void confirmFinish(Long taskId, int actualQty) { Task task = taskMapper.lockById(taskId); validateCanFinish(task);
deductExecutedResources(taskId, actualQty); updateTaskFinished(taskId, actualQty); advanceRelatedFlow(taskId); releaseDeviceBinding(taskId); clearExecutionSlots(taskId); releaseResourceOccupation(taskId);}顺序也很重要。通常应先完成扣减和主状态更新,再释放附属占用;释放前要确保它只处理当前任务相关的数据,避免误清理其它任务正在使用的资源。
flowchart LR A["移动端展示状态"] --> B["扫码或确认"] B --> C["后端定位当前记录"] C --> D["计算真实可用量"] D --> E["终态确认"] E --> F["扣减与状态推进"] F --> G["释放绑定和占用"]关键实现示例
用统一状态函数驱动展示和操作
移动端页面最好避免每个区域自己写一套判断。状态标签、位置文案、按钮可用性和扫码过滤都应该围绕同一组函数。
function getResourceState(item: ResourceItem): "empty" | "blocked" | "executing" | "pending" { if (item.empty) return "empty" if (item.blocked) return "blocked" if (item.executing || item.deferredExecution) return "executing" return "pending"}
function canScan(item: ResourceItem): boolean { return getResourceState(item) === "pending"}如果状态函数定义得清楚,页面就不需要靠文案兜底。用户看到“执行中”,系统也确实不会把它放入待扫码集合。
终态清理使用条件更新
释放设备或资源占用时,要尽量带上当前任务条件,防止把其它任务刚写入的新状态清掉。
machineMapper.update(null, new UpdateWrapper<Machine>() .eq("id", machineId) .eq("current_task_id", taskId) .set("status", MachineStatus.IDLE) .set("current_task_id", null) .set("current_binding_id", null));这个 eq("current_task_id", taskId) 很关键。它让清理动作只作用于自己负责的那一轮任务,降低并发或补偿操作带来的误伤风险。
常见坑
把“没有亮灯位置”当成“没有资源”
现场系统里,资源没有可亮灯位置,不代表资源不存在,也不代表不能通过扫码确认。移动端提示要说清楚是“暂无位置,需要扫码确认”,而不是泛化成“未分配”或“不可处理”。
页面异常提示和后端扣减口径不一致
如果页面用当前数量计算,后端用可分配数量扣减,就会出现页面提示不足但提交成功,或者页面提示正常但后端拒绝。数量口径应该被显式命名,并在前后端保持同一语义。
结束流程遗漏附属状态
设备、绑定、槽位、资源占用都属于流程终态的一部分。只更新主任务状态,短期看像是成功,长期会把问题推给下一次操作。
可复用经验
- 现场页面的状态展示要服务操作判断,不只是补充信息。
- 特殊状态应封装成函数,让展示、扫码、亮灯和提交共用同一语义。
- 异常数量计算要明确区分当前数量、预占数量和可扣数量。
- 后端定位当前记录优先走直接索引,必要兜底也应交给数据库限定查询。
- 终态清理要列清单,并放在事务边界内完成。
- 释放设备或资源占用时,条件更新要带上当前任务关系。
总结
多端现场流程的稳定性,很多时候取决于状态是否被正确解释。移动端要让用户看清当前资源为什么能操作或不能操作;后端要用清晰的数量口径和查询路径确认事实;终态流程要把设备、位置、资源和绑定关系一起收尾。
当状态可见、计算准确、清理完整时,系统才不会把上一轮任务的残留问题带到下一轮任务里。