简介:制造执行系统(MES)是连接企业计划层与车间设备层的关键桥梁。面向项目开发人员、系统实施顾问及制造企业信息化负责人,这份完整版源码覆盖了可运行的车间管理核心代码,围绕生产派工、工序报工、物料批次追溯、设备状态监控、质量检验与产量统计等典型车间业务,完整呈现从工单下发到完工入库的数据流转与状态闭环。压缩包为zip格式,整体约50.79MB,主要内容为后端服务代码、前端页面、数据库初始化脚本及部署配置文件;文件类型明细暂未提供,解压后可自行查看目录结构。目前已有198人浏览学习,适合用于毕业设计、企业MES项目选型验证、教学实验环境搭建或基于现有代码的定制改造。通过阅读源码,可快速掌握车间数字化管理系统的模块划分、权限控制思路与关键业务表设计,显著降低从零开发的时间与试错成本。
1. 拿到一套 MES 车间管理源码完整版,别急着跑,先把它拆明白
做工厂信息化的朋友,大多有过这种尴尬:老板说“上套 MES 系统”,预算一问从二十万到两百万都有,最后很可能先丢给你一份“完整版源码”,让你评估能不能自己落地。这里说的 MES 车间管理源码,指的是一套包含生产工单、报工、质量、设备、追溯等核心业务,并且有前端页面、后端服务、数据库脚本和文档的完整工程,不是某个 GitHub 上只有几个类的 Demo。你拿到的不是产品,而是原料。
这套东西能解决什么问题?对甲方来说,它意味着你可以基于一套可运行的系统改出符合自己车间流程的版本,不用从零写排产逻辑;对乙方和做二次开发的人来说,它是省掉原型期的宝贵底稿。但“完整版”三个字里藏着两种含义:业务覆盖上的完整,和技术栈上的完整。很多源码只做到了表结构完整,一跑起来全是坑。这篇文章我会从拆解、部署、源码走读到避坑,按一线工程师的视角把这套东西讲明白。适合谁?准备选型或刚拿到源码的从业者,以及想从零搭 MES 的学生和初中级开发。耐心看完,你会知道该先看哪个文件、先改哪个参数,以及哪些模块其实是花架子。
2. 拆解 MES 车间管理源码:先分清“完整”的两层含义
2.1 从业务模块看:生产、质量、设备、追溯一个都不能少
很多标着“完整版”的 MES 源码,打开数据库脚本一看,其实只有工单和报工两张表,这就叫不完整。真正的车间管理系统,哪怕是最小可用版本,也至少要覆盖五个核心域:生产工单、工序流转、质量检验、设备状态、生产追溯。
工单模块要处理订单拆分、排产下发、工序派工。常见做法是用一个work_order表存储主单,再用work_order_operation表存储工序明细。报工模块则要对接工序流转,记录完工数量、合格数、不良数、工时。质量模块至少要有来料检、过程检和完工检三种检验单,这些单据会反过来影响工单状态。设备模块负责记录设备台账、点检记录、运行状态。追溯模块则是把工单、批次、物料、操作工、设备、检验记录串联在一起,形成正向和反向追踪链条。
拿到源码后,我一般会先打开数据库脚本,逐个搜这些表。如果缺了其中某个,说明这套“完整版”其实只是部分模块的完整。当然,MES 的行业差异很大,注塑厂和 SMT 贴片厂关心的点完全不同,但生产执行、质量、设备、追溯这四件事是所有车间系统的地基。你在评估时,可以拿一张业务清单去对照,别被 UI 截图迷惑。
2.2 从技术栈看:前后端分离 + 数据库 + 采集接口
MES 车间管理源码的常见技术组合是 Java 系(Spring Boot + MyBatis)+ 前端 Vue + MySQL,这也符合目前制造业信息化的人才储备。看到这个组合,你至少要知道怎么分层。后端目录一般有controller、service、mapper、entity、config这些包。mapper 里放 SQL 和查询条件,service 里写业务逻辑,controller 只做参数接收和结果返回。前端则按页面模块分,比如production、quality、equipment、trace。
除了业务代码,完整版源码里最容易被忽略的是采集接口。车间的数据不全是人录进去的,还有设备 PLC、扫码枪、电子秤这些数据源。所以源码里通常会预留一个collect或iot模块,里面是接收设备数据的 HTTP 接口,或者对接数据库视图的读取逻辑。这块的完整程度往往决定了这个系统能不能真的在车间跑起来,而不是变成“Excel 录入系统”。
我评估源码时有个习惯,先看有没有一个sys_config表,或者叫system_config。这个表里存的往往是所有业务参数,比如报工方式、是否启用批次管理、工序转移规则。如果这个表设计得太简单,后续改业务逻辑会非常痛苦。因为很多行为是写死在代码里的,改一个分支就要动几处地方。
2.3 用一条命令快速体检代码仓库:找关键模块入口
当你拿到源码压缩包,第一步不是点开 README,而是先做一次目录体检。我用 Linux 或 Git Bash 时,通常先跑下面这个命令:
# 只显示目录结构,限制两层深度,避免被 node_modules 淹没 find . -maxdepth 2 -type d \ -not -path "*/node_modules/*" \ -not -path "*/.git/*" \ | sort这段命令的意思是:找出当前目录下两层以内的所有文件夹,排除掉前后端依赖目录和 Git 目录,然后排序输出。为什么要这么做?因为很多“完整版”源码会夹带几十个node_modules包,直接看项目根目录容易被误导。跑完你会发现,如果只有backend、frontend、database三个目录,并且database里能看到多个.sql文件,那这套源码结构是健康的;如果只有一个src目录塞满全部代码,那大概率是个单体老项目,二次开发时前端和后端的耦合会让人崩溃。
接着我会去打开后端的pom.xml或者build.gradle,看依赖列表里有没有spring-boot-starter-quartz(定时任务)、druid(数据库连接池)、shiro或spring-security(权限)。这些依赖的存在与否,能够直接告诉你系统是否支持定时排产、生产看板轮询、多岗位权限控制。如果没有这些,别灰心,自己补也不是太难,但要在计划里留出工时。
3. 把完整版源码跑起来:从建库到页面出现的完整命令行
3.1 建库与初始化脚本:先看 SQL,别急着运行
部署这套源码时,最容易翻车的不是代码,而是数据库脚本。很多“完整版”把建表语句和数据字典混在同一个 .sql 文件里,如果你直接整个导入,经常会出现“表已存在”或外键错误。我的习惯是先打开 SQL 文件,把建库语句、建表语句、初始数据分三段来看。
用 MySQL 举例,常见做法是先在命令行里建库,再选择库,然后导入脚本:
# 创建数据库,指定 utf8mb4 字符集,避免中文乱码 CREATE DATABASE IF NOT EXISTS mes DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE mes; SOURCE /path/to/mes_init.sql;这里的重点有两个:第一,字符集要用utf8mb4,因为车间里的品名、不良描述可能包含生僻字,以及特殊符号;如果沿用老的utf8,一旦写入带音标或扩展字符就会报错,这是 MySQL 8 以下版本的常见问题。第二,SOURCE导入时不要去管输出里那些重复的ERROR 1050告警,只要表存在且数据量正确就行。导入完成后,先查一下核心表行数:
SELECT COUNT(*) FROM sys_user; SELECT COUNT(*) FROM work_order; SELECT COUNT(*) FROM base_process_flow;这三个查询是为了验证初始化数据是否完整。如果sys_user只有一条 admin,说明数据字典是精简过的;如果work_order有几百条演示数据,说明脚本里带了很多模拟业务数据,这些数据在测试时很有用,但上线前必须清空。
3.2 后端配置文件与启动参数:application.yml 里最值得改的 6 项
后端起不来的原因,十有八九在配置。Spring Boot 项目的配置集中在application.yml或application.properties里。拿到源码后,我建议你逐个字段检查,而不是直接双击 jar 包开跑。下面是 6 个几乎每次都要改的配置项,我以常见写法列出来:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/mes?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true logging: level: com.mes.mapper: debug说明一下这几个参数的坑:
serverTimezone=Asia/Shanghai不可省,否则 MySQL 8 驱动会报时区错误。map-underscore-to-camel-case: true表示数据库字段的下划线自动映射为 Java 驼峰属性。如果这个配置缺失,你就得在 XML 里写大量resultMap,非常痛苦。mapper-locations要确认路径和你的资源目录一致。很多完整版会把 mapper XML 放在src/main/java下,这时要额外加一个 build 配置把 XML 也打包进去。- Redis 如果没安装,系统不会启动。你可以先启动一个最简单 Redis,或者暂时把依赖注释掉。但不建议去掉,因为工单投料、报工时的锁都用到了 Redis。
改完这些,启动后端的方式是:
mvn clean package -DskipTests java -jar target/mes-backend.jar --spring.profiles.active=dev如果启动日志里出现HikariPool ... TimeoutException,说明数据库连接参数不对,重点检查防火墙和用户权限。启动成功后,会看到Tomcat started on port(s): 8080,这一步才算通了。
3.3 前端安装与代理:npm install 之后还要做的事情
前端部分一般是 Vue 工程,启动流程看起来简单,但坑最多的是代理配置。找到vue.config.js或vite.config.js,你会发现一个开发服务器代理,把请求转发给后端地址。举个例子:
module.exports = { devServer: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' } } } } };这里的/api前缀和后端 controller 的@RequestMapping路径要匹配。很多“完整版”源码的前后端接口路径是两套体系,前端统一加/api,后端不加;另一套是前后端直接同路径。如果你不改代理,页面永远拿不到数据,控制台全是 404。我的经验是先看src/utils/request.js里的 baseURL,再看代理配置,两者必须对应。
跑前端的最小命令是:
npm config set registry https://registry.npmmirror.com npm install npm run serve说明:npm install如果报node-sass编译失败,多半是 Node 版本不对。常见做法是把 node-sass 换成 sass(dart-sass),但注意源码里如果用了很多deep样式穿透,sass 语法要改成:deep()。这个步骤是纯血泪经验,别问我是怎么知道的。
4. 核心流程源码走读:工单下发到报工,状态机是怎么转的
4.1 工单实体与状态枚举:status 字段的流转路径
车间管理系统的核心不是页面,而是工单状态。大多数“完整版”源码里,工单状态是用一个整型字段表示的,比如 0 表示待下达,1 表示已下达,2 表示生产中,3 表示完工,4 表示暂停。看源码时不要急着找界面,先找到实体类WorkOrder.java,把 status 的每一步流转画出来。
以 Spring Boot 项目为例,你会在entity包里看到类似这样的字段:
// WorkOrder.java /** * 工单状态 * 0-待下达 1-已下达 2-生产中 3-已完工 4-已暂停 */ private Integer status; private Integer currentOperationId; private Date planStartTime; private Date planEndTime; private BigDecimal plannedQty; private BigDecimal completedQty;这个注释很关键,它直接定义了业务边界。下一步去搜所有对setStatus(的调用,你会发现每个方法都附带一个操作:下达工单时判断当前状态是否为 0,报工时判断是否为 1 或 2,完工时判断是否所有工序都已报工。有时候翻车就是因为有人直接执行UPDATE work_order SET status = 3,绕过了业务逻辑,导致数据不一致。
我一般会建议新手在这个实体上做一件事:加一个状态机校验方法,把允许的流转方向做进校验里。例如:
public boolean canTransferTo(int targetStatus) { // 待下达只允许流转到:已下达、已暂停 if (this.status == 0) { return targetStatus == 1 || targetStatus == 4; } // 生产中只允许流转到:已完工、已暂停 if (this.status == 2) { return targetStatus == 3 || targetStatus == 4; } // 其他情况直接拒绝 return false; }这种改造虽然增加了代码量,但能防止别人在二次开发时把事情搞坏。记住,MES 的工单状态是车间的“秩序之源”,状态乱掉,后续所有报表数据都别想准。
4.2 报工接口实现:事务、库存更新与异常处理
报工是 MES 车间管理里最频繁的操作,也是源码中最能提现水平的部分。一个完整的报工接口,不只做一件事:把工单的完工数量加上去,还要更新工序完成数量、计算不良数、生成检验记录、联动库存。这些操作必须在一个事务里完成,任何一个失败都要整体回滚。
下面是一个简化的报工 service 代码:
@Transactional(rollbackFor = Exception.class) public void report(ReportRequest req) { // 1. 查询工单并加锁,防止并发重复报工 WorkOrder wo = workOrderMapper.selectByIdForUpdate(req.getWorkOrderId()); // 2. 校验当前工单状态是否允许报工 if (wo.getStatus() != 2) { throw new BusinessException("工单未处在生产状态,不能报工"); } // 3. 更新工单完成数量 wo.setCompletedQty(wo.getCompletedQty().add(req.getQty())); workOrderMapper.updateById(wo); // 4. 更新当前工序的实际完工量 WorkOrderOperation op = operationMapper .selectByWorkOrderIdAndOperationId( req.getWorkOrderId(), wo.getCurrentOperationId()); op.setCompletedQty(op.getCompletedQty().add(req.getQty())); operationMapper.updateById(op); // 5. 生成一条工序流转记录,用于追溯 processTraceMapper.insert(buildTraceRecord(req, wo)); // 6. 如果达到完工数量,自动推进到下一工序 if (op.getCompletedQty().compareTo(op.getPlanQty()) >= 0) { nextOperation(wo); } }这段代码里有一个非常关键的点:selectByIdForUpdate。这是数据库悲观锁,保证两个工人同时扫码报工时,后一个请求会等待前一个事务结束,避免把完工数从 100 加到 150 又覆盖成 120。很多“完整版”源码并没有这一步,并发场景下数据会乱掉。你自己做二次开发时,一定要检查所有涉及数量更新的查询,务必带上FOR UPDATE。
另一个容易忽略的参数是rollbackFor = Exception.class。Spring 默认只对 RuntimeException 回滚,如果抛的是 checked exception,事务不会回滚。有些源码里把这个参数漏了,结果写了一半失败,数据半保持状态,就是大家常说的“邪门问题”。看到这种代码,改掉,别犹豫。
4.3 与 ERP 对接的 WebService 接口:参数映射是主要工作量
车间管理系统往往不会独立存在,它上面有 ERP 下发生产订单,下面有 PLC 采集设备数据。很多完整版源码都包含一个erp包,专门放对接逻辑。技术上是 SOAP WebService 还是 HTTP REST 不重要,重要的是参数映射表。MES 和 ERP 用的是两套编码体系,比如 ERP 叫“物料编码”,MES 叫“物料号”;ERP 的订单行号是字符串,MES 是整数。
源码里常见的坑是直接写一个固定映射对象:
public class ErpOrder { private String orderNo; // ERP订单号 private String materialCode; // ERP物料编码 private BigDecimal qty; // 数量 private String planDate; // 计划完工日期 }而到了 MES 这边,工单表的字段叫 workOrderNo、itemCode、planQty、dueDate,中间就靠一两个转换方法支撑。真正的生产环境里,ERP 的字段随时会变,所以你要检查源码里有没有配置化的字段映射表,比如erp_field_mapping,而不是把映射逻辑写死在 Java 代码里。如果没有,我建议你花半天时间把常见字段抽到一个配置表,否则每次 ERP 升级都是一次重编译。
关于 WebService 客户端,我习惯在对接之前先用soapui或curl把 WSDL 里的方法名和参数结构打印出来,列出和 MES 的对照表。没有这个对照表,开发时靠猜,调试时看走眼,最后联调不是超时就是对不上。很多完整版源码其实已经带了一个可调通的 WebService 示例接口,你先跑通再改参数,不要一上来就改逻辑。
5. MES 车间管理源码落地避坑指南:现象、原因、解决办法
5.1 页面能打开但登录报“验证码失效”或“session 过期”
现象:前端 npm run serve 跑起来了,输入 admin 密码,登录接口调用能通,但始终提示验证码错误或 session 已过期。
原因:这类问题 90% 出现在前后端分离配置上。MES 系统的登录校验往往同时用 Cookie 里的 Session ID 和验证码存储,而前端代理没有把 Cookie 透传回去。具体来说,当你通过http://localhost:3000访问时,浏览器里的 Cookie 的 domain 是 localhost,端口不同也会被视为不同站点,后端拿不到对应的 session。
解决:调整前端代理的proxy配置,加上changeOrigin: true,并确认后端启动时没有强制 HTTPS。如果还是不行,就去后端找登录接口,把验证码的存取从 Session 改为 Redis,指定 key,再设置 key 的有效期。很多新版本源码已经改成 Redis 验证码了,但老版本还依赖 session,改起来也不难,把ImageCode的生成逻辑和校验逻辑对齐就行。
5.2 导入 EXCEL 报工数据时,数字变成科学计数法
现象:用源码自带的 Excel 导入功能把含有长数字编码的物料批次导入系统,结果读出来变成1.23456E+10,导致查询不到数据。
原因:Apache POI 读取 Excel 时,把纯数字单元格默认转为 double,长编码在超过 11 位后必然丢失精度。这是 MES 开发里的经典坑。很多现场物料批次号是 15 位的数字,一导入就翻车。
解决:在导入工具类里,强制设置单元格类型为字符串,并使用DataFormatter工具读取,而不是直接调用getStringCellValue()。具体代码可以这样做:
DataFormatter formatter = new DataFormatter(); Cell cell = row.getCell(i); cell.setCellType(CellType.STRING); String value = formatter.formatCellValue(cell);这里有个参数要特别留意:DataFormatter会把日期型单元格按 Excel 的格式输出为字符串,比如“2025/1/1”,如果你要存到数据库的 date 字段,必须再套一层日期解析,否则会报转换错误。建议导入前先打印几行原始值,别信 Excel 里显示的格式。
5.3 启动报“数据库连接池初始化失败”但数据库明明是通的
现象:后端启动时报Cannot create PoolableConnectionFactory,但你在命令行用手动 mysql 连接完全正常。
原因:这种“玄学”问题大多数是驱动版本和 MySQL 版本不匹配。比如源码用的是mysql-connector-java5.1.x,但本地 MySQL 是 8.0 以上,认证协议不同。即便 jar 包里有两个驱动,Spring Boot 也可能加载了错误的那一个。
解决:升级驱动为com.mysql.cj.jdbc.Driver(带 cj 的),并在 pom.xml 里改为 8.0 系列版本。同时,在 application.yml 的 url 后面加上allowPublicKeyRetrieval=true,因为 MySQL 8 用 caching_sha2_password 认证时,连接的第一次握手需要获取公钥,如果配置为 false,JDBC 会拒绝连接。这个问题我以前排查了整整一个下午,最后发现就是少了这一小段参数,非常值得写进你的部署检查清单。
5.4 报表页面打开很慢,工单列表查询超时
现象:车间反馈系统用久了,工单列表点开要十几秒,导出报表直接卡死。
原因:完整版源码的 SQL 往往只做了基本增删改查,没有为主表建立复合索引。工单表可能有上万行、工序表几十万行,特别是查询条件按work_order_no、status、plan_start_time组合时,全表扫描就会越来越慢。
解决:给高频查询组合加索引,这是收益最明显的调整。不要先改代码,先看数据库慢查询日志,找到耗时的 SQL,再决定加什么索引。常用的一条命令是:
ALTER TABLE work_order ADD INDEX idx_status_time (status, plan_start_time);参数说明:这里把status放在前面、plan_start_time放在后面,是因为查询里通常先用状态筛选,再用时间排序。如果查询还经常按车间过滤,在status前面加上workshop_id,效果会更好。注意,索引不是越多越好,写频繁的表索引多了会拖慢插入和更新,建议只覆盖核心查询。
5.5 前后端联调时跨域问题:明明配了 CORS 还是报跨域
现象:前端页面请求后端接口,浏览器提示No 'Access-Control-Allow-Origin' header is present,但源码里明明配置了 CORS。
原因:问题往往出在拦截器或过滤器把请求拦住了,统一返回了错误信息,错误信息里没有 CORS 响应头。也就是说,你看到跨域其实不是浏览器拦截,而是后端在进入 controller 之前就被验证码过滤器或登录鉴权过滤器拦截掉了。
解决:将 CORS 配置加到过滤器链的最前面,而不是只用一个全局 CORS 过滤器。常见做法是在 Spring Boot 里实现OncePerRequestFilter,通过addCorsMappings配置好允许的源、方法、头,同时保证自定义过滤器里对预检请求(OPTIONS)直接放行。做法是在前端代理已经配置了同源的时候,其实可以完全绕开后端 CORS,直接让代理转发,这样少掉很多麻烦。但如果是外部系统直连后端,那 CORS 必须按“请求先通过过滤器再校验”的顺序来调。
6. 从“完整版”到“够用版”:二次开发与验收技巧
6.1 扩展一个设备数据采集模块:最小改动方案
车间里设备数据采集往往不是等你想好再做,而是生产第二天就要接入。拿到完整版源码后,我建议的常规做法是复用现有collect接口,通过定时任务去读一张中间表。设备端的 PLC 或数据网关把实时数据写到equipment_rt_data表,MES 每隔 30 秒通过 Quartz 任务解析该表并更新设备状态。
这个方案有几个好处:不修改既有核心逻辑,新业务被隔离在独立模块里,出问题只影响采集,不影响报工。你只需要新增一张采集表和一个定时任务类,所有代码控制在 200 行内。注意采集表的字段要带上设备编码和采集时间,并且时间要用数据库默认时间,不要由采集端传,这样后续做时序分析不会出现时间乱跳。
6.2 性能看板和车间大屏的数据刷新策略
车间大屏需求的本质是“总览要醒目,局部能下钻”。完整版源码里通常已经有看板页面,但往往是通过前端定时轮询后端接口拿到数据。如果接口每次都全量统计,数据库扛不住。我会改成分级缓存:把工单完成率、设备稼动率这些聚合结果放到 Redis,设置 10 秒过期,后端接口优先查缓存,缓存没有再查数据库。
大屏展示还应该区分“当前时刻”和“本班次累计”。我用过的可靠方案是前端每 5 秒请求一次current-trend接口,而班次汇总数据由后端每 30 秒刷新。接口的数据量很小,响应时间应控制在 200 毫秒内。如果存在毫秒级抖动,不要过多纠结,大屏上没人会介意 1-2 秒延迟,但刷新频率过高会导致数据库连接池耗尽。
6.3 如何验证二次开发没破坏原有逻辑
二次开发最怕的是改一处崩一片。我会在交付前跑三组回归验证。第一组是核心流程烟囱测试,手工构建一条工单,完成下发、报工、完工、追溯四步,中间故意制造不良和返工场景,确认状态流转正确。第二组是接口压力测试,重点看报工接口并发时数据是否准确,发现并发问题先看有没有加锁。第三组是权限测试,低权限账号访问高权限菜单必须被拦截,MES 里很多问题源于权限映射错乱。
我养成的习惯是每改完一个模块,就导出一份数据库备份,并记录下来改动了哪些表结构。这样后续上环境时,可以用比较工具先把两份库结构 diff 一遍。数据可以不要,但表结构必须和生产环境一致,这是化工车间项目给我的最大教训。现在每次团队拿到“完整版”源码,我都会跟他们强调一句:不要迷信“完整”这个词,只有经过自己验证跑通、能对接真实业务流程的代码,才算真正落地了。希望这点经验能帮你少走几步弯路。
本文还有配套的精品资源,点击获取