简介:一套面向制造业的开源MES系统设计源码,采用Java为后端核心,并整合Vue、JavaScript与HTML构建前端交互,可覆盖订单管理、物料跟踪、生产调度、品质管理等生产全流程,适合制造企业技术人员、Java开发者以及MES系统学习者参考。压缩包共588个文件,核心包含264个Java源码、81个Vue组件、62个JS脚本及31个XML配置,辅以SVG图标、SQL脚本、批处理工具等,结构上按核心模块与系统模块划分,便于按需阅读和二次扩展。已有515人学习/下载。通过该项目,使用者可获取完整的前后端分离工程结构、模块化开发思路、数据库建表脚本以及一键构建部署脚本,从而快速理解MES与ERP、PLC等系统的对接逻辑,积累企业级Java项目实战经验。
1. 开源制造执行系统MES为什么值得用Java Web重做一遍:先看它解决谁的痛点
车间里最贵的不是设备,是设备闲着。工单靠Excel传来传去、报工靠下班补录、物料齐不齐没人说得准——这种现场,一打听商业MES,报价动不动几十万,项目还没启动就黄了。开源制造执行系统MES给了另一个选项:用Java和Web技术,自己把生产执行层搭起来。标题里的“设计源码”,本质是一套可拆分、可改、可二次开发的车间管理骨架,覆盖工单下达、工序报工、物料追溯、看板展示。它适合两类人:一是想低成本验证MES适不适合自己车间的IT负责人;二是需要一个能跑通Web项目证明业务理解的Java工程师。下文按落地顺序讲:选型、数据模型、最小可运行方案、常见坑。
2. 开源MES的Java技术选型和架构:从若依到Spring Boot,骨架怎么搭
2.1 为什么Java Web组合是MES的主流:车间终端的免安装优势
制造业车间环境很特别:终端可能是Windows工控机、触摸屏一体机,甚至还有不少老旧的浏览器环境。如果MES做成C/S客户端,每一台设备都要装运行时,版本更新要逐台去跑,维护成本直接吞掉项目预算。做成Web项目,只要浏览器能打开页面就能用,升级只动服务器,这是Java Web在MES里长期占据主流位置的根本原因。
加上Java生态里Spring Boot对Web服务、定时任务、Excel导入导出、消息推送的支持都相当成熟,招Java工程师比招小众语言好招得多,企业拿到一套开源源码后也敢让自有团队接手。这里还涉及一个生产场景:MES不是独立系统,上游要接ERP拿工单和物料需求,下游要接PLC、扫码枪、电子秤。Java在这些设备对接上有大量现成案例,如果换一个小众技术栈,光写串口对接就可能卡住两周。我和同行聊选型时,默认第一句话都是:能用Java Web就先用Java Web,别在底层上挑战车间。
2.2 若依框架做底座:权限、代码生成、数据字典带来的开发边界
很多开源的Java版MES并不是从零写起的,而是在若依框架(RuoYi)这类后台管理框架上加业务模块。若依本身不是MES,它提供用户、角色、菜单、部门、数据权限、定时任务、代码生成这些通用能力。把MES业务挂在上面,等于先把框架层的活外包了,自己只专注生产流程。这套组合在国内非常流行,“基于若依框架的mes”也是从业者搜索最多的关键词之一,因为它确实能少踩基础框架的坑。
常见开源MES的代码目录长这样:
mes-project ├── mes-admin # 启动模块,打包成jar ├── mes-framework # 框架核心:安全、拦截器、权限 ├── mes-system # 系统管理:用户、角色、菜单 ├── mes-business # MES业务模块,二次开发写在这 │ ├── controller # 接口层,参数校验和权限注解 │ ├── service # 业务逻辑,状态机、库存校验 │ ├── domain # 实体类 │ ├── mapper # MyBatis的Mapper接口 │ └── resources/mapper # 业务SQL映射文件 ├── mes-common # 公共工具,字符串、日期处理 └── mes-ui # 前端,Vue + Element组件这套分层的核心逻辑是:mes-business依赖mes-framework和mes-common,但不反向依赖;将来升级框架版本时业务模块不用动。实际二次开发时,最大的风险就是手痒去改mes-framework里的公共类,一旦改了,今后想合并上游新版本会冲突到怀疑人生。我一般只在mes-business和mes-ui里写业务,公共模块基本不开刀。
技术选型上,前端用Vue和Element,后端用Spring Boot加MyBatis,数据库选MySQL。MySQL在中小车间足够用,PostgreSQL也可以,但开源项目多数默认MySQL,换库要改方言,不是不行只是没必要。Redis用来存验证码、登录状态、看板缓存。对单个车间、几百人同时在线的场景,这套单体现在就够了。不要一上来就上微服务那套全家桶,MES的核心是数据准确和链路完整,不是架构够不够花哨。
2.3 模块拆解:基础数据、执行数据、追溯数据的分层
MES从业务上可以切成五块,源码设计时每一块的边界要清晰,否则后面每加一个功能都要改三张表。
| 模块 | 核心数据 | 做什么用 | 设计时注意 |
|---|---|---|---|
| 基础数据 | 物料、工序、工艺路线、BOM | 告诉系统“车间在做什么、怎么做” | 必须支持Excel导入,导入错误要给出行号 |
| 生产计划 | 工单、排产 | 告诉系统“先做哪个、做多少” | 工单状态机要独立,不能在Service里到处改status |
| 生产执行 | 报工、质检、不良品记录 | 采集现场实际完成情况 | 报工防重是底线,重复提交必须挡住 |
| 物料追溯 | 批次、出入库记录 | 出了问题能追回源头 | 批次号编码规则要提前定,上线后改不动 |
| 看板展示 | 生产看板、设备状态 | 把数据变成车间能看懂的页面 | 刷新用Redis或WebSocket,别让看板扫数据库 |
这五块的依赖方向是单向的:基础数据 -> 生产计划 -> 生产执行 -> 物料追溯。看板依赖下面四块的聚合数据,不要单独建大宽表。很多源码翻车就翻在追溯模块想建一张“万能表”,把所有字段塞进去,结果数据冗余、口径不一致。设计时宁可多join两张表,也别造一个谁都不敢碰的怪物表。
3. 核心模块落库:把车间报工、工单、物料追溯拆成可运行的源码
3.1 工单、报工、批次三张表:建表SQL与字段选择
MES源码的设计质量,一半在表结构上。工单表是执行的核心单据,报工表是现场采集的记录凭证,物料批次表是追溯的源头。我通常先建这三张表:
-- 工单表:车间执行的核心单据 CREATE TABLE work_order ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', order_no VARCHAR(32) NOT NULL COMMENT '工单号,业务查询用', product_id BIGINT NOT NULL COMMENT '产品物料ID', plan_qty INT NOT NULL COMMENT '计划数量', completed_qty INT NOT NULL DEFAULT 0 COMMENT '累计合格数', scrap_qty INT NOT NULL DEFAULT 0 COMMENT '累计报废数', status TINYINT NOT NULL DEFAULT 0 COMMENT '0待下达 10已下达 20生产中 30已完工 40已关闭', plan_start_time DATETIME NULL COMMENT '计划开工', plan_end_time DATETIME NULL COMMENT '计划完工', actual_start_time DATETIME NULL COMMENT '实际开工', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', create_by VARCHAR(64) NULL COMMENT '创建人', create_time DATETIME NOT NULL COMMENT '创建时间', PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='生产工单';-- 报工表:现场完工记录的原始凭证 CREATE TABLE work_report ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', work_order_id BIGINT NOT NULL COMMENT '工单主键', process_id BIGINT NOT NULL COMMENT '工序ID', batch_no VARCHAR(64) NOT NULL COMMENT '物料批次号', report_qty INT NOT NULL COMMENT '报工合格数', scrap_qty INT NOT NULL DEFAULT 0 COMMENT '报工报废数', operator_id BIGINT NOT NULL COMMENT '操作员用户ID', device_id BIGINT NULL COMMENT '设备ID', report_time DATETIME NOT NULL COMMENT '报工时间', unique_key VARCHAR(128) NOT NULL COMMENT '幂等键', remark VARCHAR(255) NULL COMMENT '备注', create_time DATETIME NOT NULL COMMENT '创建时间', PRIMARY KEY (id), UNIQUE KEY uk_report_unique (unique_key), KEY idx_order_id (work_order_id), KEY idx_batch_no (batch_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工序报工记录';-- 物料批次表:追溯的源头 CREATE TABLE material_batch ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', batch_no VARCHAR(64) NOT NULL COMMENT '批次号,建议编码规则:料号+生产日期+流水', material_id BIGINT NOT NULL COMMENT '物料ID', qty INT NOT NULL COMMENT '当前库存数量', qc_status TINYINT NOT NULL DEFAULT 0 COMMENT '0待检 1合格 2不合格', in_time DATETIME NOT NULL COMMENT '入库时间', supplier_id BIGINT NULL COMMENT '供应商ID', PRIMARY KEY (id), UNIQUE KEY uk_batch_no (batch_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物料批次库存';字段选择有几个值得展开讲。工单号必须独立业务唯一键,因为生产管理部打印出来的表上只有order_no,不会给你数据库主键。completed_qty和scrap_qty不走“先查再update”,而是用version做乐观锁。报工表里的unique_key是防重的最后一道保险,业务防重代码就算漏了,数据库唯一索引也能挡住。批次号的编码规则要提前和仓库、采购对齐,否则追溯时根本无法通过条码猜出是哪天哪条线生产的。
3.2 报工接口的状态机流转与防重逻辑
工单状态不能各处乱改,必须收敛在Service层。报工接口是MES里调用最频繁、最容易出错的地方,我一般这么写:
@Transactional(rollbackFor = Exception.class) public R workReport(WorkReportDTO dto) { // 1. 先锁工单,拿到当前状态,避免两个流程同时改状态 WorkOrder order = workOrderMapper.selectByIdForUpdate(dto.getWorkOrderId()); if (order == null) { return R.error("工单不存在"); } if (order.getStatus() == 0 || order.getStatus() == 40) { return R.error("工单未下达或已关闭,不能报工"); } if (order.getStatus() == 30) { return R.error("工单已完工,请先做超报工审批"); } // 2. 业务级防重:同一工单同一工序同一报工时间窗只允许一次 String uniqueKey = order.getOrderNo() + ":" + dto.getProcessId() + ":" + dto.getReportTime(); if (reportMapper.countByUniqueKey(uniqueKey) > 0) { return R.error("检测到重复报工,请刷新后确认"); } // 3. 扣减批次库存,并写库存变动流水 int remain = batchMapper.selectQtyByBatchNoForUpdate(dto.getBatchNo()) - dto.getReportQty(); if (remain < 0) { throw new BizException("批次[" + dto.getBatchNo() + "]库存不足,扣减后不能为负数"); } batchMapper.updateQty(dto.getBatchNo(), -dto.getReportQty()); batchLogMapper.insert(new BatchLog(dto.getBatchNo(), -dto.getReportQty(), dto.getOrderNo())); // 4. 插入报工单,并累加工单完工数(乐观锁) workReportMapper.insert(buildReport(order, dto)); int updated = workOrderMapper.increaseCompletedQty(order.getId(), dto.getReportQty(), order.getVersion()); if (updated == 0) { throw new BizException("工单已被其他人更新,请重试"); } return R.success("报工成功"); }逻辑说明:第一步用selectByIdForUpdate锁工单,让同一工单的报工串行化,这是单机部署下最直接的办法。第二步的uniqueKey是业务防重;如果车间存在同一工序多人同时报工的情况,可以改成“班次ID+工序ID+报工人ID”。第三步扣库存一定要加锁,否则并发时库存会负。第四步走乐观锁,如果工单在别处被改过,increaseCompletedQty返回0就整体回滚,避免完成数量覆盖丢失。
参数说明:WorkReportDTO来自前端表单;reportQty必须以设备扫码或人工确认的合格数为准,不能拿“计划数减已完成数”反推,那样保存时非常容易出错。BizException是若依风格的通用业务异常,抛出后事务回滚,前端会收到统一的错误提示。
3.3 追溯查询:一条SQL把单号、批次、操作员串起来
追溯的核心是“由结果反查过程”。车间来了客诉,客户给的是出库批次号,现场能扫的是产品条码,技术部拿的是工单号,所以查询入口至少要支持orderNo和batchNo两个条件:
SELECT wo.order_no, wo.product_id, mb.batch_no, mb.qc_status, wr.report_qty, wr.report_time, u.nick_name AS operator_name FROM work_report wr JOIN work_order wo ON wr.work_order_id = wo.id JOIN material_batch mb ON wr.batch_no = mb.batch_no LEFT JOIN sys_user u ON wr.operator_id = u.user_id WHERE wo.order_no = #{orderNo} OR mb.batch_no = #{batchNo} ORDER BY wr.report_time DESC LIMIT 500;LEFT JOIN sys_user而不是INNER JOIN,是因为用户可能离职被禁用,LEFT JOIN能保证报工明细不丢。LIMIT 500是保险阀,防止车间一次导出几十万条拖垮数据库,全量导出应该走异步任务而不是在线接口。不要在这里去join排产表或BOM表,追溯链路越短越容易排查;真需要看“这批料是谁在什么时间投到哪道工序的”,再单独加一道工序投料表关联,别在一张SQL里写六张表。
4. 在本地把开源MES跑通:环境准备、配置修改和第一个页面请求
4.1 环境清单与版本匹配:别让版本把源码拦在门外
拿到一套开源MES源码,先别急着打包,先把环境对齐。很多开源项目长期停留在Spring Boot 2.x,对应JDK 8;新的3.x要求JDK 17。版本错配最常见的报错是编译时提示Unsupported class file major version。我一般按这张表准备环境:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 8或17,以源码pom.xml为准 | 编译报class version错误就是版本不匹配 |
| Maven | 3.6+ | 配置国内仓库镜像加快依赖下载 |
| MySQL | 5.7或8.0 | 源码SQL按哪个版本写就用哪个,别混用 |
| Redis | 5.x+ | 装好后先用redis-cli ping验证 |
| Node.js | 16或18 | 前端构建,npm版本别太新 |
| 浏览器 | Chrome/Edge | 开源MES前端基本放弃老IE兼容 |
这里最大的坑是“新版本强迫症”:JDK直接用17但项目是Boot 2.x,编译时一堆javax和jakarta命名空间报错;或者npm装到20,前端构建报OpenSSL错误。解决办法只有一个:先打开pom.xml和package.json看版本,再决定装什么,不要凭感觉。
4.2 一键启动后端:依赖、建库、改配置、打包
# 1. 拉源码并初始化数据库 git clone http://your-git-server/mes-source.git cd mes-source mysql -uroot -p < sql/init.sql # 2. 改数据库和Redis连接 vim mes-admin/src/main/resources/application-druid.yml # 把url、username、password改成自己本机的 # 3. 打包并启动后端 mvn clean package -Dmaven.test.skip=true java -jar mes-admin/target/mes-admin.jar逻辑说明:init.sql通常包含建库语句和演示数据;如果根目录没有sql目录,去mes-admin的resources目录找。改配置文件只改application-druid.yml,不要动application.yml里的公共配置,因为后者可能被多个模块引用。-Dmaven.test.skip=true跳过测试编译,能省几分钟;如果源码里测试类有环境依赖,不跳过会在打包阶段失败。
配置片段:
spring: datasource: druid: url: jdbc:mysql://localhost:3306/mes_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 redis: host: localhost port: 6379 password:参数说明:url里的characterEncoding=utf8必须保留,少了它中文就变问号;serverTimezone=Asia/Shanghai避免MySQL驱动报时区错误。Redis没设密码就留空,本机调试可以,生产环境至少要设一个高强度密码。启动成功日志会出现Tomcat started on port(s): 8080,随后浏览器访问http://localhost:8080。如果端口被占用,用java -jar mes-admin.jar --server.port=80临时改端口,不要改配置文件里的默认端口,因为前端对接地址常常写在菜单配置里。
4.3 前端跑起来:开发模式和打包自托管两种选择
cd mes-ui # 安装依赖,node_modules很大,等几分钟 npm install # 开发模式启动,热更新 npm run dev开发模式适合改页面,但它自带一套请求转发配置,把/dev-api转到后端8080。生产环境更省心的做法是把前端构建产物塞进Spring Boot的static目录,同一个端口发布,既没有跨域也不需要额外配置Web服务器:
npm run build:prod cp -r dist/* ../mes-admin/src/main/resources/static/ mvn clean package -Dmaven.test.skip=true java -jar mes-admin/target/mes-admin.jar这种自托管方式适合六百人以下的车间规模,一个Tomcat完全扛得住。以后真要多车间分布式部署,再把前端单独放出去做入口,最小闭环阶段不搞复杂,先把页面跑通再说。生产环境的Web服务器只监听80和443端口,开启HTTPS,别给车间开不必要的对外端口,这是最容易忽略的web服务器安全问题。
4.4 第一个接口请求:验证前后端链路是不是通的
登录后在工单管理打开一张工单,按F12看Network。如果看不到工单请求,说明前端路由或登录token有问题;如果请求返回了但页面没渲染,多半是实体类字段和前端camelCase没配对。用curl直接验证后端更纯粹:
curl -H "Authorization: Bearer eyJhbGciOi..." \ "http://localhost:8080/system/workOrder/detail?orderNo=MO20250701-001"返回JSON里能看到planQty和status,就说明后端、数据库、Redis这一条链路已经通了。开发阶段把控制台日志级别调到debug,MyBatis会把每一条SQL打印出来,定位问题比看业务日志快得多。登录进去后第一件事是去用户管理改掉默认管理员密码,开源项目的默认账号太容易被人猜到了。
5. 开源MES上线排雷:五个绕不开的坑和它们的排查方法
5.1 坑一:前端页面刷新就404,直连部署也逃不掉
现象:开发模式下一切正常,打包部署后,点菜单进详情页没问题,一按F5刷新就白屏404。
原因:前端路由用的是history模式,浏览器刷新时把请求发到了后端路径,而后端没有返回index.html兜底。
解决:前端用hash模式可以当场规避,但URL会带个#号,车间标签打印、扫码访问时容易踩坑。更好的做法是后端加一个兜底转发,把前端页面路由都指向index.html:
@Controller public class WebForwardController { // 只兜前端页面路径,接口路径不要进这里 @RequestMapping(value = {"/", "/production/**", "/report/**", "/quality/**"}) public String forwardIndex() { return "forward:/index.html"; } }这里的production、report、quality要按源码里的前端路由前缀调整;凡是/api或/system开头的接口请求都不走这个Controller,否则会把正常接口转成HTML。加完重新打包,刷新404就不会再出现。
5.2 坑二:并发报工把批次库存打成负数
现象:月底对账发现某批次库存是-5,但车间说“平时没人乱动”。
原因:报工逻辑是“先查库存、减掉、再写库”,两条产线同时报工时,后面的update覆盖了前面的结果,负数和少账都拦不住。
解决:库存扣减必须加锁。单体应用用select for update即可,集群再用分布式锁兜底:
@Transactional(rollbackFor = Exception.class) public R consumeStock(StockConsumeDTO dto) { MaterialBatch batch = batchMapper.selectByBatchNoForUpdate(dto.getBatchNo()); if (batch.getQty() < dto.getConsumeQty()) { throw new BizException("批次库存不足"); } batchMapper.updateQty(dto.getBatchNo(), -dto.getConsumeQty()); return R.success(); }@Transactional开事务,selectByBatchNoForUpdate拿到行锁,事务提交后锁才释放。注意这种写法则只对单数据库实例有效,多实例必须引入分布式锁,否则forUpdate落到不同库节点就各自为政。另一个兜底是库存变更日志表里对同批次建唯一约束,保证相同单据不能重复扣减。
5.3 坑三:BOM导入中文全变问号,追查半天是字符集
现象:Excel里物料名称正常,导入后页面显示????,导出报表再变一次乱码。
原因:MySQL连接串没有指定UTF-8字符集,或者解析Excel的入口把物料名里的逗号给“吃”了,导入静默错行。
解决:先看数据库字符集,再用连接串和表结构两面夹击:
ALTER DATABASE mes_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; ALTER TABLE material CONVERT TO CHARACTER SET utf8mb4;url: jdbc:mysql://localhost:3306/mes_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai给开发者的建议是:Excel导入用Apache POI时,用WorkbookFactory.create(inputStream)按文件真实类型解析,不要自己拼CSV再split逗号,否则物料名里带逗号时导入会静默错行。这个坑是最典型的“数据源正常但入口解析有问题”,排查时先看日志里有没有字符转换异常,再看数据库字段的collation。
5.4 坑四:菜单隐藏了但接口还能直接访问
现象:给质检员配了角色,菜单里看不到“工单修改”,但有人把URL复制发给其他人,对方直接打开了修改页。
原因:菜单隐藏只控制前端显示,后端接口没做权限校验,等于把钥匙放在门边。
解决:在Controller方法上加权限注解:
@PreAuthorize("@ss.hasPermi('mes:workOrder:edit')") @PutMapping("/workOrder") public AjaxResult edit(@RequestBody WorkOrderDTO dto) { return workOrderService.save(dto); }mes:workOrder:edit是权限标识,要同步到若依的菜单管理里,给角色勾上才放行。二次开发时每次新增接口都补这个注解,比事后审计省事。同一集团不同厂区共用一套系统时,还要在Service层加@DataScope按部门过滤,避免越权看到别厂数据。
5.5 坑五:代码生成器覆盖了手工改写的Mapper XML
现象:用若依代码生成器生成新模块后,旧功能报SQL语法错误,打开XML一看,文件被恢复成初始模板。
原因:生成器默认按模板覆盖同名文件,手工加的查询条件全没了。
解决:把自定义SQL单独建目录:
mes-business/src/main/resources/mapper/custom/ WorkOrderCustomMapper.xmlMapper的扫描配置要同时包含mapper/**/*.xml和mapper/custom/*.xml,然后在Service注入WorkOrderCustomMapper。这样代码生成器再生成多少遍,也不会动到custom目录。这是二次开发时最值得养成的习惯,也是很多MES源码后期维护翻车的重灾区。
6. 从能跑到能数:上线前验证MES源码的五个检查方法和一个随手技巧
6.1 五个检查方法
上线前不是只看功能清单,而是验证“数据到底能不能对上”。我按这个顺序过:
- 状态机验证:把一张工单模拟走完“下达-开工-报工-完工-关闭”,每一步检查页面和数据库的status是否一致。最常发现的问题是完工后还能报工,说明Service里少了状态拦截。
- 防重验证:连续双击报工按钮,或者直接调两次同一unique_key接口,第二次必须失败。如果库存被扣两次,防重就是摆设。
- 追溯验证:拿一张真实批次号跑一条SQL,确认能查到“哪张工单、谁、什么时候、报了多少合格数”。
- 并发验证:模拟两个线程同时扣同一批次库存,看最终数是否等于预估值;不通过就加锁。
- 接口安全扫描:用低权限账号直接访问接口URL,确认每个修改操作都有
@PreAuthorize。
6.2 随手技巧:用SQL批量造数,把追溯跑冒烟
-- 把日期往前推90天,每天造5张工单和对应的报工记录 INSERT INTO work_report (work_order_id, process_id, batch_no, report_qty, report_time, unique_key) SELECT wo.id, p.process_id, mb.batch_no, FLOOR(10 + RAND() * 30), DATE_SUB(NOW(), INTERVAL n DAY), CONCAT(wo.order_no, '-', p.process_id, '-', DATE_SUB(NOW(), INTERVAL n DAY)) FROM work_order wo JOIN production_process p ON p.is_first = 1 JOIN material_batch mb ON mb.material_id = wo.product_id JOIN (SELECT 1 AS n UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) nums LIMIT 100;跑完SELECT COUNT(*) FROM work_report看总量,然后在追溯页面翻页看响应时间;如果超过2秒,就要检查是否缺了work_order_id索引,或者SQL里join了不必要的表。造数不是乱造,要让order_no、batch_no和真实业务一样有关联,才能真正验证口径。
我头一回给车间上线MES时,就是没做这一步,上去第二天发现追溯数据差两条,车间主任当着全生产线面问我“你这系统到底能不能信”,场面相当难忘。从那以后,我改任何MES源码都先造数、再走全链路验证,确认没问题才让车间用。希望帮到你。
本文还有配套的精品资源,点击获取