ARTICLE DETAIL

资讯详情

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

基于SpringBoot+Vue3的工厂车间管理系统全栈开发实践

基于SpringBoot+Vue3的工厂车间管理系统全栈开发实践 这套系统我前前后后做了三个多月从第一行代码到最终交付车间上线踩过的坑和总结的经验都在这篇里面。如果你正打算做工厂车间管理系统或者准备用 SpringBoot Vue3 这套技术栈接实际项目这篇文章应该能帮你省下不少试错成本。先把这个项目的基本盘说清楚这是一套前后端分离的工厂车间管理系统后端用 Java SpringBoot 提供 RESTful API前端用 Vue3 Element Plus 做界面数据持久层用 MyBatis 操作 MySQL 数据库。系统覆盖了工单管理、设备管理、物料管理、质量检验、生产报工和统计看板六大模块针对的是制造型企业车间日常管理场景解决纸质工单流转慢、生产进度不透明、质量数据无法追溯这些痛点。如果你是刚接触这类业务系统的开发者或者正在为毕业设计、公司内部项目找参考架构这篇文章会从需求拆解、数据库设计、核心功能实现、部署运维四个维度完整还原整个建设过程。里面所有代码思路和表结构都是我在实际项目中验证过的不是那种只能跑 demo 的教学代码。1. 项目整体设计与需求梳理1.1 工厂车间管理的真实痛点我接手这个项目时客户的车间还在用最原始的管理方式工单打印出来发到班组工人完成一道工序就手写勾选班组长每天下班前把纸质单据汇总成 Excel 发给生产主管。这带来几个非常典型的问题。第一生产进度严重滞后。管理层看到的报表永远是昨天的数据今天车间里哪个订单卡在哪个工序、哪台设备停机了只能靠电话和跑腿去问决策效率很低。第二质量问题无法闭环。一件产品在加工过程中如果出现了尺寸偏差纸质记录只能查到是哪道工序、哪个操作工但当时用的哪批原材料、哪台设备、哪些工艺参数这些关键信息都是缺失的。第三设备维护靠经验。哪台设备该保养了、哪台设备故障频率高完全依赖老师傅的记忆一旦人员流动设备档案就断了。所以这个系统在需求调研阶段就明确了三条主线让生产进度实时可视、让质量数据完整可追溯、让设备管理从经验变成数据驱动。这三个目标直接决定了后面每一个表的设计和每一个接口的粒度。1.2 功能模块怎么划分的系统按用户角色和使用场景拆成了六个核心模块每个模块解决一类具体问题模块核心功能解决的核心问题工单管理工单创建、工序流转、完工入库生产进度实时透明设备管理设备台账、点检保养、维修记录设备状态可监控物料管理领料、退料、库存变动物料消耗可追溯质量检验来料检、过程检、完工检质量数据闭环生产报工工序报工、工时记录计件工资有依据统计看板产量趋势、设备OEE、不良率管理决策有数据模块划分有个原则我特别想说一下不要一上来就想着做排产和 MES 级别的功能。车间管理系统的核心是先解决数据有没有的问题再谈数据怎么用。我的做法是先保证所有业务动作都有系统记录然后再叠加统计分析这样每个功能都能快速落地不会陷入大而全的泥潭。1.3 技术选型的实际考量技术栈定的是 SpringBoot 2.7 Vue3 MyBatis MySQL 5.7这套组合不是最潮的但绝对是最稳的。SpringBoot 的好处不用多说内嵌 Tomcat、自动配置、生态成熟招人好招遇到问题百度一搜一大堆。选 2.7 而不是 3.x是因为当时项目里要集成一些第三方库SpringBoot 3 的 Jakarta EE 迁移会让部分旧依赖出兼容问题而且 2.7 还在社区主流维护周期内。Vue3 这边用了 setup 语法糖加 Composition API配合 Element Plus 组件库开发效率和代码可维护性都比 Options API 时代提升了不少。MyBatis 在复杂报表查询和动态 SQL 上确实是强项车间管理系统里有大量多表联查和条件统计用 MyBatis 可以精细控制每条 SQL 的执行计划这点比 JPA 那种自动生成的 SQL 可控得多。MySQL 5.7 是客户现有的数据库版本生产环境迁移成本最低。2. 架构设计与数据库建模过程2.1 前后端分离的工程结构前端工程用 Vite 构建后端是标准的 Maven 多模块项目。前端目录结构划分大概是这样的src/ ├── api/ # 按模块封装的接口请求 ├── layout/ # 后台管理框架布局 ├── router/ # 动态路由配置 ├── stores/ # Pinia 状态管理 ├── views/ # 页面组件 │ ├── work-order/ # 工单管理页面 │ ├── equipment/ # 设备管理页面 │ ├── material/ # 物料管理页面 │ ├── quality/ # 质量检验页面 │ └── dashboard/ # 统计看板页面 └── utils/ # 工具类axios 封装、权限指令等后端结构我按业务模块分包而不是按技术层次分包com.example.factory ├── common/ # 通用类统一返回、异常处理、工具类 ├── config/ # SpringBoot 配置类安全、拦截器、跨域 ├── workorder/ # 工单模块controller/service/mapper/entity ├── equipment/ # 设备模块 ├── material/ # 物料模块 ├── quality/ # 质量模块 ├── report/ # 统计模块 ├── system/ # 用户、角色、权限模块 └── auth/ # 登录认证这种按业务分模块的结构在项目变大以后优势非常明显各模块之间边界清晰多人协作时 Git 冲突的概率也小。前端调接口的路径就对应后端的 controller 路径比如/api/workorder/前缀的接口都走工单模块。前后端交互这一层我用 Axios 统一封装了请求实例设置了请求拦截器和响应拦截器。请求拦截器里自动携带 JWT Token响应拦截器里统一解包后端返回的data字段遇到状态码非200的情况统一弹错误提示。这么做的好处是页面上调接口只需要关心业务代码不用每个接口都写一遍错误处理。2.2 核心表结构设计过程数据库设计是整个项目的基础我在动手写代码前花了大概一周时间梳理表结构。总共设计了 20 多张表这里挑几个核心的讲。工单表work_order是系统的中枢字段设计上除了基本的单号、产品、数量、计划时间关键是有个current_process_seq字段记录当前进行到第几道工序配合status字段实现工序流转。这个设计比单独维护一张工单状态表简单得多查询进度的时候直接定位到当前工序。CREATE TABLE work_order ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 工单编号, product_id BIGINT NOT NULL COMMENT 产品ID, plan_quantity INT NOT NULL COMMENT 计划数量, current_process_seq INT DEFAULT 1 COMMENT 当前工序序号, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待开工 1生产中 2已完工 3已取消, priority TINYINT DEFAULT 1 COMMENT 优先级 1普通 2紧急, deleted TINYINT DEFAULT 0 COMMENT 逻辑删除, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status_priority (status, priority) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT生产工单表;工单明细表work_order_process记录每道工序的安排和执行信息它和工单表是一对多关系。每个工单下挂多道工序每道工序有理论工时、计划开始/结束时间、实际报工时间、当前处理人。设备表和物料表的设计有几处细节值得注意。设备表我加了一个last_maintenance_date和next_maintenance_date后续通过定时任务扫描到期设备自动生成点检任务。物料表里加了库存安全阈值字段safety_stock库存低于阈值时系统自动预警。质量模块我设计了quality_inspection和quality_inspection_detail两张表。主表记录一次质检任务的基本信息比如对应的工单、工序、检验类型、检验结论和检验员明细表记录具体的检验项、标准值、实测值、测试结果。这样既能让一次检验包含多个项目也能单独追溯某一批产品的质量数据。2.3 几个容易忽略的设计细节状态字段能用 int 绝不用字符串。我这个项目里所有状态都维护在系统字典表里后端存数字前端通过字典接口映射成中文展示。好处是数据比较的时候用数值比较效率高后续加新状态只需要在字典表加一条记录不需要改代码。逻辑删除和唯一索引要搭配使用。比如工单编号本身是唯一的给order_no加了唯一索引之后就不能直接物理删除已经存在的记录否则下次创建相同编号会冲突。我的做法是加deleted字段做逻辑删除同时把唯一索引改成(order_no, deleted)联合约束这样同一个业务编号只有未删除的那条记录唯一。基线数据表统一加上create_by、create_time、update_by、update_time四个审计字段。这个习惯从第一个项目就养成了排查数据问题的时候真的能救命。有一次车间反馈工单数量不对通过create_by字段直接定位到是某个新来的文员重复导入了数据十分钟就解决了要没有审计字段得翻半天日志。3. 核心链路实现与联调复盘3.1 后端接口规范与基础封装所有接口统一返回一个结构{ code: 200, message: success, data: { } }对应的后端泛型类我把它简化为RT包含code、message、data三个字段。成功直接R.success(data)失败抛业务异常由全局异常处理器统一接管。全局异常处理器分成三层最上面兜底的ExceptionHandler捕获所有未处理异常返回固定错误码中间层处理BusinessException携带具体的业务错误信息最底层处理参数校验异常MethodArgumentNotValidException把校验失败的原因拼成可读的提示信息返回前端。分页查询统一用PageResultT封装包含total、pageNum、pageSize、records四个字段。MyBatis 的分页插件用 PageHelper在 service 层直接调用PageHelper.startPage(pageNum, pageSize)然后接着写查询语句返回结果自动带上总数。这里有一个坑PageHelper 的startPage只对紧接着的下一条 SQL 生效如果中间有别的查询语句执行分页就会失效所以一定要确保startPage紧跟查询方法调用。3.2 登录认证与权限控制登录流程是典型的 JWT Redis 方案。用户提交账号密码后端校验通过后生成一个 JWT Token 返回给前端同时把用户的权限标识列表缓存到 Redis设置过期时间。前端把 Token 存到本地存储每次请求在Authorization头里带上。后端的拦截器里先校验 Token 的签名和有效期再根据 Token 里的用户 ID 从 Redis 捞权限数据做接口级别控制。这里我特别想强调一个安全细节JWT 一旦签发在有效期内是无法主动作废的。如果用户修改了密码或者管理员冻结了账号旧的 JWT 在到期前依然有效。解决办法是 Redis 里存一份 Token 的黑名单每次请求的时候先查 Redis 看这个 Token 是不是已经被踢掉了。代价是多了一次 Redis 访问但对安全性的提升非常明显。前端权限控制分两级路由级的通过动态路由实现按钮级的通过自定义指令实现。路由守卫里判断用户是否登录、是否有访问该页面的权限没有权限直接跳转 403 页面。按钮级的权限用v-permission指令指令里检查用户权限列表里有没有指定的权限标识没有就移除该 DOM 元素。这样后端的接口校验是安全兜底前端的指令控制只是优化体验两层配合才是一个完整的权限闭环。3.3 生产报工接口的实现细节生产报工是车间里操作最频繁的功能也是最容易出现并发问题的场景。多个工人可能同时对同一个工单进行报工如果处理不好会出现重复计数、状态错乱、库存虚增等问题。报工接口的完整业务逻辑是这样的首先校验工单是否存在、状态是否合法接着校验当前报工的工序序号是否等于工单的当前工序然后插入报工记录更新工单的实际完成数量最后判断实际完成数量是否达到计划数量达到则推进工单状态到下一道工序或者完工。这里的核心逻辑用事务包裹保证数据一致性。防并发我用的是数据库层面的乐观锁更新工单状态的 SQL 带上条件判断UPDATE work_order SET actual_quantity actual_quantity #{quantity}, status CASE WHEN actual_quantity #{quantity} plan_quantity THEN #{targetStatus} ELSE status END WHERE id #{workOrderId} AND status #{currentStatus}UPDATE语句自带行锁两条并发的更新请求只会有一条成功另一条会因为条件不满足而返回影响行数为 0。Service 层根据返回的影响行数判断是否更新成功如果影响行数为 0 就直接抛异常提示工单状态已变更请刷新后再试。这种方案比代码里加同步锁靠谱得多因为生产环境部署多个实例的时候应用层的锁在单机内有效跨实例就失效了数据库的行锁才是天然的分布式互斥机制。前端报工页面为了减少用户重复提交除了在点击事件里做防抖之外最有效的一招是提交成功或失败都立即把按钮置为禁用状态等接口返回结果后再按业务逻辑重置。3.4 工单工序流转的前后端配合工序流转是整个系统里页面交互最复杂的部分。车间主任在工单详情页能看到完整的工序流程图当前工序高亮显示工人完成当前工序报工后系统自动点亮下一道工序。前端我用 Element Plus 的steps组件实现工单进度展示每一道工序对应一个步骤节点。工序流转的关键在后端如何自动推进。我的实现方式是报工接口里有一段推进逻辑当前工序完成数量达标之后先更新当前工序的状态为已完成、记录实际完成时间和工时再把工单的current_process_seq加 1并让下一步工序的状态变成进行中。这里每一步都要落库因为后面质量追溯的时候需要知道每一道工序的实际执行时间。还有一个细节是工单完工后的成品入库。完工推送逻辑里会在事务内同时写库存表和工单状态保证要么两个都成功要么都不成功。刚开始我用的是先更新工单状态再单独写库存结果有一次工单状态更新成功但库存写入失败查了半天才发现事务边界没包住两个操作。这种跨模块的事务一定要在同一个 service 方法里处理接口层拆成两个独立的事务必然会出现数据不一致的风险。3.5 统计看板的 SQL 聚合实践统计看板是管理层最看重的模块包括每日产量趋势、各产线完工率、设备 OEE 仪表盘、不良品 Pareto 图。后端有几个聚合查询的 SQL我分享一下写法思路。每日产量趋势的查询按天分组统计完工数量SELECT DATE(completion_time) AS stat_date, SUM(completed_quantity) AS total_quantity FROM work_order_process WHERE completion_time #{startDate} AND completion_time #{endDate} AND process_status 2 GROUP BY DATE(completion_time) ORDER BY stat_date设备 OEE 的计算则复杂一些需要同时考虑设备的时间利用率、性能效率和质量合格率。我在设备表里记录了每天的运行时间、故障时间、理论产能和实际产出然后通过三条聚合 SQL 分别计算出三个子指标在 Java 层把它们拼成最终的 OEE 值。数据库里存明细、应用层做计算这是处理复杂指标的好方式。前端看板用 ECharts 渲染页面挂载时请求一次统计数据然后用定时器每隔 30 秒刷新一次。这种方式实现简单、对服务器压力小车间大屏场景完全够用。比引入 WebSocket 实时推送更有性价比因为在大多数车间管理场景里分钟级别的数据延迟是完全可以接受的没必要为了实时性增加一整套消息推送的基础设施。4. 部署上线、性能优化与常见问题排查4.1 生产环境的搭建配置生产环境我这边用的是 CentOS 7 Nginx MySQL 5.7 Redis后端直接用 SpringBoot 打成的 fat jar 运行。这里有一个经常遇到的坑SpringBoot 的默认配置是为开发环境设计的部署到生产一定要显式指定运行环境。java -jar factory-system.jar --spring.profiles.activeprodapplication-prod.yml里配了生产环境的数据库连接、Redis 地址和日志级别数据库密码通过环境变量注入而不是硬编码在配置文件里spring: datasource: url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: ${DB_USERNAME} password: ${DB_PASSWORD}数据库连接串里的几个参数每个都是踩坑踩出来的。useSSLfalse不配的话 MySQL 8.0 会默认强制 SSL 连接导致报警告serverTimezoneAsia/Shanghai是解决时区差八小时问题allowPublicKeyRetrievaltrue是解决 MySQL 8.0 的 caching_sha2_password 认证方式在客户端二次连接时报错的问题。这些参数如果不提前配好程序跑起来经常会遇到莫名其妙的连接问题。前端打包直接用 Vite 的构建命令npm run build生成的dist目录放到 Nginx 的 html 目录下。Nginx 的配置里需要把/api前缀的请求反向代理到后端服务server { listen 80; server_name your-domain.com; location / { root /opt/factory/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files $uri $uri/ /index.html这行务必保留。前端路由用的 history 模式如果不配置这个规则用户访问某个子页面后刷新就会出现 404因为 Nginx 没有找到对应的物理文件。这个问题几乎每个用 history 路由的前端项目都会遇到提前配好省得后面手忙脚乱。4.2 MyBatis 使用中的几个经典坑MyBatis 虽然灵活但一些细节不注意真的会让人排查到崩溃。我把自己实际踩过的问题整理成一个清单。第一个问题是数据库字段下划线和 Java 驼峰命名的映射。MySQL 里字段习惯用user_nameJava 实体类习惯用userName如果不在配置文件里开启驼峰映射查询结果会是null。在application.yml里这样配mybatis: configuration: map-underscore-to-camel-case: true第二个坑是动态 SQL 里的逗号问题。用if标签拼接UPDATE语句的时候最后一个if条件不满足容易留下多余的逗号。我通常的做法是set标签会自动处理多余的逗号在set内的每个if项末尾统一加逗号MyBatis 会自动去掉最后一个逗号。这个特性只有在用set标签时才生效自己拼 SQL 就要非常小心。第三个坑是一级缓存和二级缓存导致的数据不一致。MyBatis 的一级缓存是 SqlSession 级别的默认开启在同一次会话内重复查询同样的 SQL 会直接返回缓存结果。在批量操作场景里这个问题特别致命循环里先查询再更新再查询拿到的还是更新前的缓存值。我的规范是涉及数据更新的地方一律不在同一次会话里缓存查询结果二级缓存默认关闭需要缓存的数据交给 Redis 做可控性更强。第四个坑是foreach批量插入的 SQL 语句长度。批量插入 500 条数据时生成的 SQL 可能会超过 MySQL 的max_allowed_packet限制需要分批插入或者调大 MySQL 的包大小。我这边最终选择每批 200 条实测稳定性和速度都比较理想。4.3 线上问题的排查手段项目上线初期压力不大出现了不少数据逻辑问题排查手段比较常规但很有效。最常用的方式是在后端配置 MyBatis 的 SQL 日志打印。生产环境的日志级别设成INFO但给 MyBatis 的 mapper 包单独设置DEBUG级别这样既能打印 SQL 又不会日志爆炸logging: level: com.example.factory.mapper: DEBUG排查慢 SQL 更直接的手段是打开 MySQL 的慢查询日志。在my.cnf里配置slow_query_log ON slow_query_log_file /var/log/mysql/mysql-slow.log long_query_time 2long_query_time 2表示执行时间超过 2 秒的 SQL 会被记录到日志文件。上线一段时间后查看慢查询日志发现有几个查询执行时间在 5 秒以上都是没有走索引导致的。解决方式是对高频查询条件建立联合索引。比如设备点检记录表经常按(device_id, check_date)查询就建对应联合索引工单列表通常按(status, plan_start_date)筛选就建这两个字段的联合索引。索引也不是越多越好每个索引都会拖慢写入速度只给高频查询建索引就够了。分页查询的深分页问题也要提前处理。当页码特别大的时候LIMIT 100000, 20这种查询 MySQL 需要扫描前面 10 万条记录再丢弃性能极差。我在报表模块的翻页改成了基于主键 ID 的游标分页查询上一页的最大 ID然后WHERE id #{lastId} LIMIT 20性能提升非常明显。4.4 前端联调的高频问题汇总前后端联调阶段遇到的问题类型比较集中整理一个速查表方便排查现象根本原因解决方案前端请求接口报跨域后端未配置 CORS 或配置错误后端配置跨域过滤器允许指定来源时间显示成 UTC 格式后端返回的 LocalDateTime 没格式化后端配置 Jackson 的日期格式统一为yyyy-MM-dd HH:mm:ss下拉框显示的是数字而不是文字枚举值没做字典翻译前端调用字典接口转换或后端直接返回格式化后的值登录后刷新页面就退出Token 存 sessionStorage 而非 localStorage改为 localStorage 持久化存储修改数据后页面不更新Vue3 的响应式对象被整体重新赋值丢失了响应修改对象属性而不是整体替换或者用reactive的赋值方法处理同一个表单提交两次前端未做防抖或者按钮没禁用量提交方法加防抖提交期间禁用按钮Vue3 响应式这块补充一下reactive创建的响应式对象如果直接Object.assign(form, newData)整体替换响应式连接会丢失。正确做法是逐字段赋值或者用Object.assign(form, newData)的方式原地修改属性const form reactive({ name: , quantity: 0 }) // 正确 Object.assign(form, response.data) // 错误 // form response.data4.5 系统上线前后的几个补充建议部署上线后有几点要特别注意。第一数据库一定要定期备份而且备份要验证可恢复性。我见过有人配置了自动备份结果半年后要恢复数据才发现备份文件是损坏的。备份脚本除了执行 mysqldump 之外一定要加一步检查备份文件大小是否正常的逻辑低于某个阈值就告警。第二用户权限要提前梳理清楚。车间管理系统的角色至少会有管理员、车间主任、班组长、操作工、质检员、设备管理员这几类每个角色能看哪些页面、能操作哪些按钮在开发阶段就要做好规划。等系统上线了再补权限前端路由、后端接口、数据库字典都要跟着改非常麻烦。第三操作日志表要有。谁在什么时间修改了哪个工单的状态、把数量从多少改成了多少这些审计数据在制造业企业里特别重要。我在工单、设备、物料三个核心模块里都加了操作日志记录统一写入一张日志表排查数据争议的时候直接翻记录。第四数据字典要提前规划好。状态、类型这些枚举值集中维护在数据库字典表前端通过接口拉取渲染下拉选项。车间后续调整工序名称、新增设备状态线上就能配置不需要改代码发版。5. 我对这套系统的一些体会项目上线运行了三个多月车间从最开始的不愿意用到现在每天早上班组长第一件事就是打开看板看今天的生产任务和昨日产量这个过程还是挺有成就感的。回顾整个开发过程最核心的经验是业务系统开发的技术难点其实不在技术栈本身而在于对业务逻辑的理解和抽象能力。SpringBoot Vue3 MyBatis 这套组合的每个技术点都有大量现成资料真正需要花心思的是工单状态怎么流转、并发报工怎么防重、质量数据怎么关联追溯。这些业务设计做扎实了系统才能经得起真实生产环境的考验。如果你打算在这个基础上继续扩展有几个方向可以考虑一是对接工业 IoT 设备通过 Modbus 或 OPC UA 协议实时采集设备运行参数让 OEE 的计算从人工填表变成自动采集二是引入工序级的条码追溯从原材料入库到成品出库全链路扫码实现一物一码的完整追溯链三是把排产功能做深从简单的优先级排序升级到考虑设备产能、物料齐套和交期约束的有限产能排程。最后再分享一个我个人的小习惯凡是涉及状态变更的接口文档里一定把状态流转图画清楚让前端同事知道什么状态下才能发起什么操作。前后端因为状态流转规则没对齐而产生的返工是我在这些项目里见过最多的资源浪费。这份文档我现在每个项目都会第一时间写实测可以省掉大量无效沟通。
返回列表