这么多年接手的工厂信息化项目里,车间生产管理这块是最容易"看着简单、做起来绕"的。业务方通常会先丢过来一句"就是管一下工单、设备、物料嘛",等真正开始梳理流程才发现:一个工单从下达到完工要经过排产、领料、报工、质检好几道手,中间任何一环状态对不上,后面统计报表全是错的。这篇文章我就以一套基于SpringBoot+Vue的工厂车间管理系统为例,把整个项目的业务模块设计、技术选型理由、数据库核心表结构、后端接口实现要点、前端页面落地方式,以及最后打包部署上线遇到的坑完整拆开讲一遍。无论是拿去做毕业设计、课程设计,还是想给中小型工厂做一套实用的管理工具,这套思路和源码结构都可以直接参考复用。
1. 车间管理系统到底在管什么:业务模块与数据流转
很多初学者拿到这类项目第一反应是"不就是几个增删改查页面嘛"。如果你真这么想,做出来的东西大概率只是看起来能跑,但完全没法在真实车间里用。因为车间管理系统本质上不是CRUD堆砌,而是一条完整的数据流转链路:计划驱动工单,工单驱动领料和生产,生产产生报工记录,报工结果触发质检,质检合格才允许成品入库,整个过程里设备和人员都在产生跟生产相关的关联数据。
1.1 核心业务模块拆解
一套比较完整的工厂车间管理系统,通常至少要覆盖下面这些模块:
- 工单管理:这是绝对的核心。业务人员创建生产工单,指定产品、数量、计划开始和结束时间,工单下面还可以拆成多道工序,每道工序对应不同的车间、设备和操作人员。工单状态一般要经历:待下发→已下发→生产中→已完工→已关闭。
- 生产计划与排程:在工单创建之前,需要有一个简单的计划层面逻辑:根据销售订单或者库存预警生成生产计划,再由计划拆解为具体工单。小型系统里这步可以弱化为在工单上直接关联计划单号。
- 物料与库存管理:车间生产要消耗原材料,完工后要产出成品。所以系统里必须有物料档案、物料出入库记录,并且要和工单联动:工单下发后可以生成领料单,完工后可以生成产品入库单。
- 设备管理:设备台账、设备状态(运行/停机/维修)、设备点检和维修记录。设备状态直接关联到工单工序的执行——设备停机时不应该允许该工序报工,这一点是车间系统的特色逻辑。
- 质量检验:报工完成后生成待检记录,质检员录入检验结果(合格/不合格/返工),不合格的工单不能进入入库环节,或者进入返工流程。
- 人员管理:工人信息、班组划分、工序报工时记录操作人,方便后续做工时统计。
- 生产看板与统计报表:用图表展示今日工单数量、设备状态分布、完工率、良品率等。这块通常是领导最关心的页面,也是项目答辩时最容易出彩的地方。
1.2 数据是怎么流转起来的
画不出数据流图,就不要急着建表。我一般会先理清楚一条主链:生产计划(或直接下发)→ 生成工单 → 工单下发生成领料需求 → 仓库发料生成出库记录 → 各工序报工 → 完工生成质检任务 → 质检合格生成成品入库记录 → 工单关闭。
这条链路串起来之后,模块之间的关系就很清晰了。比如"工单详情"页面不只是显示工单表那一行数据,它要同时把这道工单关联的领料记录、工序进度、报工记录、质检结果都聚合展示出来。这也是为什么后端接口不能只是单表查询,需要有多表关联聚合的能力。
1.3 系统的功能边界:什么该做,什么不该做
做这类管理系统最容易犯的毛病是"贪大求全"。一个小型工厂可能就几十个人,你非要上复杂的APS高级排程、MES对接设备PLC采集,那是给自己挖坑。作为一套可以落地并且能讲清楚的设计,我更建议控制好边界:
- 生产计划可以做得简单些,能手工创建和维护计划、由计划一键生成工单,这个程度就够用。
- 工序报工可以用最直接的方式:操作工在电脑或平板上选择工单和工序,录入完成数量,系统记录时间和人员。
- 不建议在初版就做复杂的条码/RFID对接,先把核心数据流转做通,后续扩展并不难。
提示:不管最终做多少模块,工单、物料、设备、质检四个模块必须有,因为它们是车间管理的"骨架"。
2. 技术栈选择与版本搭配:为什么是SpringBoot+Vue+MyBatis+MySQL
这套技术栈现在几乎是Java全栈项目的标准配置,但"标准"不等于"无脑选"。每个组件解决什么问题、替换成别的行不行,心里要有数。
2.1 SpringBoot解决的是"快速构建可靠后端"的问题
SpringBoot的核心价值是自动配置和起步依赖。以前用SSH(Spring+Struts+Hibernate)搭一个能跑的项目,光写各种XML配置就要折腾一两天,SpringBoot把这一切摊平了:引入spring-boot-starter-web,一个内嵌Tomcat就起来了;引入mybatis-spring-boot-starter,数据源和SqlSessionFactory的配置就自动完成了。
这套项目我建议用SpringBoot 2.7.x版本,不要直接用3.x。原因是3.x底层是Spring Framework 6和Jakarta EE命名空间,很多老教程、网上资料里的javax.*包代码直接复制会报错,对新手和做课程设计的人来说折腾成本高。2.7.x配合JDK 8或者JDK 11,生态最稳定,资料也最好查。
2.2 为什么前端选Vue而不是别的
Vue的上手曲线比React平滑,中文文档和社区资源丰富,而且Vue的双向数据绑定让"表单收集、表格展示"这类后台管理页面的开发效率特别高。
版本上,Vue 2.7是Vue 2的最后一个大版本,Element UI配套成熟稳定;Vue 3 + Element Plus则是当前的主流方向。如果你是从零开始做新项目,我更推荐直接上Vue 3 + Element Plus + Vite,原因很简单:Vite的本地启动速度比Webpack快一个量级,而且Vue 3的Composition API写业务代码更清晰。不过要注意,Vue 3生态里有些组件库的坑比Vue 2多,比如部分第三方插件还没完全适配,如果用到的功能比较简单,问题不大。
2.3 MyBatis:SQL可控性比ORM的"自动"更重要
JPA(Hibernate)的优点是开发快,但缺点是你很难精确控制SQL,尤其到了多表关联、动态查询条件多、SQL需要针对索引优化的场景,JPA要么写JPQL,要么写原生SQL,体验很割裂。MyBatis则是"SQL你写,映射它做",既保留了SQL的灵活性,又省掉了传统JDBC里ResultSet手动封装的那堆重复代码。
在这个项目里,工单列表的多条件分页查询、工单详情聚合查询、统计报表的按天/按状态分组统计,都是SQL灵活性的典型场景。用MyBatis写动态SQL,<where>、<if>、<foreach>几个标签配合,查询逻辑一目了然。
2.4 MySQL在中小型工厂场景下完全够用
很多人纠结要不要上PostgreSQL或者Oracle,其实对于车间管理系统这种典型的联OLTP系统,数据量级通常就是几万到几十万条工单记录,MySQL InnoDB引擎的性能、事务支持(ACID)和主从复制能力完全覆盖需求。唯一要注意的是建表时统一使用utf8mb4字符集,否则存不了生僻字和特殊符号。
2.5 版本搭配参考表
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | SpringBoot 2.7.x对JDK8支持最稳 |
| SpringBoot | 2.7.x | 避免3.x的javax/jakarta命名空间切换问题 |
| Vue | 3.x + Vite | 新项目首选,Vue2.7也可但不再建议新开 |
| UI组件库 | Element Plus | 后台管理系统最成熟的组件库 |
| MyBatis | 3.5.x + mybatis-spring-boot-starter 2.3.x | 保持SpringBoot版本兼容 |
| MySQL | 5.7 或 8.0 | 8.0默认认证插件要留意驱动版本 |
| JDBC驱动 | mysql-connector-j 8.0.x | 对应MySQL 8.0 |
3. 数据库设计决定项目上限:核心表结构与字段规划
我可以直接说一句可能得罪人的话:这个项目能不能拿高分、能不能真正跑通业务,七成看数据库表设计,三成看代码。很多代码写得很漂亮的项目,因为表设计里状态字段没规划好、关联关系没理清,最后做出来的功能怎么改都别扭。
3.1 核心表清单
按上面的业务模块,一套完整的库表大致需要这些:
| 功能域 | 表名 | 核心字段 |
|---|---|---|
| 用户权限 | sys_user / sys_role / sys_menu | 账号、角色、菜单权限 |
| 基础资料 | product(产品) / customer(客户) | 产品编码、规格、单位 |
| 生产计划 | production_plan | 计划单号、产品ID、计划数量、交期 |
| 工单管理 | work_order | 工单号、计划ID、产品ID、数量、状态、时间 |
| 工单工序 | work_order_process | 工单ID、工序名称、排序、计划工时 |
| 物料库存 | material / material_stock | 物料编码、库存量、安全库存 |
| 领料与入库 | material_record | 类型(领料/退料/入库)、关联工单ID、数量 |
| 设备管理 | device / device_maintenance | 设备编号、状态、维修记录 |
| 生产报工 | work_report | 工单ID、工序ID、报工数量、报工人、时间 |
| 质量检验 | quality_check | 工单ID、报工ID、检验结果、检验员、不良数 |
| 生产看板 | (用视图或接口聚合) | 无需单独建表 |
3.2 关键表设计解析:以work_order为例
工单表是整个系统的心脏,字段设计直接影响上下游所有模块的联查效率:
CREATE TABLE `work_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '工单编号,业务唯一', `plan_id` bigint(20) DEFAULT NULL COMMENT '来源生产计划ID', `product_id` bigint(20) NOT NULL COMMENT '产品ID', `order_quantity` int(11) NOT NULL COMMENT '计划生产数量', `completed_quantity` int(11) DEFAULT '0' COMMENT '累计完工数量', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0待下发 1生产中 2已完工 3已关闭', `priority` tinyint(4) DEFAULT '1' COMMENT '优先级:1普通 2紧急', `plan_start_time` datetime DEFAULT NULL, `plan_end_time` datetime DEFAULT NULL, `actual_start_time` datetime DEFAULT NULL, `actual_end_time` datetime DEFAULT NULL, `create_by` varchar(32) DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_status` (`status`), KEY `idx_product_id` (`product_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='生产工单表';几个容易踩坑的细节:
状态字段用tinyint,不要用varchar。用数字做状态的好处是排序、过滤、统计都方便,而且前后端用统一的枚举类映射含义,比到处写死字符串"已下发"强得多。我见过有人用"已下发""生产中""已完成"这种中文直接存库,后来报表统计都靠LIKE匹配,改一次状态描述全表都要UPDATE,那酸爽谁经历谁知道。
完工数量单独冗余一个字段。completed_quantity可以从work_report表实时SUM出来,但工单列表页每次都要聚合查询,数据量大了很吃力。冗余这个字段,每次报工时累加更新,性能和逻辑都简单很多。这就是典型的"用空间换时间"。
唯一键用业务编号而不是自增主键。order_no设置UNIQUE KEY,因为多系统对接、Excel导入工单时,业务编号才是唯一身份。
3.3 工序与报工记录怎么挂
work_order_process表记录工单拆出的工序步骤,work_report表记录每一道工序的实际报工。这组关联是整个生产进度追踪的基础。
设计时有个关键点:报工记录上除了关联work_order_id,一定要把process_id也带上。否则一个多工序工单完工后,你想统计"哪个环节产能消耗最大"根本查不出来。另外,报工表要独立记录report_user和report_time,这不仅是操作留痕,也是后续算计件工资的数据来源。
3.4 物料的领用与入库:一个字段区分方向
物料流水表(material_record)一般就两个方向:出库(领料/退料出)和入库(采购入库/成品入库/退料入)。我用一个record_type字段区分,再通过关联ID指向不同的上游单据。这样一个表通吃所有库存变动,月底对账时一张SQL就能拉出所有流水。
CREATE TABLE `material_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `material_id` bigint(20) NOT NULL COMMENT '物料ID', `work_order_id` bigint(20) DEFAULT NULL COMMENT '关联工单ID', `record_type` tinyint(4) NOT NULL COMMENT '1领料出库 2退料入库 3采购入库 4成品入库', `quantity` decimal(12,2) NOT NULL COMMENT '变动数量,出库为负数,入库为正数', `record_time` datetime NOT NULL COMMENT '变动时间', `create_by` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_material_id` (`material_id`), KEY `idx_work_order_id` (`work_order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物料出入库流水表';关于数量字段,有个建议:库存类字段能用decimal(12,2)就不要用int,因为很多原材料按重量、长度计量,会有小数。省这一步,后面做单据的时候哭着改字段类型的滋味不好受。
3.5 扩展:如果想加多租户/多工厂
如果系统要卖给多个工厂用,或者集团下有多个厂区,建议在核心业务表(工单、物料流水、报工)上预留一个factory_id字段,所有查询默认带上这个租户条件。加这一个字段的成本很低,但这是你从"单机项目"走向"商用产品"的关键一步。
4. 后端实现:SpringBoot项目结构、鉴权、动态SQL与事务
后端部分我按"项目怎么分层、接口怎么设计、核心功能怎么落地"三个角度来讲。这套项目的后端代码结构长这样:
src/main/java/com/example/mes ├── controller # 接口层,只做参数接收和结果返回 ├── service # 业务逻辑层,事务控制在这层 ├── mapper # MyBatis数据访问层,接口+XML ├── entity # 数据库实体类 ├── dto # 前端交互对象(VO/Query对象) ├── common # 统一返回体、异常处理、枚举 ├── config # 配置类(WebMvc、CORS、拦截器) └── utils # JWT工具、日期工具等4.1 统一返回体与全局异常处理
所有接口统一返回Result<T>结构,前端拿到数据后不需要各种判断,省掉大量重复代码:
{ "code": 200, "message": "操作成功", "data": { } }配套地,用@RestControllerAdvice做全局异常处理。业务异常直接抛BizException,校验异常自动转为400返回,数据库异常统一转为500并记录日志。这块是很多课程设计项目做得最差的地方——异常处理散落在每个Controller里,try-catch满天飞,改一个逻辑要动三个地方。
4.2 JWT登录鉴权:token怎么发、怎么验
不推荐用Session,因为前后端分离后前端可能部署在独立端口,Session跨域处理麻烦,而且Vue打包后的静态资源一般走Nginx,后端接口在另一个域名或端口,Cookie跨域是坑。用JWT最简单:
用户登录成功后,后端生成一个有效期为24小时的token,里面带上用户ID和角色编码,返回给前端。前端每次请求在HTTP头里带Authorization: Bearer <token>。后端用一个拦截器统一校验token,解析出用户身份后放行。
具体实现上,我会用一个JwtUtil类负责生成和解析token,然后在WebMvcConfigurer里注册一个HandlerInterceptor处理校验。要注意:拦截器要放行登录接口、静态资源(如果前后端混合部署)、swagger文档路径,其他接口全部走校验。
提示:JWT有个常见误区是"把用户密码写进payload"。token里只需要用户ID和角色,不要放敏感信息,因为payload只是Base64编码,不是加密。
4.3 工单分页查询:MyBatis动态SQL实战
工单列表是系统最核心的查询接口,查询条件可能有:工单号模糊匹配、产品ID、状态、计划开始时间范围。用MyBatis的动态SQL写这个分页查询,是体现"为什么选MyBatis"的最佳例子:
<select id="selectWorkOrderPage" resultType="com.example.mes.dto.WorkOrderDTO"> SELECT wo.id, wo.order_no, wo.order_quantity, wo.completed_quantity, wo.status, wo.priority, wo.plan_start_time, wo.plan_end_time, p.product_name FROM work_order wo LEFT JOIN product p ON wo.product_id = p.id <where> <if test="query.orderNo != null and query.orderNo != ''"> AND wo.order_no LIKE CONCAT('%', #{query.orderNo}, '%') </if> <if test="query.productId != null"> AND wo.product_id = #{query.productId} </if> <if test="query.status != null"> AND wo.status = #{query.status} </if> <if test="query.startTime != null"> AND wo.plan_start_time >= #{query.startTime} </if> <if test="query.endTime != null"> AND wo.plan_end_time <= #{query.endTime} </if> </where> ORDER BY wo.plan_start_time DESC </select>分页用PageHelper插件,引入依赖后在Service层直接PageHelper.startPage(pageNum, pageSize),紧接着的查询自动拼接LIMIT,返回的PageInfo里自带总条数和总页数,前端表格的分页组件直接对接就好。
这段XML有几个细节:0 1 != ''的判断是防控制返回模板,一定要记得擦除那部分(模板页内无需输出)——直接返回纯净的模板页内容。 `标签自动处理多余AND,首条条件不成立也不会出现SQL语法错误,这是MyBatis动态SQL最方便的地方。
4.4 报工事务:库存扣减和工单进度必须一起成功
车间报工这个动作涉及三个数据变更:新增work_report记录、累加工单的completed_quantity、可能触发物料流水(产成品入库)。这三步要么全成功,要么全失败,必须放在同一个事务里。
@Transactional(rollbackFor = Exception.class) public void reportWork(WorkReportRequest request) { // 1. 校验工单状态是否为生产中 // 2. 校验报工数量不超过剩余待完工数量 // 3. 插入work_report记录 // 4. 累加工单completed_quantity,到达计划数量后置为已完工 // 5. 生成成品的物料入库流水 }这里有个业务规则值得注意:超量报工必须拦截。一不留神操作工多报了数量,后面成本统计和库存账面就全乱了。校验逻辑写在Service层,基于乐观锁或者直接在UPDATE语句里带条件,保证并发下也不会超量。
4.5 报表统计:一个SQL搞定生产看板数据
生产看板需要展示今日工单数、完工率、良品率,这些数据如果每一个都单独查一次库,代码啰嗦性能也差。我更推荐写几个聚合SQL,比如:
SELECT COUNT(*) AS total_order, SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) AS finished_order, ROUND(SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS finish_rate FROM work_order WHERE create_time >= #{todayStart} AND create_time < #{tomorrowStart}以及按设备状态分组统计:
SELECT status, COUNT(*) AS cnt FROM device GROUP BY status这类SQL配合一个DashboardController,一次性返回多个统计指标给前端看板组件,前端ECharts拿到数据直接渲染,效果很好。
5. 前端落地:Vue3工程搭建、Axios封装与核心页面实现
前端这部分,我按从搭建到核心页面开发的顺序来讲。Vue项目的工程化程度直接影响开发效率,很多人卡在"环境装好了但页面怎么写都别扭",多半是基础配置没做踏实。
5.1 工程结构:用Vite搭建Vue3项目
用Vite创建项目只需一条命令:
npm create vite@latest mes-web -- --template vue生成后在项目里安装核心依赖:
npm install element-plus axios vue-router pinia echarts工程结构建议保持清爽:
src ├── api # 每个模块的接口请求函数统一放这里 ├── assets # 静态资源 ├── components # 通用组件(分页表格、弹窗表单等) ├── layout # 后台主框架布局(侧边栏+顶栏+内容区) ├── router # 路由配置 ├── store # Pinia状态管理(用户信息、token) ├── utils # axios封装、工具函数 └── views # 页面组件5.2 Axios封装:拦截器统一处理token和错误
Axios不封装直接用,每个页面里写重复的请求配置,后期要改baseURL能把人改疯。我通常在utils/request.js里做一个统一实例:
import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:自动带上token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器:统一处理业务错误和登录过期 request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res.data }, error => { if (error.response && error.response.status === 401) { ElMessage.error('登录已过期,请重新登录') router.push('/login') } else { ElMessage.error('网络异常,请稍后重试') } return Promise.reject(error) } )关键在baseURL设置/api:开发环境通过Vite的代理把/api转发到后端地址,生产环境通过Nginx把/api反向代理到后端服务。这样前后端分离部署不存在跨域问题,代码里也不用写死IP地址。
5.3 路由守卫:未登录不能进后台
后台系统的路由必须做登录校验:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path !== '/login' && !token) { next('/login') } else { next() } })多角色系统还可以在路由meta里加roles字段,做基于角色的菜单过滤。比如管理员看到"用户管理"菜单,普通操作工只能看到"工单报工"和"我的任务"。这一步实现起来不难,但对项目的完整度很加分。
5.4 工单管理页面:列表、筛选、弹窗表单的三件套
后台管理页面写多了会发现套路非常固定:页面顶部是搜索条件区,中间是数据表格,右侧或弹窗里是详情/编辑表单。工单管理页也不例外。
我习惯把"列表页+表单弹窗"拆成三个组件:WorkOrderList.vue(页面容器)、WorkOrderSearch.vue(搜索条件)、WorkOrderFormDialog.vue(新增/编辑弹窗)。这样页面逻辑清晰,后续加字段改动范围小。
表单校验是Element Plus的强项,比如工单创建时产品必选、数量必须大于0、计划时间不能为空,写好rules规则后,表单提交前调用formRef.validate()即可。手写校验逻辑又慢又容易漏,这个便宜一定要占。
5.5 生产看板:ECharts让数据"说话"
看板页面建议至少做四个图表模块:今日工单状态饼图、近7天完工量柱状图、设备状态分布图、车间良品率折线图。ECharts的用法很直接:先init一个DOM容器,再setOption传入配置项,数据部分从后端接口获取后填入series即可。
一个容易忽略的细节:图表容器在页面刚渲染时可能宽度为0,导致图表显示不正常。解决办法是在nextTick里初始化图表,或者给容器设置固定高度。另外,页面切换时记得调用chart.dispose()释放实例,否则内存会持续上涨。
5.6 权限控制的前端配合:按钮级权限
光有菜单级权限还不够,比如"工单关闭"按钮只有车间主管能点。我的做法是在登录时把用户权限标识列表存入Pinia,页面里用自定义指令v-permission控制按钮显隐:
const permission = { mounted(el, binding) { const required = binding.value const userPermissions = useUserStore().permissions if (!userPermissions.includes(required)) { el.parentNode.removeChild(el) } } }这样在模板里写<el-button v-permission="'workorder:close'">关闭工单</el-button>,没有权限的用户压根看不到这个按钮,配合后端的接口权限校验,双重保障。
6. 打包部署与上线踩坑:从本地跑到服务器稳定运行
项目开发完只是第一步,真正部署到Linux服务器上跑起来,你会发现坑一个接一个。这部分我单独拎出来讲,是因为"能跑"和"上线稳定跑"完全是两码事。
6.1 后端打包:Maven跳过测试
后端项目在服务器上打包时,我习惯先本地执行:
mvn clean package -DskipTests在application-prod.yml里把数据库地址改成服务器地址,然后上传jar包到服务器:
nohup java -jar mes-server.jar --spring.profiles.active=prod > logs/app.log 2>&1 &这里有个前车之鉴:日志文件一定要单独指定,并且定期切割。否则时间长了可能要看你系统磁盘,虽然数据量不大,但前期系统还是需要留意一下。生产环境上线前建议在application.yml里显式配置日志级别和输出路径,我用logback按天切割,保留30天,这个配置花十分钟,后面排查问题会感激自己。
6.2 前端打包:路由模式与刷新404问题
前端构建:
npm run build生成dist目录后上传到Nginx的html/mes-web目录。这里最大的坑是:如果用Vue Router的history模式,直接访问/workOrder这种二级路由刷新会报404,因为Nginx找不到对应的物理文件。
解决方案是在Nginx配置里加一个try_files指令:
location / { root /usr/share/nginx/html/mes-web; index index.html; try_files $uri $uri/ /index.html; }这样所有前端路由都回退到index.html,由Vue Router接管路由解析。
6.3 Nginx反向代理解决跨域
前端代码里所有请求baseURL是/api,Nginx配置把/api转发到后端服务的实际端口:
location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }注意proxy_pass结尾的斜杠:有斜杠表示把/api前缀去掉后转发,比如/api/workOrder/list转发到后端变成/workOrder/list,和后端Controller的RequestMapping保持一致就行。没有斜杠则是保留完整路径转发,两种写法对应不同的后端接口设计,千万别混。
6.4 MySQL连接常见坑:时区和SSL
SpringBoot连接MySQL8.0最容易报的两个错:The server time zone value 'Öйú±ê׼ʱ¼ä'(时区问题)和SSL connection error。在JDBC连接串上加上参数一次性解决:
url: jdbc:mysql://127.0.0.1:3306/mes_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueuseSSL=false是测试环境省事,生产环境如果数据库和服务器在同一内网,也可以关掉SSL减少开销。allowPublicKeyRetrieval=true是MySQL8.0配合caching_sha2_password认证方式时的常见参数,不加连不上。
6.5 上线后的系统检查清单
服务器部署成功后,不要急着交付,按这个清单过一遍:
- 数据库备份策略是否配置?建议每天凌晨自动备份,保留7天。
- 后端日志有没有接入logback按天切割?
- 前端页面有没有用
nginx -t验证过配置语法? - 服务器防火墙端口是否只开放了80/443和SSH?后端8080端口不要暴露公网。
- 如果有多台服务器或者以后要扩容,Redis要不要引入?当前阶段可以不用,但代码里缓存逻辑最好预留接口。
7. 扩展思路:这套系统还能往哪个方向升级
如果你的项目是用于毕设或者想作为产品原型继续迭代,下面几个方向的扩展性价比很高:
引入Redis缓存:工单列表热点数据、产品基础资料的查询频率很高,用Redis缓存可以显著降低数据库压力。SpringBoot集成Redis相当简单,引入spring-boot-starter-data-redis,把字典数据、产品列表做一次缓存即可。但要注意缓存和数据库的一致性,写入操作要同步更新缓存或设置合理过期时间。
引入EasyExcel做导入导出:真实工厂里Excel操作是逃不掉的。工单批量导入、物料库存导出盘点表,用EasyExcel可以轻松实现。这块功能在答辩展示时效果很好,因为直观解决了"工厂办公日常"的实际问题。
引入WebSocket做实时看板:生产看板目前是打开页面才请求数据,如果要做到"工位报工后看板自动刷新",可以引入WebSocket,后端在报工事务提交后推送一条消息,前端看板收到后重新拉取统计数据。这个功能能显著提升系统的"实时感",而且技术亮点也够。
引入工作流引擎(如Flowable)做审批流:如果车间的领料超量、工单变更需要走审批流程,可以集成Flowable。但我的建议是:除非业务上有硬性需求,否则用状态机加一张审批记录表完全够用,工作流引擎的学习和维护成本对小项目来说偏重了。
我在实际做这类项目时最深的体会是:技术永远是为业务兜底的,真正让这个系统能被车间师傅用起来的关键,是"数据流转逻辑顺不顺"。一个工单从下发到完工,每一步操作的人在系统里走的路径,和他在车间里实际干活的动作越一致,系统就越容易被接受。所以拿到这套源码后,我建议你先别急着改代码,而是把表结构和工单流转状态图理清楚,后面加功能、改需求都会顺手很多。