简介:苦糖果MES系统是一款面向国内离散制造型中小企业的开源免费生产执行管理系统,采用B/S架构,基于J2EE技术栈开发,旨在解决其在生产计划排程、工序管控、物料追溯、设备状态采集等核心环节缺乏专业且低成本数字化工具的现实痛点。资源包为ZIP格式,大小49.03MB,包含系统源码、数据库脚本、部署说明及基础配置文档等关键内容,虽文件总数未提供,但结构覆盖后端服务模块、前端界面资源与典型工厂数据样例,便于快速本地部署与二次开发。已有1885人学习下载,体现了中小企业对轻量级国产MES方案的迫切需求。用户可直接获取完整可运行系统框架、适配离散产线的业务模型设计思路、开箱即用的SQL初始化脚本,以及结合多年智造行业经验沉淀的工艺路线与工单管理逻辑,显著降低MES选型与实施门槛。
1. 苦糖果MES系统:不是Demo玩具,而是能跑通车间报工、工序流转、设备点检的开源B/S生产执行系统
你见过把“开源MES”当PPT讲的团队吗?我见过三次——一次在客户现场,他们用GitHub上clone下来的某Java版MES前端页面刷屏,后台连数据库都没连上;一次在技术交流会,有人演示“开源MES支持扫码”,结果扫码后弹出404;还有一次,是某厂IT主管拿着“免费开源”宣传页来问:“能不能直接替换掉我们那套用了八年的老系统?”——答案是:不能,除非你愿意花两周配通基础数据、改三处硬编码、重写两段设备对接逻辑。
苦糖果MES系统不是这样。它从第一天就定位为「可落地的轻量级生产执行系统」:B/S架构意味着产线工人用Chrome就能报工,不用装客户端;开源(MIT协议)意味着你能看到所有SQL语句、所有API路由、所有设备通信心跳包的解析逻辑;免费不是噱头——它不卖License、不设用户数上限、不锁高级功能。它解决的是中小制造企业最痛的三个场景:① 班组长每天手工抄录20张纸质报工单;② 车间主任查某订单进度要等IT导3小时Excel;③ 设备异常停机后,维修记录和备件消耗还在微信里传。
适合谁?不是想搭个Demo应付领导检查的IT助理,而是真有5条产线、30台CNC/冲压/组装设备、日均200+工单、且愿意花3天部署+2天调参的生产信息化负责人。它不承诺“一键上线”,但承诺“每一步都有日志、每一处都能改、每一个接口都带示例”。
提示:这不是SaaS,没有账号注册页;这不是低代码平台,没有拖拽表单;这是用C# + Vue3 + SQL Server(也支持MySQL)写的完整MES,源码包里含数据库初始化脚本、IIS部署说明、OPC UA设备接入样例、甚至还有车间看板的Vue组件源码。
2. 部署前必读:为什么选它?不是因为“开源”,而是因为它的B/S结构真能扛住产线并发
2.1 B/S架构不是口号:它用SignalR实现实时工单推送,而非轮询
很多所谓“B/S MES”本质是“伪B/S”:前端用Vue/React,但后端API全是HTTP短连接,工人同时扫码报工时,服务器瞬间涌进上百个GET请求,IIS队列爆满。苦糖果MES不同——它用ASP.NET Core SignalR建立长连接通道,工单下发、设备状态变更、质检结果回传全部走WebSocket。
验证方法很简单:打开/signalr/hubs(调试模式下),用浏览器控制台执行:
const connection = new signalR.HubConnectionBuilder() .withUrl("/hub/workorderhub") // 工单中心Hub .build(); connection.start().then(() => { console.log("SignalR已连接"); connection.invoke("SubscribeToOrder", "SO20240501-001"); // 订阅工单 });注意:此Hub路径在
Startup.cs中定义,WorkOrderHub.cs里明确写了[Authorize],必须先登录获取JWT Token才能订阅。信号通道不暴露给未认证用户,这是它和某些“开源MES Demo”最根本的安全分水岭。
2.2 开源≠无维护成本:MIT协议下,你能改什么、不能动什么
MIT协议允许商用、允许修改、允许闭源,但苦糖果MES的源码结构决定了你的修改边界:
- ✅ 可安全修改:
Controllers/WorkOrderController.cs(报工逻辑)、Views/Device/PointCheck.vue(点检表单)、Data/SqlScripts/InitDB.sql(建库脚本) - ⚠️ 修改需谨慎:
Services/Device/OPCUAService.cs(OPC UA连接池管理)、Models/Entities/WorkOrder.cs(实体类,关联12张表) - ❌ 禁止删改:
Common/Constants.cs(所有业务码值,如WorkOrderStatus.InProgress=2)、Migrations/20231001120000_Init.cs(EF Core迁移文件,删了会导致数据库升级失败)
我一般会做三件事:
- 把
Constants.WorkOrderStatus里的中文描述抽成资源文件(zh-CN.resx),方便多语言; - 在
WorkOrderController.Create()里加一行_logger.LogInformation($"新工单{model.OrderNo}创建,来源:{model.Source}"),追踪数据入口; - 把
OPCUAService.Connect()的超时时间从30秒改成15秒(产线设备响应慢,30秒太长)。
2.3 免费≠零成本:硬件与环境的真实开销清单
| 项目 | 最低要求 | 生产建议 | 为什么关键 |
|---|---|---|---|
| 数据库 | SQL Server Express(10GB限制) | SQL Server Standard(启用AlwaysOn) | Express版不支持作业调度,无法自动清理7天前的设备日志 |
| Web服务器 | IIS 10 + .NET 6 Runtime | IIS 10 + .NET 6 Hosting Bundle + URL Rewrite Module | 缺URL重写模块,Vue路由history模式会404 |
| 前端访问 | Chrome 90+ / Edge 95+ | Chrome 115+(启用WebAssembly) | 车间平板常驻Chrome,旧版本不支持<webusb>设备直连API |
| 设备对接 | OPC UA服务器(如Kepware) | OPC UA服务器 + 本地证书(.pfx) | 开源版默认用匿名连接,产线OPC UA服务器强制证书认证时会失败 |
提示:部署前务必运行
PowerShell ./scripts/CheckEnv.ps1——它会检测IIS是否启用WebSocket、.NET Runtime版本、SQL Server实例名是否为MSSQLSERVER(非命名实例需改appsettings.json中的ConnectionStrings:Default)。
3. 核心功能落地:从创建第一个工单到设备点检闭环
3.1 创建工单:不是填表,而是绑定BOM、工艺路线、设备组
苦糖果MES的工单(WorkOrder)不是独立存在,它必须关联三要素:
- BOM版本:在
/bom页创建BOM,注意Version字段必须唯一,否则工单生成时会报错BOM version conflict; - 工艺路线(Routing):在
/routing页定义工序序列,关键字段是OperationCode(如OP001)和WorkCenterCode(如WC-CNC-01); - 设备组(WorkCenter):在
/workcenter页配置,Type选Machine或AssemblyLine,Capacity填理论日产能(单位:件/班)。
创建工单的POST请求体长这样:
{ "OrderNo": "SO20240501-001", "ProductCode": "P-00123", "BomVersion": "V2.1", "RoutingCode": "RT-P00123-2024", "Quantity": 100, "DueDate": "2024-05-10T00:00:00", "Priority": 1 }注意:
RoutingCode必须存在于Routing表中,且该Routing下的每个Operation必须有对应的WorkCenter。如果OP001指向WC-CNC-01,但数据库里没这个设备组,工单创建会返回400 Bad Request并提示Work center WC-CNC-01 not found。
3.2 工人扫码报工:Vue前端如何调用扫码枪并校验工单状态
车间用的不是手机APP,而是Windows平板+USB扫码枪。扫码触发逻辑在src/views/production/Report.vue:
<template> <input ref="scanInput" type="text" v-model="scanValue" @input="onScan" class="sr-only" autofocus /> </template> <script> export default { data() { return { scanValue: '' }; }, methods: { onScan() { if (this.scanValue.length > 8 && this.scanValue.includes('SO')) { // 假设扫码格式:SO20240501-001|OP001|EMP001 const [orderNo, opCode, empId] = this.scanValue.split('|'); this.$http.post('/api/workorder/report', { OrderNo: orderNo, OperationCode: opCode, EmployeeId: empId, Quantity: 1, Status: 'Completed' }).then(res => { this.$message.success(`报工成功:${res.data.Message}`); this.scanValue = ''; // 清空输入框,准备下一次扫码 }).catch(err => { this.$message.error(`报工失败:${err.response.data.Message || '网络错误'}`); }); } } } }; </script>关键点:
@input事件比@change更及时,扫码枪输出是连续字符流,v-model绑定后立即触发;sr-only类让输入框视觉隐藏但键盘焦点可用;autofocus确保页面加载后光标自动落在输入框——工人无需点击屏幕。
3.3 设备点检闭环:从计划生成到异常上报的完整链路
点检不是填表,而是带状态机的流程:
- 计划生成:系统每天00:00根据
PointCheckPlan表生成当日点检任务,存入PointCheckTask表,Status=Pending; - 工人执行:扫码进入
/pointcheck/task/123,页面显示CheckItems(如“主轴温度≤65℃”、“冷却液液位≥80%”),每项有Pass/Fail/NA按钮; - 异常上报:选
Fail后弹出DefectReportModal,必须填写DefectCode(下拉选,来自DefectCode字典表)和PhotoUrl(调用平板摄像头拍照); - 自动派单:提交后,系统调用
DefectService.CreateMaintenanceOrder(),生成维修工单并推送给指定维修组。
后端关键逻辑在Controllers/PointCheckController.cs:
[HttpPost("report")] public async Task<IActionResult> Report([FromBody] PointCheckReportDto dto) { var task = await _context.PointCheckTasks .FirstOrDefaultAsync(x => x.Id == dto.TaskId); if (task.Status != PointCheckTaskStatus.Pending) // 只允许报未开始的任务 return BadRequest("Task is not in Pending status"); // 更新任务状态 task.Status = PointCheckTaskStatus.Completed; task.CompletedAt = DateTime.UtcNow; // 处理缺陷 if (dto.HasDefect) { var defect = new DefectRecord { TaskId = dto.TaskId, DefectCode = dto.DefectCode, PhotoUrl = dto.PhotoUrl, CreatedAt = DateTime.UtcNow }; _context.DefectRecords.Add(defect); // 自动创建维修工单 await _maintenanceService.CreateFromDefect(defect); // 此方法发SignalR通知维修组 } await _context.SaveChangesAsync(); return Ok(new { Message = "Report submitted" }); }血泪经验:
DefectCode必须提前在/admin/defectcode页录入,否则CreateFromDefect()会因外键约束失败;PhotoUrl上传路径默认是/uploads/defect/,需在IIS中为此目录开启写权限,否则SaveChangesAsync()会抛IOException。
4. 避坑指南:那些让部署卡在第三天的五个真实翻车现场
4.1 现象:IIS部署后首页空白,F12看Network全是404
原因:Vue Router用history模式,但IIS未安装URL Rewrite Module,或web.config里缺少重写规则。
解决:
- 下载并安装 IIS URL Rewrite Module ;
- 确认
web.config中有以下规则(位于<system.webServer><rewrite><rules>内):
<rule name="VueRouter" stopProcessing="true"> <match url=".*" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> </conditions> <action type="Rewrite" url="/" /> </rule>注意:此规则必须放在
<rules>节点最底部,否则会被其他规则拦截。
4.2 现象:扫码报工返回401 Unauthorized,但登录态正常
原因:ASP.NET Core JWT认证中间件未启用,或appsettings.json中Jwt:Key长度不足32字节(AES-256要求)。
解决:
- 检查
Startup.cs中services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)是否被注释; - 运行
openssl rand -base64 32生成32字节密钥,填入appsettings.json:
"Jwt": { "Key": "your-32-byte-base64-key-here==", "Issuer": "kugua-mes", "Audience": "kugua-mes" }提示:密钥不能含特殊字符如
+/=,若生成含这些字符,重新执行命令直到得到纯字母数字组合。
4.3 现象:设备点检照片上传失败,日志报Access to the path 'D:\kugua\uploads\defect\' is denied
原因:IIS应用池身份(默认ApplicationPoolIdentity)对uploads目录无写权限。
解决:
- 右键
D:\kugua\uploads→ 属性 → 安全 → 编辑 → 添加; - 输入
IIS AppPool\DefaultAppPool(应用池名需与IIS中一致); - 勾选
修改和写入权限 → 确定。
4.4 现象:OPC UA设备连接超时,日志显示Could not establish trust for endpoint
原因:产线OPC UA服务器启用了证书验证,但苦糖果MES默认用匿名连接。
解决:
- 在
appsettings.json中启用证书模式:
"OpcUa": { "UseCertificate": true, "CertificatePath": "certs/client.pfx", "CertificatePassword": "your-password" }- 将
.pfx证书文件放入/certs/目录(与wwwroot同级); - 确保证书私钥导出时勾选“导出私钥”,且密码不含中文或空格。
4.5 现象:工单状态不更新,SignalR连接正常但workorderhub无消息推送
原因:WorkOrderHub的SubscribeToOrder方法未正确注册,或前端未传Token。
解决:
- 检查
WorkOrderHub.cs中OnConnectedAsync是否调用await Groups.AddToGroupAsync(Context.ConnectionId, orderNo); - 前端连接时必须带Token:
const connection = new signalR.HubConnectionBuilder() .withUrl("/hub/workorderhub", { accessTokenFactory: () => localStorage.getItem('token') // 登录后存的JWT }) .build();注意:
localStorage.getItem('token')必须是完整JWT字符串,不能是Bearer xxx前缀。
5. 进阶技巧:用OPC UA实时数据驱动车间看板,绕过数据库轮询
5.1 为什么不用数据库查设备状态?延迟太高
车间看板要求“秒级刷新”,但SQL查询设备表(DeviceStatus)有天然瓶颈:
- 每次查询需走EF Core ORM层,生成SQL,网络往返;
- 若10台设备每秒查一次,数据库连接池迅速耗尽;
- 更糟的是,设备状态变更可能发生在两次查询之间,造成“假离线”。
苦糖果MES的解法是:让OPC UA服务器主动推送——设备状态变,OPC UA发DataChangeNotification,MES服务端监听并广播给看板前端。
5.2 实现步骤:四步打通OPC UA→SignalR→Vue看板
Step 1:在OPCUAService中订阅设备节点Services/Device/OPCUAService.cs里添加:
public async Task SubscribeToDeviceStatus(string nodeId) { var subscription = new Subscription(_session, 1000); // 1000ms刷新间隔 subscription.Notification += (s, e) => { foreach (var notification in e.NotificationMessages) { foreach (var item in notification.NotificationData.OfType<DataChangeNotification>()) { foreach (var value in item.MonitoredItems) { // value.Value是Variant类型,需转为bool var status = Convert.ToBoolean(value.Value.WrappedValue); // 广播给所有看板客户端 _hubContext.Clients.All.SendAsync("DeviceStatusChanged", new { NodeId = nodeId, Status = status, Timestamp = DateTime.UtcNow }); } } } }; await subscription.SubscribeAsync(new[] { nodeId }); // 如"ns=2;s=Device001.Status" }Step 2:前端看板页监听SignalR事件src/views/dashboard/Realtime.vue:
<script> export default { mounted() { this.initSignalR(); }, methods: { initSignalR() { this.connection = new signalR.HubConnectionBuilder() .withUrl("/hub/dashboardhub") .build(); this.connection.on("DeviceStatusChanged", (data) => { // 更新Vuex store或本地data const device = this.devices.find(d => d.NodeId === data.NodeId); if (device) { device.Status = data.Status; device.LastUpdate = data.Timestamp; } }); this.connection.start(); } } }; </script>Step 3:看板UI用颜色编码状态
<div v-for="device in devices" :key="device.NodeId" :class="['device-card', { 'online': device.Status, 'offline': !device.Status }]"> <h3>{{ device.Name }}</h3> <p>状态:{{ device.Status ? '运行中' : '已停机' }}</p> </div> <style scoped> .device-card.online { border-left: 4px solid #4CAF50; } /* 绿色 */ .device-card.offline { border-left: 4px solid #f44336; } /* 红色 */ </style>Step 4:避免SignalR消息风暴——加节流
若设备每秒发10次状态变更,前端会收到10次DeviceStatusChanged,导致UI频繁重绘。加节流:
// 在initSignalR中 let throttleTimer; this.connection.on("DeviceStatusChanged", (data) => { clearTimeout(throttleTimer); throttleTimer = setTimeout(() => { // 执行UI更新 this.updateDeviceStatus(data); }, 100); // 100ms内只处理最后一次 });从那以后我每次部署新产线看板,都强制走一遍OPC UA订阅测试:用UA Expert手动改设备节点值,看前端是否1秒内变色。如果延迟>2秒,立刻查
OPCUAService里的subscription.PublishingInterval是否被误设为5000(应为1000),而不是怪网络或IIS。希望帮到你。
本文还有配套的精品资源,点击获取