干工厂信息化这行将近十年,ERP和MES的集成项目接触过不少,但像这次这样,两套系统源代码全部交付、还带WPF上位机和AGV联动场景的,确实值得拿出来好好拆一遍。这个项目整体是“ERP + MES + WPF上位机 + AGV调度”四位一体,适合正在规划车间数字化改造、或者准备自研MES的团队参考,尤其对做C#方向的开发者来说,是一条很完整的实战路线。
先说清楚这套东西解决什么问题:ERP管的是“企业级资源”(订单、物料需求、采购、财务),但车间里实际发生了什么,ERP往往看不到。MES就是补这一段生产执行层的空白,管工单下发、工序流转、报工、质检、设备状态。而WPF上位机在这里承担的是MES面向车间现场的交互入口——操作工在工位终端上报工、扫码、看任务、触发异常,管理员在办公室看看板、处理报警。AGV又是另一个独立系统,负责物料搬运,MES需要把搬运任务交给它,还得盯着它有没有送到、有没有超时、货有没有拿错。
接下来的内容,我会按这套系统的设计思路、WPF上位机核心模块、ERP与MES的集成方案、AGV联动逻辑、以及我实际调试中踩过的坑这个顺序来讲。里面所有表结构、接口设计、任务状态机,都是基于我从源代码里读到的实现方式,再结合通用实践还原出来的,你可以当成一套可直接落地的案底来抄。
1. 整体架构设计与方案选型背后的逻辑
1.1 系统边界:哪些事交给ERP,哪些事交给MES
很多工厂在推信息化时犯的第一个错误,就是让MES去管ERP的活,或者让ERP下钻到工序级。这套系统的边界切得挺清楚,值得拿出来单独说。
ERP这边只做三件事:物料主数据、生产订单(工单)下达、完工后的库存回冲。换句话说,ERP只管“这个月要生产什么型号、数量多少、原材料够不够、产出之后库存怎么记账”。至于这个工单在车间里走到了哪道工序、谁干的、用了多少时间、中途有没有不良,ERP一概不碰。
MES这边则从接收ERP生产订单开始接管后续一切。它把ERP一张整单拆成工艺路线上的多道工序任务,每道工序分配到具体工位、绑定到具体设备,操作工扫工单条码、扫物料条码、扫员工工号,系统记录开工时间、完工时间、报工数量、不良数量。这里有个关键设计:报工不是一单一次,而是每个工序都有独立的报工记录,这样后面做工序追责、设备OEE统计、计件工资核算,都有原始数据可以查。
WPF上位机在整个体系里,属于MES的“展示层+采集层”。它负责把MES后台数据库里的工单、任务、质量信息呈现到工位终端上,同时接收操作工的下拉选择、条码扫码、按钮点击这些输入。为什么选WPF而不是Web端?现场需求决定的:一是车间网络环境复杂,设备经常断线,WPF客户端断线重连比Web页面掉线后的恢复体验自然得多;二是工位终端需要对接扫码枪、电子秤、报警灯这类硬件,WPF通过串口和USB通信比Web页方便太多;三是实时看板那种大屏刷新效果,WPF的渲染性能在Windows工控机上明显优于浏览器页面。
1.2 技术栈选型:C# WPF在这套系统里的位置
这项技术选型非常稳。C#的WPF脱胎于.NET生态,是Windows桌面端开发最成熟的一套UI框架。它最大的优势有三个:
第一,数据绑定能力极强。MES上位机这种场景,页面上密密麻麻全是实时刷新的数据——当前工单、待加工数、已完成数、设备状态、报警信息。如果用传统方式写,就是给每个控件赋值,代码量大且容易漏。WPF的MVVM模式,把数据和界面解耦,数据变了界面自动跟着变。这套系统里所有看板页面都是这个思路:ViewModel里定义好属性,前端视图绑定,后台线程更新数据,界面自动刷新。
第二,控件模板灵活。工位终端要显示的颜色、字体、布局,不同车间要求不一样。WPF的样式和模板机制可以做到一个按钮在不同状态下换颜色换形状,工具看板里设备的运行、空闲、故障三种状态,直接通过DataTemplate切换,比MFC或者WinForms时代硬编码画界面灵活得多。
第三,硬件交互能力。C#可以很干净地调用Windows API和第三方库,接入扫码枪(通过串口或HID键盘模拟)、读写器、电子秤、IO卡。这套系统的工位端就接入了扫码枪,工单号和序列号不需要手动输入,扫码枪把字符串传给输入框,自动触发查询,整个过程不到一秒,操作工没有任何学习成本。
数据库选型是SQL Server,理由也很务实:现有团队对SQL最熟,事务支持好,和.NET的配合最顺。单机部署在车间服务器上,只在内网访问,没有远程大规模连接的压力,这个选型完全够用。
1.3 这套系统的核心亮点在哪
两套系统源代码一起交付这个情况本身就说明一个问题:集成是预设计好的,不是后面硬凑的。我总结下来有三个亮点:
第一个亮点是“任务流贯穿全链”。从ERP的生产订单,到MES的工单,再到工位工序任务,最后到AGV的搬运任务,所有环节都通过一种“任务状态机”串起来。任何环节的状态变化,都会驱动上下游动作。比如AGV把料送到了工位,传感器给上位机一个到位信号,上位机自动把工单状态从“待料中”切换成“可开工”,同时给操作工弹出提示。不需要任何人工干预,这是我觉得整个系统设计最见功力之处。
第二个亮点是“同一套代码库,两种使用形态”。WPF上位机在服务器上跑的是管理端,功能全开,包含工单管理、人员管理、报表查询、异常处理;在工位终端上跑的是操作端,界面精简到只剩当前任务、扫码区、报工按钮和报警按钮。同一个解决方案,编译时通过配置切换启动界面。这样做的好处是维护一套代码,坏处是打包和发布时得多留个心眼,后面讲部署的时候我再细说。
第三个亮点是“数据驱动的防错机制”。代码里大量使用了条码唯一性校验和工单工序校验。举个例子,操作工要投料,必须先扫工单条码、再扫物料条码,系统核对这个物料是不是该工单BOM里指定的、数量够不够、批次对不对,全部通过才放行。在源代码里这个逻辑写得非常严密,各种NULL判断和超时回退都做了,看得出来是经过真实车间验证过的代码。
2. WPF MES上位机的核心模块与实现要点
2.1 权限与登录:三级用户体系是怎么设计出来的
这套系统的用户权限模型是经典的RBAC三层权限:用户-角色-功能点。用户表存账号密码和所属角色ID,角色表定义角色名称(比如系统管理员、车间主任、班组长、操作工、质检员、维修工),功能点表存的是每个页面和按钮的唯一编码。
每个WPF页面初始化的时候,都调用一个全局权限服务来判断当前登录用户能不能访问这个页面、能不能看到某个按钮。这个不是只在按钮上隐藏,而是从数据层就做了限制——用户在查询工单时,SQL语句会根据其角色自动加上车间过滤条件,班组长只能看到本班组的任务,车间主任能看到全车间,操作工只能看到分配给自己的任务。
这里面有个容易被忽视的细节:密码处理。源代码里用的是哈希加盐存储,不是明文。很多内部系统的密码都是明文存或者简单的MD5加密,一旦数据库泄露就是裸奔。这套系统加盐处理的做法是值得所有MES开发者学习的——哪怕只是车间内网系统,密码安全也不能省。
2.2 工单与工序任务的拆分逻辑
MES的底层操作单位是工单,但真正让车间运转的是“工序任务”。系统从ERP拿到一张生产订单后,会根据这个产品对应的工艺路线,把它展开成多道工序任务。比如一个“车削→铣削→钻孔→去毛刺→终检”的产品,在MES里会生成五道工序记录,每道记录关联工位、设备、标准工时、所需物料。
关键点来了:工序任务的状态不是完全级联的。也就是说,第一道工序报工完成后,第二道工序并不会自动开工,而是处于“待开工”状态,必须由班组长或调度员在管理端手动下发(或者设置自动流转规则)。为什么要这么设计?因为实际生产中,不是所有工单都会按顺序走完所有工序,有的会跳工序,有的会返工,有的会中途暂停换生产其他急单。如果系统强制工序间自动流转,遇到现场插单、换线、返工这些正常操作,整个流程就卡住了。
所以代码里工序任务的枚举大概是这样写的:
NewCreated(已创建,待下发)Released(已下发到工位,待开工)Processing(已扫码开工,进行中)Paused(暂停,比如中途设备故障)Completed(报工完成)Closed(质量确认关闭)Rejected(不良,退回上一工序或转入返工)
手动控制工序状态切换,配合每道工序独立的“开始/暂停/完工”按钮,这个设计在真实车间里非常好用。我见过很多MES项目卡在“自动流程”上,原因就是车间里的不确定性远超过系统设计者的想象。
2.3 数据采集与看板刷新:WPF的绑定和多线程实战
这套系统最吃性能的功能就是实时看板。车间大屏上要同时显示几十台设备的状态、当前工单、产量趋势、异常报警,数据每秒都在变。如果每次刷新都直接操作UI控件,用不了多久界面就会卡死。
代码里的做法是三层缓冲:
第一层,设备数据采集线程通过Modbus TCP 或 OPC连接PLC,把读到的数据直接丢进内存中的公共数据缓冲(一个线程安全的字典)。这一层不做任何UI操作,采集线程只管“读数据、填缓冲”。
第二层,一个定时器每200毫秒从缓冲里取一次快照,更新各自ViewModel中通过RaisePropertyChanged通知的属性。WPF的Binding系统会自动把属性变化推到界面上,不直接操作控件就是安全的。
第三层,看板的某个局部区域(比如报警列表)如果变化频繁,直接走VirtualizingStackPanel虚拟化,只渲染可见区域的内容,避免上千行报警记录全量加载导致内存爆掉。
这里有个很关键的点:WPF的UI线程(Dispatcher线程)不能阻塞。凡是涉及数据库查询、网络请求这种耗时的操作,代码里统一用async/await做异步化,返回后通过Dispatcher.Invoke或者直接依赖Binding机制回到UI线程更新。我在实际项目中见过不少团队,功能都对,界面就是卡,多半是同步查询数据库把UI线程堵死了。这这套系统的异步处理方式可以直接抄:所有Repository方法返回Task<T>,ViewModel里全是await,界面再怎么刷新都不会假死。
2.4 报工与质检:数据是怎么从现场进到系统里的
操作工完成一道工序后,在工位终端上点击“完工报工”,会弹出一个小窗口:输入完成数量、不良数量,选不良原因代码(比如划伤、尺寸超差、漏加工)。如果这道工序要求首检(从工艺路线表里配置标志位获取),系统会在报工前强制弹出一个首检录入框,只有质检员填写了首检数据才能继续报工。
这个设计看起来简单,但对质量追溯特别重要。因为后续要查某个批次的质量记录、某道工序的不良率、某个操作工的历史报工,全都依赖这些报工记录。数据库里报工表的主键是工单ID+工序ID+操作工ID+报工时间,一个工单可以有多条报工记录(比如前后两个班次分别报了数量)。不良数量单独存放,不混在合格数量里,这让后面SQL统计不良率变得很简单。
质检模块支持两种模式:报工前的首检和报工后的巡检。首检数据单独存表,关联工单工序;巡检是独立功能页面,质检员在WPF管理端扫码录入抽样数量、测量值、判定结果。测量值的上限下限来自检验标准配置表,超出范围自动标红拒绝。这套源代码里还把检验标准的录入做成了Excel导入,现场换产品的时候,工艺人员不需要一行一行敲,一个Excel模板贴进去就成。
3. ERP与MES的集成方案:数据是怎么在两个系统之间流动的
3.1 主数据同步:物料、BOM、工艺路线的来源
ERP系统是主数据源头,MES这边不直接改物料主数据,所有物料、BOM、工艺路线的变更,都从ERP通过接口同步过来。这套系统的接口方式选的是“API调用 + 中间表兜底”的双通道方案。
正常流程是:ERP侧的开发包在物料信息变更后,调用MES开放的WebAPI接口,把数据推过来。MES接收后先写接收日志,再校验数据完整性,通过后写入正式表,更新缓存。如果ERP掉线或者接口调用失败,还有一种兜底方式是让ERP直接把数据导出到共享文件夹的SQL脚本,MES的定时任务去扫描这个文件夹,发现新文件就执行导入。
这个双通道看起来多此一举,但在实际工厂里非常管用。很多ERP部署在很老的环境上,有时候WebAPI接口有问题,或者老系统不稳定,一旦接口挂掉,MES的新物料进不来,整个车间就只能手工报工。有中间表兜底就不怕这个,哪怕ERP那边连一台电脑的数据库都连不上,只要IT能把脚本文件拷出来,MES一样能同步数据。
物料编码的规则统一由ERP制定,MES侧全部采用“只读+校验”。MES在接收新物料时,校验编码格式、物料类型、是否含有禁止字符(比如空格、中文括号),不合格的直接拒绝并给ERP返回错误日志。这是防止脏数据腐蚀MES所有下游模块的最重要防线。
3.2 工单下发与完工回写的状态一致性处理
工单集成是这个系统最复杂的部分。ERP创建生产订单后,通过接口推给MES;MES确认接收后,把ERP订单号作为外键存到MES的工单表。后续所有的工序流转、报工、质检都挂在MES工单ID下。
完工之后,MES还需要把结果回写给ERP:完成数量、完工时间、不良数量、消耗的物料清单和数量。ERP拿这个做库存冲减和成本核算。
这里涉及一个经典的分布式事务难题:两个系统分别有自己的数据库,无法用本地事务保证一致性。代码里应对思路是“状态字段+补偿任务”。
比如ERP推送一个工单给MES,接口参数里带上一个唯一请求ID,MES处理完成之后,在工单表里写一条状态为“已同步”的记录。如果MES处理失败,工单表里这条记录的状态就是“待同步”,此时MES的定时补偿任务每5分钟扫一次“待同步”的表,重新调用ERP确认接口,直到成功为止。
这种状态机模式的优点是不需要在接口层做幂等(虽然代码里也加了),只要保证最终状态一致就可以了。实际运行中,两个系统之间的数据同步偶尔断掉一两个小时是很正常的,靠这个补偿机制,断掉的数据能在一段时间内自动补齐,不会出现MES里工单做了一堆,ERP那边毫不知情的情况。
3.3 库存联动:投料与领料怎么避免账实不符
这套系统里库存联动的设计也值得一提。MES不直接管库存总额,但管“工单对应的物料消耗”。
操作工在工位终端领料时,先扫工单条码,系统从BOM表算出需求数量,再扫实际领用的物料批次条码。此时系统不是扣库存总额,而是在工单的临时物料表里记录一笔“已领用”,库存的还是ERP来管。
等到工序完工,MES把消耗数量回传给ERP,ERP才正式扣减库存。这个异步扣减的方式,避免了两个系统在同一时刻读写同一条库存记录导致死锁或超卖。
不过在实操中有个坑:如果一个工单改了物料清单,但MES这边没有及时同步,领料时会出现系统显示需要的物料和实物不一致。解决办法是在同步BOM时带一个“生效版本号”,MES只在工单创建的那一刻使用当时的BOM版本,所有现场领料判断都绑定版本号,后面BOM再改也不影响已创建的工单。
4. AGV调度联动:MES怎么指挥小车干活
4.1 通信架构:任务怎么从MES到小车
AGV调度在这套系统里是通过独立模块接入的。MES做任务发起方,AGV调度系统(通常来自小车供应商)做执行方。通信方式用的是HTTP的REST API加回调(Callback),MES向调度系统发起搬运任务,小车执行完成后调度系统再回调MES通知结果。
为什么没用消息队列?几个原因:这套系统的AGV数量不多(大概是十几台),没有大规模并发压力;HTTP接口调试方便,问题定位直观;MES和AGV调度系统部署在一台交换机下,网络延迟极低。如果用消息队列,反而要额外维护一套消息中间件,对车间IT来说又多一个故障点。
AGV任务的数据结构大概是这样的:
- 任务ID(MES生成,GUID)
- 源位置(比如原料库位、上一道工序工位)
- 目标位置(目标工位)
- 优先级(1-5,数字越小越优先)
- 任务类型(送料、收成品、空托盘回收)
- 物料批次号
- 载具编号
- 超时时间(多久未完成自动报警)
- 回传URL(AGV系统完成后回调的地址)
4.2 任务状态机:从创建到完成的完整生命周期
和MES工单一样,AGV任务也有一套完整的状态机。这套状态机对于理解整个系统的可靠性设计非常重要:
Created(MES创建任务,写数据库)Sent(已推送给AGV调度系统,等待确认)Accepted(AGV调度系统确认接收)EnRoute(小车已出发,前往源位置)Loading(已到达源位置,等待装载,通常是人工或机械臂配合)TransPorting(装载完成,向目标位置行驶)Arrived(到达目标位置)Unloading(等待卸载)Finished(卸载完成,任务正常结束)Canceled(任务被取消)Timeout(超时未完成)Failed(调度系统报告执行失败,比如取货时没有找到目标物料)
实际调试中,最常出问题的是Loading和Unloading这两个装/卸环节。因为这两个环节通常需要人工或外部设备配合,一旦人工没及时操作或传感器没感应到,任务就会卡住。代码里专门给这两个状态设置了最长等待时间,比如从MES推任务那一刻起,如果装货阶段超过3分钟没有变化,系统就自动把任务标记为Timeout并通知车间调度员介入处理。这个超时机制非常实用,没有它,一台小车卡住能让整条产线物流全部停摆。
4.3 任务优先级与异常恢复:“插单”和“断网”都是常态
AGV调度里一个比较难做的地方是任务优先级。实际生产中,质量部门的紧急退料、设备故障后的急件搬运、夜班交接时的托盘回收,这些任务的紧急程度完全不同。如果系统设计成FIFO(先来先服务),小车就会傻乎乎地执行低优先级任务,高优先级的急件反而排队。
代码里做法是:MES在创建任务时根据“业务场景类型”生成优先级,退换料、设备宕机补料这类紧急任务给优先级1,正常送料给优先级3,托盘回收这种例行任务给优先级5。AGV调度系统被要求按照优先级排序后分配小车。这部分实现逻辑其实主要在AGV供应商那侧,但MES这边要主动提供明确的优先级字段,否则调度系统默认只会按接收顺序来,急事反被耽误。
异常恢复也是个大问题。车间网络偶尔会闪断,MES推任务给AGV调度系统时如果正好赶上网断了,任务就会丢失。代码里给每个AGV任务设计了确认机制:MES推送后如果30秒内没有收到确认响应,会自动重试推三次;三次都失败就把任务状态改成“待定时重发”,由定时任务每2分钟重新扫描一次未确认任务再推。这套“推送-确认-补偿”的可靠性设计,直接保证了AGV物流在弱网环境下依然能正常跑。
5. 常见问题、排坑实录与实战避坑指南
5.1 WPF客户端卡死:八成是UI线程被阻塞
调试过程中遇到最多的一个问题就是工位终端用着用着界面就卡死。最典型的情景是:操作工在报工页面上点了“查询当前工单”,如果这个查询动作是同步操作,SQL在车间服务器上跑了几十秒才返回,UI线程就会卡住,鼠标点击无响应,整个终端看起来像死机了。
排查思路:打开Visual Studio的性能探查器,看调用栈里主线程卡在什么地方。如果是SqlCommand.ExecuteNonQuery或者Stream.Read这类同步阻塞调用,就说明是UI线程阻塞。解决方式很简单,把所有这类调用改成await异步调用。另外还有一个WPF特有的大坑:DispatcherTimer如果在界面上频繁执行重活(比如每秒钟去查一次数据库刷新下拉框),同样会拖垮UI线程,尽量改成后台线程执行然后通过Binding推送数据。
5.2 ERP同步过来的工单数量不对:先查中间表
有次现场反馈,ERP推过来的工单数量偶尔比实际少一两条。排查了接口日志、数据库日志,最后发现问题出在ERP侧的推送脚本上——它只推送当前数据库里状态为“已下达”的订单,但是ERP里有些订单是“已修改重新下达”状态,脚本漏掉了这部分。这种问题从MES侧没法根治,只能在接口层加一个“全量对账”功能:每天晚上定时从ERP拉取当天所有生产订单明细,和MES本地工单表比对,发现缺失的自动补建。
这个“对账补偿”的思想在MES集成里几乎是万能解药。不要指望上游系统每次都按约定推准时推送,只要定期做全量比对,就能兜住所有推送遗漏的情况。
5.3 AGV任务卡住:超时机制没配好
AGV那边最头疼的问题是任务一直卡在“Loading”状态,不往下一步走。排查后发现是AGV调度系统的装载确认信号没有正确触发——可能是光电传感器被灰尘挡住,也可能是人工操作时没有按确认按钮。这个问题的根治办法还是开头说的超时机制:每个阶段都设置最长等待时间,超时后自动把任务状态置为“异常”,同时在MES管理端弹出一个醒目的异常任务列表,让调度员看到是哪个任务卡住了、卡在哪个环节。
光有超时还不够,代码里还给异常任务加了“手动干预”按钮。调度员可以在管理端看到一个卡住的任务详情,点击“强制完成”或者“取消任务”后,系统自动把相关工单状态恢复成可操作状态。这个功能上线后,现场反馈说解决问题的时间从以前的几小时缩短到几分钟。
5.4 WPF发布升级简直是噩梦:版本不一致
WPF客户端有个通病——发布升级。如果每个工位终端都需要IT人员手动拷贝安装包升级,几十台终端升级一次要折腾一整天。这套系统代码里其实已经考虑了这个问题,用了ClickOnce发布模式,终端启动时自动检查服务器上的新版本并更新。这个方案比较老,但是胜在稳定,不依赖额外服务。我建议没有专门IT团队维护的小工厂优先考虑这套方案;如果团队技术能力强,也可以自己写一个简单的“启动器程序”做版本检查和增量更新,效果更好。
5.5 报表查询慢:不要把所有数据都放一张表
报表查询慢是MES系统避不开的问题。每次查“近三个月每台设备的产量趋势”,SQL要在工单报工表里搜几十万条记录。源代码里面没有做分表分库这种复杂操作,而是采用一个很务实的优化:建了汇总统计表,定时任务每隔5分钟把五分钟之内发生的报工、不良、设备运行时长等数据汇总一次,写入统计表。报表查询直接查统计表,不扫明细表,查询时间从十几秒降到一两秒。
这个做法特别适合中小型工厂的MES系统,原理上和“物化视图”类似,但实现起来简单得多,不用担心数据库版本兼容问题。真实工单报工明细数据仍然保留,只是报表不直接读它而已。
6. 可复现路径与二次开发建议
6.1 这套代码能直接复用到什么程度
如果你准备在自己的项目里参考这套代码,我建议先弄清楚自己手头的需求属于哪一档:
- 如果只是需要“工位终端显示任务+报工”,那WPF上位机模块可以直接套用,只需要改数据库名称即可。
- 如果需要和已有ERP对接,那ERP集成模块的“中间表+补偿任务”方案是核心资产,建议优先复用。
- 如果现场有AGV,可以参考AGV任务状态机的设计来对齐物流任务和工单状态;如果没有AGV,把AGV模块整体裁掉即可,所有关联工单状态的地方通过配置关闭“等待物流”逻辑就好。
6.2 推荐的最小落地顺序
如果要从零搭建一个类似系统,千万别一上来就贪大求全。我的建议是分四步走:
第一步,先搭数据库和WPF框架,把用户权限、登录、菜单框架跑起来。这部分直接抄这套系统的RBAC模型,半天就能出一个雏形。
第二步,做物料、BOM、工单管理。先不管接口,用Excel导入初始化主数据,工单手动创建,把工序任务的状态流转做通。
第三步,做工位终端报工+看板。这是现场真正用起来的一步,操作工能在工位上报工,大家看到系统能减少手工记录工作量,推动阻力就小了很多。
第四步,再接ERP接口和AGV调度。等流程稳定了、数据干净了,再动这两个外部系统,避免一开始就双线作战。
6.3 剪裁和扩展:哪些地方可以做得更细
代码本身有一些可以扩展的切入点。一个最典型的点就是设备数据采集——这套系统里采集模块用的是简单轮询方式,如果设备数量增加、点位需求变多,建议升级成独立的采集服务,用OPC UA统一建模,避免WPF客户端越做越臃肿。
另一个值得扩展的是多维度的数据分析。当前系统的报表以“产量、不良、工时”为核心,如果想往精益生产方向做,可以把报工数据按时段拆细,做时区产耗分析、瓶颈工序识别、产能预测。只要报工表里保留了每个工序的开工时间和完工时间,这些都是可以通过SQL直接算出来的指标。
最后说一个我在调试这套系统的过程中最有感触的事:MES系统真正的难点不是写代码,而是现场的不确定性。你把工时算得再精确,也挡不住一把刀具突然断了、一个料批次突然被仓库扣留。所以做这套系统的核心思路一直不是“用系统固化流程”,而是“用系统感知异常、暴露问题”。代码里那些状态机、补偿任务、超时机制、手动干预入口,本质上都是给异常一个快速收敛的路径。开发这类系统的人,如果能始终抱着“所有异常都会发生,所有异常都要能处理”的心态来设计,这个系统上线之后就不会成为现场骂娘的负担。
另外再分享一个小技巧:系统里所有任务表我都建议加一个remark字段,允许调度员和操作工在这些异常任务上写备注。现场解决问题的时候,书面的上下文往往比系统里任何一个状态字段都更有价值。这套代码里保留了这个字段的设计,是真正被车间踩过坑之后才会有的细节。