ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

开源Java Web MES源码拆解:Spring Boot工单与追溯实战

开源Java Web MES源码拆解:Spring Boot工单与追溯实战 简介这是一套面向制造业信息化从业者、Java后端开发者及MES系统学习者的开源制造执行系统设计源码定位于帮助读者理解并搭建覆盖订单管理、物料跟踪、生产调度、品质管理等环节的完整解决方案。压缩包共589个文件其中以264个Java源文件为业务逻辑核心辅以81个Vue组件、62个JavaScript脚本构建前端交互87个SVG保证界面清晰另有XML、YAML配置及VM模板等包体仅3.62MB目录结构清晰便于检索。该资源已有515人学习下载。源码基于若依框架扩展可对照ruoyi-mes与ruoyi-system模块体会模块化拆分思路并从批处理脚本、环境配置和SQL脚本中了解项目部署与初始化流程其中批处理脚本可一键完成编译、打包与Web端启动降低环境搭建成本。配套的若依环境使用手册可显著降低上手门槛适合用于学习JavaWeb技术在工业场景中的落地实践和二次开发参考对实施MES项目亦有直接借鉴价值。1. 开源的 Java Web MES别被“制造执行系统”吓住这份源码能让你两天看懂车间级生产第一次看到“MES”这三个字母不少 Java 开发者的第一反应是“这是工厂里用的系统离我太远”。但我拆完这份基于 Java 和 Web 技术的开源制造执行系统源码后发现它并没有想象中那么“重”核心就是管“生产工单怎么下发、工序怎么报工、物料怎么追溯、设备状态怎么实时上报”这几件事。对正在做 Java 后端、想接触工业互联网场景的工程师来说这份源码最大的价值是给你一个完整的、可落地的 MES 业务骨架——不是那种只有 CRUD 的 demo而是带工序建模、工单流转、条码追溯的闭环系统。而且它基于 Spring Boot 等主流 Web 技术栈代码结构清晰拆起来不会劝退新手。这篇笔记我会直接从源码结构、数据模型、部署步骤、核心业务实现到常见的坑完整带你过一遍值不值得下、怎么用。2. 先看清家底这套 MES 源码的模块边界与技术选型2.1 技术栈判断为什么说它适合拿来“抄作业”拿到源码包我会先看pom.xml和启动类这能最快判断一套系统的技术品味。这套 MES 源码属于典型的 Spring Boot 单体架构但做了模块化拆分。整体技术栈是后端框架Spring Boot 2.x Spring MVC这个版本选择比较务实稳定性和社区资料都足够多遇到问题容易搜到答案持久层MyBatis-Plus这一点对我这种习惯写复杂 SQL 的人来说很友好。MyBatis-Plus 的分页插件和条件构造器在 MES 这种需要大量多条件筛选的业务场景里比如按工单号 工序 状态查报工记录能省下很多重复的 XML 编写工作权限模型基于 RBAC基于角色的访问控制菜单、按钮、数据权限三层都做了。MES 系统里车间主管和操作工的权限边界清晰这个设计可以直接复用到其他管理类项目前端Vue 2 Element UI这是国内开源项目最常见的组合如果你之前只写过后端接口这套前端的页面逻辑和 API 封装方式也值得一看数据库MySQL 5.7存储引擎建议 InnoDB这里有个细节我觉得做得不错源码里把业务模块和框架模块分得很清。framework包放通用配置安全、拦截器、异常处理modules包放具体业务生产管理、设备管理、质量检验。这种边界感对二次开发至关重要——你改业务逻辑时不需要动底层框架代码。2.2 六大核心功能模块每一块都对应一个车间真实角色通读源码里的数据库初始化脚本和 Controller 层接口我会把它的功能梳理成下面这张表方便你对号入座找自己要看的代码功能模块涉及角色对应业务对象关键接口部分生产工单管理计划员工单production_order工单下发、拆分、齐套校验工序派工与报工车间主任、操作工工序任务process_task派工到人、报工数量、工时录入物料追溯质量专员批次号batch_no、SN序列号正反向追溯查询链路设备状态监控设备管理员设备台账equipment设备点检记录、停机时长统计质量检验质检员检验单inspection_order来料检、过程检、完工检安灯与异常上报一线操作工异常事件exception_event异常上报、升级流程每个模块不是一个孤立的表而是有完整的状态流转。比如production_order表里会有status字段草稿、已下发、生产执行中、完工每个状态的变更在源码里都对应一个Transactional事务注解方法而不是直接裸露的 update 语句。这一点是我看源码时比较在意的地方——状态机不写清楚后面报表统计铁定出错。2.3 数据库脚本里的秘密三十多张表是怎么支撑起一个车间的打开根目录下的sql文件夹里面有初始化脚本。体感上接近三十多张业务表但核心链路非常聚焦。我强烈建议第一步不要看所有表而是盯住一条主线走透生产工单主线mes_order→mes_order_item→mes_process→mes_process_report这条链路的逻辑是一张生产工单mes_order拆成多个工单项mes_order_item代表不同产品型号每个工单项经过多个工序mes_process比如焊接、组装、测试每道工序完工后在mes_process_report里填报完工数量。很多新手拆代码时容易陷入“每张表都想搞懂”的泥潭我的经验是先顺着这条主链读读完你就会发现MES 的“核心账本”其实很简单物料从哪来批次号经历过哪些工序过程记录变成了什么成品SN 码。其他设备表、质检表都是围绕这条主链挂接的分支数据。源码里对这几张表的字段注释也很完整外键逻辑能在实体类注解里直接看到不需要额外去翻数据库关系图文档。3. 把可执行文件跑起来从导入 IDEA 到看到第一个看板数据3.1 部署硬性要求版本不匹配是最大的“劝退点”直接说结论遇到环境问题大多和版本有关。建议严格按照下面的版本来别直接用最新版尝鲜JDK 必须是 1.8不是 11 或 17源码里很多语法和依赖在 JDK 8 下最稳Maven 3.6需要能访问中央仓库。如果你的网络环境需要代理注意在settings.xml里配置镜像MySQL 5.7 或 8.08.0 需要注意驱动版本和时区设置下文会专门讲Redis 不用装报表和缓存用得不多但如果有模块启动报错连接 Redis直接找到配置文件禁用即可这套系统用到了前端编译npm run build所以 Node.js 环境建议用 14 或 16 LTS 版本。用太新的 Node比如 20去编译 Vue 2 项目会有大量 OpenSSL 相关的报错需要在 package.json 里加NODE_OPTIONS--openssl-legacy-provider处理。提示建议在根目录 README 做好标记该项目应该支持前后端分离部署。前端编译后的 dist 目录可以直接塞进 Nginx 或后端静态资源目录这一步对生产部署很关键。3.2 后端启动步骤IDEA 里 5 分钟跑通第一步导入项目。用 IDEA 选择Maven类型导入根目录下的pom.xml等待依赖下载完成。这一步时间长短取决于网速有个判断标准右下角进度条消失且 Maven 面板不报红。第二步改配置文件。打开application-druid.yml核心要改三处spring: datasource: url: jdbc:mysql://localhost:3306/mes?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLfalseserverTimezoneGMT%2B8 username: root password: your_password # 若Redis连接报错可临时注释/关闭 redis: enabled: false注意MySQL 5.7 的连接串必须带serverTimezoneGMT%2B8否则 Java 8 下日期时间映射会差 8 小时查出来的报工时间全是乱的。zeroDateTimeBehaviorconvertToNull是处理数据库里常见的时间零值0000-00-00 00:00:00的不加这个字段MyBatis 查询会直接抛异常。第三步执行 SQL 初始化脚本。把sql/目录下的.sql文件按文件名前缀顺序执行通常是先建库再导数据。注意检查脚本里是否有DROP TABLE语句有的话执行前务必确认你连的是自己的测试库。第四步找启动类。在mes-admin模块下找到MesApplication.java实际类名以源码为准右键运行。看到Started MesApplication in xx seconds日志且没有 ERROR就意味着后端启动成功。3.3 前端启动步骤Vue 项目的调试心态前端项目在mes-ui/目录打开终端执行下面命令# 建议先用淘宝镜像源安装依赖否则国内网络下载会比较慢 npm config set registry https://registry.npmmirror.com # 安装依赖 npm install # 启动开发调试模式 npm run dev说明npm run dev启动后默认监听 9000 端口它会在内存里起一个 Webpack 开发服务器把前端请求代理到后端的 8080 端口。这个代理配置在vue.config.js里如果后端改了端口这里同步修改即可。用开发模式的好处是有热更新后端接口改了前端页面不用手动刷新。首次编译一个 Vue 2 项目大概需要一到两分钟属于正常现象。启动成功后浏览器访问http://localhost:9000能看到登录页说明前后端已经联通。默认账号密码在 README 里通常是 admin/admin 或者 admin/123456。登录进去后找“生产看板”菜单如果能显示出工单进度和工序报工汇总数据就说明整个链路已经通了。3.4 前端只显示空白页或 404 的排查思路主流程跑完以后有个常见的后续需求把前端打包后放到 Nginx 供多个车间同时访问。这个环节经常遇到页面刷新出现 404 的情况。原因是 Vue Router 用的 history 模式刷新时浏览器会带着具体路由路径请求服务器但 Nginx 配置没指回首页。server { listen 8088; server_name your_ip; root /opt/mes-ui/dist; index index.html; location / { # 关键是下面这一行所有找不到的路径都rewrite到index.html try_files $uri $uri/ /index.html; } # 反向代理后端请注意前缀与后端context-path一致 location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }参数解释try_files指令依次判断请求的文件是否存在都不存在就内部重定向到/index.html交给 Vue Router 去解析路由。proxy_pass注意结尾是否带/影响路径拼接方式我一般建议带/并使用完整前缀匹配避免出问题。4. 核心业务实现拆解工单下发、工序流转与条码追溯的代码链路4.1 工单下发状态机与库存校验工单下发是 MES 的起点。源码里对应的是OrderController里的releaseOrder方法。这一步的关键不是改工单状态而是一连串的校验和联动Transactional(rollbackFor Exception.class) public ReleaseOrderResult releaseOrder(ListLong orderIds) { // 1. 校验工单是否处于可下发状态这里必须校验状态防止重复下发 ListOrder orders orderMapper.selectBatchIds(orderIds); for (Order order : orders) { if (!order.getStatus().equals(OrderStatusEnum.CREATED.getCode())) { throw new BizException(工单 order.getOrderNo() 状态不正确无法下发); } } // 2. 库存齐套校验遍历 BOM 物料清单检查仓库可用量 for (Order order : orders) { BomBO bom bomService.getBomByProductId(order.getProductId()); for (BomItem item : bom.getBomItems()) { int availableStock stockService.getAvailableStock(item.getMaterialId()); if (availableStock item.getRequiredQty() * order.getOrderQty()) { throw new BizException(物料编码 item.getMaterialCode() 库存不足); } } } // 3. 更新工单状态为“已下发”同时生成工序任务 for (Order order : orders) { order.setStatus(OrderStatusEnum.RELEASED.getCode()); orderMapper.updateById(order); processTaskService.createTasksByOrder(order); } return ReleaseOrderResult.success(); }逻辑说明这个方法的三个核心步骤分别对应了车间管理的三个铁律只允许“新建”状态的工单下发对应业务上的流程防错库存齐套校验确保开工时物料是够的不会干到一半停线生成工序任务则把工单自动拆解成一道道工序任务指派给对应产线。不注意事务就很容易出现“工单状态已变更但工序任务没生成”的脏数据。关于库存校验源码里是支持“部分齐套”的计算即考虑到安全库存和生产在途具体参数在stockService.getAvailableStock里可以调整。生产备料的人可能会注意这里的负数库存逻辑如果系统允许负库存表示“欠料”则在 MES 中这是一个需要关闭的参数。4.2 批次追溯用一条 SQL 串起全链路MES 听上去有技术含量但追溯原理就一张图通过唯一的批次号查询所有和它有交集的工序报工数据。源码里实现并不神秘核心是一条深度分页查询SELECT t1.report_code AS 报工单号, t1.batch_no AS 批次号, t2.process_code AS 工序编码, t2.process_name AS 工序名称, t1.worker_name AS 操作工, t1.report_qty AS 完工数量, t1.create_time AS 报工时间 FROM mes_process_report t1 LEFT JOIN mes_process t2 ON t1.process_id t2.id WHERE 11 AND (t1.batch_no IN (SELECT batch_no FROM mes_process_report WHERE sn_code #{snCode}) OR t1.sn_code #{snCode}) ORDER BY t1.create_time DESC参数说明snCode是产品唯一序列号输入这个 SN 码后系统先找到它对应的批次号再把该批次号下所有报工记录捞出来。这就是“正查”从成品 SN 反查过程数据。反向追溯也一样输入一个来料批次号即可查出这批料投到了哪些 SN 成品里。注意这里用了LEFT JOIN而不是INNER JOIN保证即使工序基础数据被误删报工记录仍能显示虽然工序名会为空但至少不会查不到记录。4.3 报工接口的并发防重这个小细节值得抄车间里不同班组可能同时扫同一个工单条码报工很容易造成数据重复。看看源码里是怎么防的/** * 工序完工报工 * 在数据库层面加唯一约束工单号 工序ID 报工日期 * 并在服务层用 synchronized 分布式锁双重校验 */ public void reportProcess(ReportRequest req) { // 1.构建唯一键 String lockKey String.format(MES:REPORT:LOCK:%S:%S:%S, req.getOrderNo(), req.getProcessId(), LocalDate.now()); // 2.尝试获取分布式锁防集群部署时多个实例并发 boolean locked redisLockUtil.tryLock(lockKey, 10, TimeUnit.SECONDS); if (!locked) { throw new BizException(该工序今日重复报工请勿重复提交); } try { // 3.再次校验数据库唯一索引防御性编程 int count processReportMapper.checkUnique(req.getOrderNo(), req.getProcessId(), LocalDate.now()); // 注意这里用时间做条件会命中索引 if (count 0) { throw new BizException(重复报工); } // 4.执行插入 把完工数量回写到工单 processReportMapper.insert(req); orderMapper.incrementCompletedQty(req.getOrderNo(), req.getReportQty()); } finally { redisLockUtil.unlock(lockKey); } }逻辑说明这个接口解决的是车间里最容易出现的“手抖多次提交”问题。用 Redis 分布式锁保证在集群部署、多个实例同时收到请求时只有一台机器能拿到锁执行后续逻辑。数据库的唯一索引是最底层的兜底就算分布式锁失效了数据库约束还能挡住第二层。步骤 4 里对工单已完工数量的更新用了increment而不是先查再加这是一个很典型的 SQL 原子更新操作示范能有效避免并发下数据覆盖。注意这里的redisLockUtil异常时会走降级逻辑源码里注释了“如 Redis 不可用则退化为 synchronized 单机锁”。但单机锁在集群环境就失效了生产环境还是建议把 Redis 高可用配置好。5. 避坑手册拆这一整套 MES 源码最常见的 6 个翻车现场5.1 启动报错Failed to configure a DataSource现象后端启动日志里出现这个错直接导致启动失败。查看代码发现没有关于数据库连接的详细报错。原因绝大多数情况是application-druid.yml里的数据库连接信息没改或者 MySQL 服务没启动。还遇到过一种情况Maven 编译时把resources目录下的配置文件漏掉了检查 target 目录下的 yml 是否存在。解决确认 MySQL 服务改对 URL、账号、密码。如果 target 目录下文件缺失在 IDEA 里执行 Maven 的Resources生命周期生成配置文件。5.2 前端 npm install 卡死或报错现象npm install执行十几分钟后提示ETIMEDOUT或ENOTFOUND依赖装不上。原因默认 npm 源访问不稳定项目里的依赖又比较多Vue 全家桶加各种 UI 组件库网络波动就会中断。解决换淘宝源再装具体命令上文已写过。还有一个小坑package-lock.json如果锁定的镜像域名是旧地址直接删掉该文件再npm install。5.3 时间字段差 8 小时现象生产报工单页面显示的报工时间比北京时间早了 8 小时。原因MySQL 连接串没加serverTimezone默认用了服务器 UTC 时区。解决连接串加上serverTimezoneAsia/Shanghai或serverTimezoneGMT%2B8。同时检查 Jackson 的全局日期格式化配置如果后端的LocalDateTime序列化没指定格式前端可能显示时间戳或数组格式。5.4 工单下发后查不到生成的工序任务现象车间主任下发了工单但工序派工页面里是空的。原因大概率是processTaskService.createTasksByOrder方法里前置条件是根据产品路线表routing查工序列表。如果mes_routing表里没有维护该产品的工序那么这一步会静默失败没有正确抛出异常只是返回了一个空集合。解决去基础数据维护页面给对应产品配置完整的工艺路线。我拆项目时曾经犯懒没配直接走接口测试结果就是这个坑经验之谈。5.5 报工数量超过工单需求数量现象质检员发现某道工序的报工数量比工单还需要数量还多系统却没有拦住。原因源码对于报工数量校验的逻辑是“加和当前已报工数量 工单需求数量才拦截”但如果两道工序同时报工后端没有对工单维度做一次select ... for update行锁。结果就是并发场景下的数量校验失效。解决读源码的时候在reportProcess初始位置加一行Order order orderMapper.selectByIdForUpdate(req.getOrderId());确保同一时间只能一个请求读取并校验同一条工单记录。这是一个很好的分布式事务还原案例。5.6 前端页面菜单显示但点击路由空白现象登录成功左侧菜单能展开但点进去内容区是空白。原因大概率不是后端问题而是当前账号没有分配按钮权限或数据权限。查看源码的getRouters接口发现它会根据当前用户角色动态生成前端路由表。如果角色没关联菜单前端路由表就是空的。解决用 admin 账号登录系统管理 → 角色管理给岗位关联对应菜单权限重新登录即可。这个坑对刚接触 RBAC 实现的人来说很典型我在给客户做完权限配置后经常看到他们一脸困惑地来找我其实只是菜单授权没点全。6. 进阶验证用 Postman 压测你的 MES 登录并发顺带聊聊二次开发规范源码跑通只是第一步要确认这套系统能扛住车间高峰期的使用压力我建议做一个最简单的压测。MES 车间里最频繁的操作不是上生产大屏而是产线员工用扫码枪/安卓 PAD 执行报工和派工操作所以我会直接测报工接口的并发不重复提交。用 Postman 的 Runner 功能或者直接写一个 JUnit 并发测试类。维护一套固定测试数据模拟产线上 500 次并发报工请求到同一个工单号配置好请求头里的 Token需要先用 admin 登录拿 Token在测试脚本里动态设置为全局变量然后观察响应结果。Test public void testConcurrentReport() throws InterruptedException { // 初始化 500 个线程同时提交报工验证成功率与重复拦截次数 int threadCount 500; CountDownLatch latch new CountDownLatch(threadCount); ExecutorService executor Executors.newFixedThreadPool(32); AtomicInteger successCount new AtomicInteger(0); AtomicInteger rejectCount new AtomicInteger(0); for (int i 0; i threadCount; i) { executor.execute(() - { try { // 调用报工接口这里为演示只模拟返回状态码 String result HttpClient.post(http://localhost:8080/api/mes/report, msg); if (result.contains(成功)) { successCount.incrementAndGet(); } else { rejectCount.incrementAndGet(); } } finally { latch.countDown(); } }); } latch.await(); // 等待所有线程执行完毕 System.out.println(成功数量: successCount.get()); System.out.println(拒绝数量: rejectCount.get()); // 符合预期成功数量应为 1剩余 499 条被唯一约束拦截 }参数说明故意把线程池设置为固定 32 线程是为了模拟车间现场多台扫码设备同时上报的场景。CountDownLatch确保 500 个任务全跑完才打印结果。如果最终输出的成功数量远大于 1说明事务或锁没生效如果正确拦截了重复报工成功数应该只有 1 条剩下的是 499 条“重复报工”记录这能直接验证第 4 章里 Redis 锁和唯一索引在并发场景下的真实表现。这套源码还有一个很值得学习的点它的异常统一处理。所有业务异常都抛BizException并由GlobalExceptionHandler统一捕获包装成R.fail()返回给前端。这种设计确保车间员工在扫码枪上看到的不是 Spring 的 500 页面而是“工单状态不正确无法下发”这样的明确提示。二次开发时请务必保持这个习惯不要在 Controller 里 try-catch 后自己封装返回体会破坏全局结构。压测之外我还习惯做一件事打开logging.level.com.mesdebug把 MyBatis 的执行 SQL 全部打印出来看一遍。你会看到 MyBatis-Plus 自动生成的 SQL 和 XML 里手写的 SQL 混合输出。重点检查那些多表 join 的查询有没有出现“N1 次查询”问题——比如循环查工序列表后再查每个工序的报工明细。源码里大部分接口都处理得很好但报表模块有时会冒出小瑕疵可以尝试用Select注解写一个批量 mapper 方法代替循环。拆完这套 MES 源码之后我最大的感受是一套生产系统最重要的根不在新技术而在于严谨的业务状态控制和数据一致性意识。从那以后不管写什么管理类系统我拿到手的第一件事就是梳理它的状态机流转图和唯一性约束清单先保证不能产生脏数据再考虑页面怎么美化。这套 MES 源码最贴合实际的价值正是拿来训练这种“车间级严谨”的代码习惯。希望帮到你。本文还有配套的精品资源点击获取
返回列表