简介这份资源是基于 QCADOO 框架构建的开源 MES 生产制造管理系统面向制造业信息化开发者、企业技术团队及工业软件学习者可用于机加工、食品包装、制鞋、服装等离散制造场景的二次开发与学习研究。压缩包共约 2000 个文件整体 37.7MB以 1090 个 Java 源码文件为核心业务实现595 个 XML 承担配置与数据映射118 个 JS 与 40 个 CSS 支撑前端交互与界面样式另有 88 个 properties 配置、54 个 JSP 页面及少量 SQL、Shell 脚本构成完整的前后端工程结构。目前已有 150 人学习关注。借助这套源码读者可深入理解 QCADOO 低代码平台在 MES 领域的落地方式掌握工单排产、物料追踪、生产报工等模块的代码组织与扩展思路并参考其目录分层与配置体系快速搭建属于自己的制造执行系统原型。1. 从一张工单卡住三条产线说起QCADOO 开源 MES 到底能接住什么车间里最怕的不是设备坏是工单在系统里卡住。操作工说“我点了开工但报工数量填不进去”班组长说“我看不到这条线今天做了多少”计划员说“我明明排了 800 件系统显示还是 0”。三个角色三套说法最后发现是 MES 里工序流转的状态机没配对。这类问题在自研 MES 里很常见而 QCADOO 开源 MES 想解决的正是这种“生产制造管理系统落地时业务逻辑和代码实现对不上”的麻烦。Manufacturing Execution System 这个词听起来大落到车间就是工单、报工、物料、质检、设备这几件事能不能串成一条线。QCADOO 的价值不在于功能多而在于它把这条线用开源代码摊开了你能改、能查、能按自己产线的节奏调。适合谁适合那些买不起商业 MES、又不想从零写一套的工厂 IT或者想拿一个真实生产系统练手的 Java 开发者。2. QCADOO 开源 MES 的技术底座为什么选它而不是自己写2.1 先看它把哪些生产对象抽象成了表QCADOO 这类开源 MES 的底层逻辑是把车间里的物理对象映射成数据库里的实体。你打开它的数据模型核心表大概围绕这几类工单work_order、工序process、报工记录production_report、物料material、BOMbom_header/bom_line、质检quality_inspection。工单表里会有状态字段常见值是“待排产、已排产、生产中、已完工、已关闭”。工序表里会定义每道工序的序号、名称、标准工时、是否质检点。报工记录表里存的是操作工每次提交的数量、合格数、不良数、设备号、时间戳。这些表不是随便建的。工单和工序是一对多工序和报工是一对多报工和质检是一对一或一对多。你如果自己从零写最容易翻车的地方就是状态流转没锁死——比如工单已经“已完工”了报工接口还能往里写数据。QCADOO 在服务层一般会做状态校验但开源版本不一定覆盖所有边界这就是你接手后要补的第一课。提示拿到源码后先别急着跑把work_order和production_report两张表的字段和索引看一遍后面所有业务逻辑都绕不开它们。2.2 技术栈选型Spring Boot MyBatis 为什么是车间里最稳的组合QCADOO 开源 MES 常见的技术栈是 Spring Boot 做后端MyBatis 做持久层前端可能是 Vue 或 Thymeleaf。这个组合在工厂环境里有个现实优势招人容易。车间 IT 往往只有一两个 Java 开发你让他去搞微服务、Service Mesh他维护不动。Spring Boot 单体应用加 MyBatis 手写 SQL出问题了能直接看 SQL 日志能手动改数据能快速定位是哪条查询拖慢了报工接口。MyBatis 的另一个好处是 SQL 可控。MES 里有很多复杂查询比如“查某条产线今天所有工序的报工汇总”用 JPA 自动生成 SQL 可能性能很差但 MyBatis 你可以自己写GROUP BY和索引提示。QCADOO 的 Mapper XML 里通常会有这类统计查询你接手后要重点看ProductionReportMapper.xml和WorkOrderMapper.xml。!-- 示例按产线和日期汇总报工数量 -- select idsumReportByLineAndDate resultTypemap SELECT p.line_code AS lineCode, SUM(r.qualified_qty) AS totalQualified, SUM(r.defect_qty) AS totalDefect FROM production_report r JOIN process p ON r.process_id p.id WHERE r.report_time BETWEEN #{startTime} AND #{endTime} AND p.line_code #{lineCode} GROUP BY p.line_code /select这段 SQL 的逻辑是按产线编码和日期范围聚合报工数据。参数startTime、endTime、lineCode从服务层传入。注意report_time字段上要有索引否则数据量上到十万级以后这个查询会拖慢整个报工页面。我一般会在report_time和process_id上建联合索引顺序是process_id在前、report_time在后因为产线过滤的区分度更高。2.3 部署前必须确认的三个环境参数QCADOO 开源 MES 部署时有三个参数最容易埋雷。第一个是数据库连接池大小。车间并发不高但报工高峰期可能同时有几十个操作工提交spring.datasource.hikari.maximum-pool-size设成 20 到 30 比较稳妥太小会排队太大数据库扛不住。第二个是时区。MES 里所有时间戳必须统一spring.jackson.time-zone和数据库的time_zone要一致否则报工时间会差几个小时统计报表全乱。第三个是文件上传路径。质检图片、工艺图纸这些附件file.upload.path要指向一个独立磁盘分区别放系统盘车间断电重启后系统盘写满会导致服务起不来。# application.yml 关键片段 spring: datasource: hikari: maximum-pool-size: 25 minimum-idle: 5 jackson: time-zone: Asia/Shanghai date-format: yyyy-MM-dd HH:mm:ss file: upload: path: /data/mes/upload/这些参数没有绝对标准但时区和上传路径是硬性要求。连接池大小可以根据实际并发微调观察 HikariCP 的日志如果经常出现Connection is not available就往上加。3. 把 QCADOO 跑起来从建库到第一条工单报工3.1 建库和初始化数据的完整命令拿到 QCADOO 源码后第一步是建库。常见做法是在 MySQL 8.0 里创建一个qcadoo_mes库字符集用utf8mb4排序规则utf8mb4_general_ci。然后执行源码doc/sql目录下的初始化脚本。注意脚本可能有多个按文件名顺序执行先建表再插基础数据。# 创建数据库 mysql -u root -p -e CREATE DATABASE qcadoo_mes DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 执行初始化脚本按顺序 mysql -u root -p qcadoo_mes doc/sql/01_schema.sql mysql -u root -p qcadoo_mes doc/sql/02_base_data.sql mysql -u root -p qcadoo_mes doc/sql/03_demo_data.sql执行完检查三张表有没有数据sys_user里应该有默认管理员material里应该有示例物料process里应该有示例工序。如果process表是空的后面建工单时选不到工序报工就无从谈起。我遇到过有人只执行了01_schema.sql就启动服务结果登录后一片空白回头查了半天以为是前端问题其实是基础数据没插。3.2 启动服务并验证报工接口初始化完成后用 Maven 打包启动。QCADOO 通常是多模块项目根目录执行mvn clean package -DskipTests然后找到mes-web或mes-admin模块下的可执行 jar。# 打包 mvn clean package -DskipTests # 启动根据实际模块名调整 java -jar mes-web/target/mes-web-1.0.0.jar --spring.profiles.activedev启动日志里看到Started MesApplication in x seconds就算成功。然后打开浏览器访问http://localhost:8080用默认账号登录。接下来手动走一遍流程新建物料 → 新建 BOM → 新建工序 → 新建工单 → 工单下达 → 操作工报工。报工的时候注意看接口返回如果报工数量超过工单数量系统应该拦截。如果没拦截说明服务层的数量校验没生效你要去ProductionReportService里补一段逻辑。// 报工前的数量校验示例 public void validateReportQty(Long workOrderId, BigDecimal reportQty) { WorkOrder order workOrderMapper.selectById(workOrderId); BigDecimal alreadyReported productionReportMapper.sumQtyByWorkOrder(workOrderId); BigDecimal planQty order.getPlanQty(); if (alreadyReported.add(reportQty).compareTo(planQty) 0) { throw new BusinessException(报工数量超过工单计划数量); } }这段代码的逻辑是先查工单计划数量再查已报工数量两者相加如果超过计划数量就抛异常。参数workOrderId和reportQty从接口传入。注意alreadyReported可能为 null要处理空值。这个校验在开源版本里不一定有我建议你接手后第一件事就是补上否则车间操作工多报几次库存和成本全对不上。3.3 工单状态流转的配置要点QCADOO 的工单状态流转通常定义在枚举类或数据库字典里。常见状态是PENDING待排产、SCHEDULED已排产、IN_PROGRESS生产中、COMPLETED已完工、CLOSED已关闭。状态之间的跳转要有约束比如PENDING只能到SCHEDULEDIN_PROGRESS只能到COMPLETED。如果你在数据库里直接改状态跳过了服务层校验很容易出现“已完工的工单还能报工”这种玄学问题。注意不要手动 update 工单状态字段所有状态变更走服务层接口。开源版本的状态机可能不完整你需要在WorkOrderStateMachine或类似类里补全跳转规则。4. 避坑与排查QCADOO 开源 MES 落地时最容易翻车的五件事4.1 报工提交后页面没刷新但数据库有数据现象操作工点报工页面提示成功但工单进度条没变。原因前端调完报工接口后没有重新拉取工单详情或者后端返回的数据结构和前端预期不一致。解决先看浏览器 Network 里报工接口的返回再对比工单详情接口的返回。如果报工接口返回的是{code: 200, data: null}前端可能以为没数据。改成返回更新后的工单对象或者前端在报工成功后主动调一次详情接口。4.2 工序顺序乱了报工可以跳过前道工序现象工单有三道工序操作工直接报第三道系统也接受了。原因服务层没有校验前道工序是否完工。解决在报工接口里加一段逻辑查当前工序的前一道工序的报工记录如果前道工序没有完工记录就拒绝报工。这个校验在 QCADOO 开源版里可能没有需要自己补。4.3 统计报表数字对不上差几个小时现象日报表里今天的产量比实际少或者昨天的产量跑到今天了。原因数据库时区和 Java 应用时区不一致或者报工时间用的是数据库NOW()而不是应用层时间。解决统一用应用层时间report_time字段由 Java 代码new Date()写入不要用 MySQL 的NOW()。同时检查spring.jackson.time-zone和 MySQL 的time_zone是否都是Asia/Shanghai。4.4 并发报工时数量超报现象两个操作工同时报工各自报 500工单计划 800结果系统接受了 1000。原因校验逻辑是先查后插没有加锁。解决在工单表上加乐观锁版本号或者用SELECT ... FOR UPDATE锁住工单行。QCADOO 开源版一般没有处理这个并发场景需要自己加。-- 报工时锁住工单行 SELECT * FROM work_order WHERE id #{workOrderId} FOR UPDATE;这条 SQL 放在报工事务的开头确保同一工单的报工串行化。注意事务隔离级别要是READ COMMITTED或更高否则锁不住。4.5 部署后上传附件失败报路径不存在现象质检图片上传报错FileNotFoundException但路径明明存在。原因file.upload.path配置的目录没有写权限或者路径结尾少了斜杠导致拼接错误。解决检查运行服务的系统用户对目录有没有写权限chmod 755或chown一下。路径配置建议用绝对路径结尾加斜杠代码里拼接时用Paths.get(basePath, fileName)而不是字符串加号。5. 让 QCADOO 真正适配你的车间两个进阶改造技巧5.1 用数据库视图简化报工统计查询QCADOO 原生的报工统计可能要多表关联查询慢。我一般会建一个视图把工单、工序、报工、物料关联好报表直接查视图。CREATE VIEW v_production_summary AS SELECT wo.order_code, wo.plan_qty, p.process_name, p.seq_no, SUM(r.qualified_qty) AS qualified_qty, SUM(r.defect_qty) AS defect_qty, MAX(r.report_time) AS last_report_time FROM work_order wo JOIN process p ON p.order_id wo.id LEFT JOIN production_report r ON r.process_id p.id GROUP BY wo.order_code, wo.plan_qty, p.process_name, p.seq_no;这个视图把工单和工序的报工汇总在一起报表 SQL 从几百行降到几行。注意视图里的LEFT JOIN保证没有报工的工序也能显示出来MAX(r.report_time)取最后报工时间。视图不存数据查询时实时计算适合数据量在百万级以内的场景。如果数据量再大就要考虑物化视图或定时汇总表。5.2 给报工接口加一个幂等键车间网络不稳定操作工可能连点两次报工按钮或者前端超时重试导致重复报工。我习惯在报工接口加一个幂等键由前端生成一个 UUID每次报工带上。后端用 Redis 或数据库唯一索引去重。// 幂等校验示例 public void reportWithIdempotent(String idempotentKey, ReportRequest request) { Boolean success redisTemplate.opsForValue() .setIfAbsent(report: idempotentKey, 1, 5, TimeUnit.MINUTES); if (Boolean.FALSE.equals(success)) { throw new BusinessException(请勿重复提交); } // 执行报工逻辑 productionReportService.report(request); }这段代码用 Redis 的setIfAbsent实现幂等键的过期时间设 5 分钟足够覆盖前端重试窗口。参数idempotentKey由前端生成每次报工请求唯一。如果 Redis 不可用可以退化成数据库唯一索引在报工记录表上加一个idempotent_key字段唯一约束。这个改造在 QCADOO 开源版里没有但车间网络环境复杂加上会省很多对账的麻烦。5.3 验证改造是否生效的检查清单改完代码别急着上线按这个清单过一遍报工数量超计划时接口返回错误码前道工序未完工时后道报工被拒绝同一幂等键第二次提交返回“请勿重复提交”并发报工测试用 JMeter 开 10 个线程同时报同一工单最终数量不超过计划报表视图查询结果和原始表汇总一致。这些检查做完基本能覆盖车间里最常见的翻车场景。我自己接手开源 MES 的习惯是先跑通一条工单的全流程再把状态流转和数量校验的代码读一遍最后补上幂等和并发锁。QCADOO 的代码结构不算复杂但车间业务逻辑的边界条件很多开源版本不可能全照顾到。你把它当成一个半成品按自己产线的规矩去补它就能用起来。希望帮到你。本文还有配套的精品资源点击获取