基于SpringBoot+Vue的工厂工单管理系统设计实现 📅 发布时间:2026/9/16 4:28:13 👁 浏览次数: 工厂里的工单流转很多中小型制造企业到今天还停在纸单和Excel阶段。一份派工单从车间写到办公室再转给维修班和质检组中间经历签字、拍照、口头提醒效率低不说查个历史记录能翻半天柜子。所以当我准备做工厂作业工单管理系统时技术选型几乎是定死的后端SpringBoot、前端Vue、数据库MySQL。这套组合在Java技术栈里的生态太成熟了文档多、轮子全、人才也好招做成一个前后端分离的Web系统既能覆盖工单创建、派单、执行、验收的完整闭环又能给车间主任和管理层提供实时看板。标题里的编号“hx0680”你可以理解为项目代号这套系统做出来的实际效果是让工单从生成到归档全程可追踪、可统计、可复盘。这篇博文我打算照着实际项目的落地顺序来写先讲清楚工单管理系统到底要管什么、模块怎么拆再分别从后端、前端、部署三个层面把核心实现讲透最后把我踩过的坑整理成排查速查表。适合正在做SpringBootVue毕设的学生、准备进制造企业做信息化的初中级开发以及想把自己工厂的纸质工单流程搬到系统里的实施人员参考。1. 项目整体设计与功能拆解1.1 工单管理的核心需求分析做系统之前先把车间里的真实场景走一遍。工厂里的工单类型很多常见的有生产任务单、设备维修单、质量异常处理单、保养计划单不同行业叫法不一样但流程骨架是一致的有人发起、有人派活、有人干活、有人验收最后留下记录供以后查询和统计。所以我把这个系统的用户拆成四个角色提交人发起工单的人通常是产线操作工或质检员他看到设备异响或者发现不良品率偏高填写工单并提交。调度员工单处理的核心角色负责审核提交上来的工单判断应该派给哪个班组设置优先级和期望完成时间。执行人接到工单后去现场处理的作业人员处理完成后填写处理结果、上传现场照片、标记工时和物料消耗。管理员负责系统基础数据的维护比如用户账号、角色权限、班组信息、工单类型字典同时能查看全流程的统计报表。明确了角色工单的状态流转自然就清晰了。一个工单从出生到归档至少要经历这些状态待审核 - 待派单 - 待接单 - 进行中 - 待验收 - 已归档 \- 驳回 - 待审核重新提交这个状态机是整个系统的核心。很多初学者在做这类系统时喜欢用状态字段直接硬编码在接口里写一长串if/else结果上线后需求一变动就容易改出Bug。我建议一开始就建一个工单状态枚举类把每个状态的合法流转规则定义清楚后面所有业务方法都围绕这个状态机来写代码会干净得多。这部分后面细说。1.2 技术选型为什么是SpringBoot Vue这个组合放到今天来看依然是最稳妥的选择之一。SpringBoot的核心价值在于“自动装配”它把Spring家族里繁琐的XML配置全部变成了约定俗成的starter依赖一个main方法就能启动整个Web服务内置的Tomcat也免去了单独部署容器的麻烦。对于工厂内部系统这种以CRUD为主、业务规则相对清晰的场景SpringBoot的模板化开发效率非常高。前端选Vue则是因为它对中小型系统特别友好。Vue的响应式数据绑定和组件化开发让工单列表、表单、看板这些复用度高的界面可以抽成独立组件哪个页面需要就直接引用。配合Vue Router做页面路由、Pinia或Vuex做状态管理再接入Element Plus之类的组件库两三天就能把管理后台的骨架搭出来。后端、前端之外还有几个必选组件组件作用选型理由MySQL主数据库存工单、用户、角色、操作日志等结构化数据免费、稳定、维护成本低Redis缓存热点数据存登录token、工单统计结果读写快支撑看板和会话管理ActiveMQ消息队列处理工单状态变更后的通知发送和SpringBoot集成简单比引入Kafka轻量得多Nginx部署时做静态资源服务和API反向代理解决前后端分离的跨域和访问入口问题这套组合的好处是每一层都有大量现成案例可以查遇到问题不用自己瞎猜。就算你之前没接触过消息队列或者Redis花个半天看看官方入门文档也能在项目里用起来。1.3 工程结构与代码分层规划我的建议是前后端分成两个独立工程放在同一个父目录下用Git分开管理。这样部署时各自构建互不影响团队协作时职责也更清晰。后端用标准的Maven多模块或单模块分层都可以小型系统单模块分层就够不必为了结构好看强行拆多模块。典型的包结构长这样com.factory.workorder ├── controller // 接口层接收请求参数返回统一响应体 ├── service // 业务层处理具体业务逻辑 │ └── impl ├── mapper // 数据访问层基于MyBatis-Plus或MyBatis ├── entity // 数据库实体 ├── dto // 数据传输对象比如查询条件、表单提交对象 ├── vo // 视图对象返回给前端展示的数据 ├── config // 配置类Redis、跨域、拦截器、消息队列 ├── common // 公共类统一返回结果、状态码、工具类 └── enums // 枚举工单状态、工单类型、删除标志等前端用Vue CLI或者Vite创建工程后在src下按模块分组src ├── api // 接口请求封装按业务模块建文件比如workOrder.js ├── assets // 静态资源图片、样式 ├── components // 公共组件上传、富文本、工单状态标签 ├── router // 前端路由配置登录守卫在这里做 ├── store // 全局状态管理用户信息、菜单权限 ├── views // 页面视图登录、工单列表、看板、用户管理 └── utils // 工具函数请求封装、日期格式化分层清晰之后前后端联调才不会手忙脚乱。后端的接口路径、请求方式、参数含义在写代码之前先用Swagger或者接口文档定下来前端照着文档调后端照着文档实现两边并行开发不阻塞。2. 后端核心实现与关键细节2.1 SpringBoot工程搭建与基础配置创建工程很简单用IDEA的Spring Initializr直接生成就行。这里要重点提醒一句SpringBoot版本选择不要一味追新工厂类项目求的是稳定。当前主流稳定版本是2.7.x很多第三方starter对接文档也以2.x为主。折腾过3.x的朋友应该深有体会从javax包名改成jakarta很多老教程的例子直接跑不起来报错定位起来相当头疼。等我踩过几次版本坑之后结论就是除非有必须用新特性的需求否则2.7.x够你用的。项目生成后配置文件至少包含三块内容数据源、Redis、文件上传相关配置。核心的application.yml配置样例server: port: 8080 servlet: context-path: /api spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/workorder?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: yourpassword redis: host: localhost port: 6379 database: 0 servlet: multipart: max-file-size: 10MB max-request-size: 50MB mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl jwt: secret: y0ur-s3cr3t-k3y-xxxxxx expire-hours: 24这里解释几个配置的关键点。数据源URL里serverTimezoneAsia/Shanghai是必须的MySQL 8.x驱动默认时区是UTC不设置的话系统时间和数据库时间对不上工单的创建时间会差8小时。map-underscore-to-camel-case这个配置用于将数据库的下划线字段自动映射到Java的驼峰属性比如数据库字段create_time自动映射到createTime省去一堆TableField注解。文件上传大小限制也要提前设好否则工单现场照片传到一半就报超出限制错。2.2 数据库设计工单主表、明细表与字典表数据库设计直接决定后面写代码的难易。工单管理系统至少要有这几张核心表用户表、角色表、工单主表、工单操作记录表、字典表。工单主表的字段设计我列一个常用版本字段名类型说明idbigint主键建议雪花算法ID不用自增work_order_novarchar(32)工单编号格式如WO年月日流水号titlevarchar(200)工单标题简要描述问题typeint工单类型关联字典表priorityint优先级1普通 2加急 3紧急statusint当前状态关联枚举类submitter_idbigint提交人IDhandler_idbigint执行人IDdispatcher_idbigint派单人IDplan_start_timedatetime计划开始时间plan_end_timedatetime计划完成时间actual_end_timedatetime实际完成时间descriptiontext问题描述result_desctext处理结果描述reject_reasonvarchar(500)驳回原因is_deletedtinyint逻辑删除标记工单操作记录表非常关键它记录工单每一次状态变更谁在什么时间把工单从待审核改成了待派单备注了什么内容。这张表的价值在于事后审计谁在哪个环节耽误了时间一查记录就清楚。工单管理看起来是管工单本质上是管流程和管事。工单编号的生成我推荐用Redis的incr命令来做每天一个key比如workorder:no:20250329当天每创建一个工单就自增1编号格式WO20250329001。天然防并发重复而且第二天自动从0开始。2.3 权限认证与用户登录工厂系统的权限做到角色级别基本够用不需要很复杂的RBAC资源权限设计。我的方案是JWT来实现登录状态管理配合Spring Boot拦截器做token校验。登录流程是这样用户提交用户名密码后端校验通过后生成一个JWT token里面带上用户ID和角色编码再把token存一份到Redis里并设置过期时间。前端把token存到localStorage每次请求在请求头里带Authorization: Bearer token。后端拦截器校验token的签名和有效期再把用户信息放到ThreadLocal里供后续业务方法随时取当前操作人。不直接用纯粹的JWT而要多存一份Redis是为了解决“注销登录”的问题。JWT在有效期内是天然无法撤销的如果你只靠JWT本身的过期时间用户点了退出登录token还是有效的遇到工单接口被拿出去扫就很被动。存到Redis后退出登录或者管理员踢人时直接删掉Redis里的key就行。拦截器里除了校验token还有一个容易被忽略的细节在线用户管理。工厂里经常需要查看“现在都有谁登录在系统里”特别是遇到安全事件要追踪时一张在线用户表能把人找出来。2.4 工单状态机的后端落地前面提了状态机这里说实现。我建议在枚举类里把工单状态和流转规则集中定义public enum WorkOrderStatus { PENDING_AUDIT(0, 待审核), PENDING_DISPATCH(1, 待派单), PENDING_ACCEPT(2, 待接单), IN_PROGRESS(3, 进行中), PENDING_ACCEPTANCE(4, 待验收), REJECTED(5, 已驳回), ARCHIVED(6, 已归档); public final Integer code; public final String desc; WorkOrderStatus(Integer code, String desc) { this.code code; this.desc desc; } }然后在Service层写一个状态变更方法所有状态修改都走这个方法而不是在Controller里直接调updatepublic void changeStatus(WorkOrder workOrder, WorkOrderStatus targetStatus, String operatorId, String note) { WorkOrderStatus current WorkOrderStatus.of(workOrder.getStatus()); if (!isAllowed(current, targetStatus)) { throw new BusinessException(不允许从 current.desc 变更为 targetStatus.desc); } workOrder.setStatus(targetStatus.getCode()); workOrderMapper.updateById(workOrder); workOrderLogMapper.insert(...); }这样做的直接好处是非法状态流转在Service层就被拦截前端就算绕过按钮直接调接口后端也不会放行。工单系统最怕的就是状态乱了套工单还没派单就跳到已完成报表数据直接成浆糊。派单是整个系统里值得多花心思写的一个点。最简单的方案是调度员手动选择执行人但更好的做法是支持按技能匹配自动推荐。工单类型比如电气维修、机械维修、焊接匹配执行人的技能标签再结合当前待处理工单数量算出负载把负载最低且技能匹配的执行人排在候选列表最前面。这套推荐逻辑不用做得很重一个SQL查询加排序就能实现。2.5 缓存、消息队列与通知Redis在我的系统里承担了几个具体任务缓存工单看板的统计数据、存储JWT、生成每日工单编号。其中工单统计看板是查询最频繁的接口车间大屏可能每5秒刷新一次如果每次都去MySQL里做聚合查询数据库压力会比较大。我的做法是每个整点算一次统计数据放Redis看板接口直接读缓存同时管理员手动刷新的时候强制更新一次这样统计数字顶多慢一两分钟车间管理完全能接受。ActiveMQ的主要用途是解耦通知发送。产生工单状态变更后系统需要发站内信通知相关负责人传统做法是在工单Service里同步调用消息服务一但消息服务出问题或者变慢工单主流程也跟着卡住。引入ActiveMQ后工单Service只负责把状态变更事件发布到队列通知模块异步消费消息各干各的互不拖累。小系统中不一定非要上消息队列但如果未来要接企业微信通知、短信通知、邮件通知队列的价值就会非常明显改一个消费端的逻辑发短信还是发邮件都只影响通知模块自身。3. 前端核心实现与页面落地3.1 Vue项目创建与开发环境配置前端我建议直接用Vite创建Vue3项目比Vue CLI更快生态也更好。创建命令很简单npm create vitelatest workorder-web -- --template vue cd workorder-web npm install npm install vue-router4 pinia element-plus axios echarts dayjs开发环境必须要配置代理否则前端请求后端接口的时候会碰到跨域问题。在vite.config.js里加上export default defineConfig({ server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这里解释一下为什么不用后端开启CORS来省事。开发阶段用代理前端请求的地址和后端是同源的浏览器没有跨域限制接口调试更顺。生产阶段用Nginx反向代理统一入口同样不依赖后端处理CORS。后端把CORS放开的话安全配置又要额外小心自己能控制的方案才是好方案。npm安装依赖慢是国内开发者的常态如果第一次npm install等了十分钟还没装完把镜像切到国内源或者手动指定npmmirror registry能省下不少时间。但注意lock文件里锁的是依赖版本换镜像不会改变版本这个可以放心。3.2 路由与权限控制前端路由的核心职责是维护页面跳转规则和页面访问控制。我这里用了两种方式结合第一种是登录守卫在router.beforeEach里判断访问除登录页外的任何页面之前先检查本地有没有token没有就重定向到登录页。这个判断放在前端是体验层面的拦截真正的安全校验还是后端拦截器在做两层各自职责不同。第二种是菜单权限。管理员登录显示用户管理、字典配置菜单普通操作工登录只显示我的工单、待办中心。实现方式是在动态路由里按角色注册路由表或者在渲染侧边菜单时用v-if判断v-permission自定义指令。对于工单管理系统菜单级权限就够了不用精细到按钮级避免过度设计。Vue Router还有一个踩坑点使用createWebHistory模式时生产部署刷新页面会出现404因为Nginx只认到了静态文件路径没有把请求转发给index.html。解决办法是Nginx里配try_files $uri $uri/ /index.html;这个后面部署部分会再提。3.3 工单列表、筛选与状态标签工单列表是系统里最核心的页面。我的设计思路是一页包含四块筛选区、操作按钮区、表格区、分页器。筛选区支持按工单类型、状态、优先级、创建时间段、负责人等条件组合查询状态用下拉框默认选中“进行中”而不是所有状态因为车间主管打开列表最关心的是正在处理中的事。状态标签组件是我前端里抽得比较成功的一个公共组件。不同状态配不同颜色待审核灰色、待派单橙色、进行中蓝色、待验收青色、已归档绿色、已驳回红色。状态枚举是核心的全局概念后端返回给前端的是状态码前端拿到状态码之后映射为标签颜色和文案。前后端的状态枚举如果各写一份共用同一个后端接口来返回状态列表就不会出现两边定义不一致的情况。表格操作列的按钮我也会根据当前行状态动态显示。比如待审核的工单显示“审核”和“驳回”“派单”按钮则不出现。这样用户可以直观地感知到当前能做的操作误点的概率也大大降低。3.4 表单校验、文件上传与看板工单表单是提交人和调度员都要用的页面差别只是字段不同。提交人填的是问题描述、设备编号、紧急程度调度员填的是派单结果、计划时间、备注。这里我拆成了两个组件而不是一个页面里通过v-if切换一堆字段避免代码越写越长。表单校验用的Element Plus自带的规则机制重点校验工单标题不能为空、描述至少10个字、计划完成时间不能早于当前时间。这些规则前端校验一遍后端在接收DTO时再用注解校验一遍两层都做才能防住前端被绕过的情况。现场照片上传用的是Element Plus的Upload组件后端提供一个通用的文件上传接口文件存在服务器的本地目录。要特别注意生产级改造时文件不能存在应用服务器的磁盘上因为单机磁盘早晚会满而且应用重新部署时文件容易丢。更好的方案是把文件存OSS或者MinIO对象存储但这对于小型毕设和内部系统不是必须的本地目录加路径存库也能用。看板页用ECharts做了三个图工单类型分布饼图、近7日处理趋势折线图、班组平均处理时长柱状图。这几个图表的数据在后端封装好前端接到数据直接塞给ECharts的option配置。如果硬件条件允许还可以接一个大屏页面把这三个图放大到电视上给车间循环播放管理效果非常直观。4. 系统部署与常见问题排查4.1 前后端分离部署的核心步骤开发阶段前后端分开跑部署到生产环境时就需要放在一起了。我的推荐拓扑是一台服务器上装Nginx和Java服务前端构建后的dist目录扔到Nginx的html目录后端jar包用systemd或Docker守护进程运行Nginx把/api开头的请求反向代理到后端的8080端口。前端构建npm run build # 产物在 dist 目录后端构建mvn clean package -DskipTests # 产物在 target/workorder-1.0.0.jarNginx关键配置server { listen 80; server_name your-domain-or-ip; root /opt/workorder/dist; index index.html; location / { 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; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }两个关键点try_files解决Vue History路由刷新404的问题proxy_pass末尾的斜杠表示把/api前缀透传给后端后端接口路径本身是/api开头所以这里不能去除前缀。4.2 常见问题排查实录速查表症状可能原因解决办法前端登录成功后再次刷新又回到登录页localStorage里的token丢失或路由守卫误判检查登录后是否localStorage.setItem刷新时先读token再初始化store接口请求404后端context-path与Nginx代理路径不一致统一约定后端context-path/apiNginx里location /api/对应前端请求接口跨域开发环境没配vite代理或生产环境直接请求了后端地址开发配vite proxy生产配Nginx反代前端请求相对路径/apinpm install安装依赖极慢默认使用官方源配npm config registry为国内镜像后重试上传图片后接口504上传大小超过默认限制或带宽问题后端设multipart大小Nginx设client_max_body_size数据库中文乱码连接串没加characterEncodingutf8或表是latin1编码连接串加useUnicodetruecharacterEncodingutf8建库用utf8mb4微服务时间差8小时数据库连接没设置时区URL加serverTimezoneAsia/Shanghai4.3 安全与性能优化建议这套系统上线前我建议至少做四件安全上的事。第一密码不要明文存数据库用BCrypt加密。SpringSecurity自带BCryptPasswordEncoder不用自己研究哈希算法直接集成就行。第二接口层做好参数校验防止SQL注入和恶意构造数据。MyBatis-Plus默认的wrapper拼接条件可能带来注入风险能用Param注解传参就不要拼字符串。第三敏感接口做权限校验。工单归档、删除、状态强制修改这类高危操作不仅要登录还要检查操作人的角色是管理员或调度员防止操作工顺手把工单删了。第四SpringBoot项目的heapdump泄露问题要上心。曾经有安全通报提到SpringBoot的Actuator在生产环境暴露了heapdump接口攻击者可以下载堆内存快照从中提取密码、token等敏感信息。解决办法是生产环境不要暴露Actuator端点或者用management.endpoints.web.exposure.include限制只开放必要的端点。性能上来说工单管理系统的表数据量在百万级以内时MySQL加上合适的索引就足够应付。工单列表按create_time排序查询给create_time加个普通索引状态筛选频繁给status加索引。再大才需要考虑分库分表但对工厂内部系统来说那属于几年后的事情现在不必预支复杂度。5. 后端一些容易忽视的开发细节很多人做SpringBoot项目启动起来能跑就以为万事大吉其实有几个细节会在联调阶段反复折腾人。我在这里把最典型的几个展开讲。第一个是统一响应体。我的所有接口返回结构是固定的code状态码、message提示消息、data业务数据。前端封装的axios会拦截所有响应code为200才把data返回给调用方否则弹出message提示。这个约定一定要在开工第一天定好否则每个人写接口返回格式不一致前端解析逻辑就要写一堆兼容分支。空数据返回空数组不返回null单个实体不存在返回code404不返回null这些边界约定写清楚联调效率直接翻倍。第二个是MyBatis-Plus的字段自动填充。创建时间、更新时间这两个字段每个表都有。如果每次insert、update都手动set很容易漏掉数据库默认值又不会自动回填到Java对象里。用MyBatis-Plus的MetaObjectHandler做一个公共处理器实现insertFill和updateFill方法自动往实体的createTime和updateTime里填当前时间从此这两个字段不用再手动管。第三个是全局异常处理。BaseException、业务异常、系统异常要分开处理。Controller里不要写try/catch统一用一个RestControllerAdvice全局异常处理器来兜底。业务异常返回业务提示比如“工单当前状态不允许此操作”参数校验异常返回字段级别的提示系统异常记日志并返回模糊的统一提示不要把堆栈暴露给前端看。第四个是接口幂等设计。创建工单接口如果前端因为网络抖动重复提交了两次数据库里就会多出一条重复工单。解决思路是前端提交时生成一个requestId后端在Redis里以requestId为key做setnx重复请求直接拦截。对制造业场景来说重复工单不只是多一条数据还可能导致同一台设备被重复派修生产计划被搞乱所以创建类接口的防重很有必要。6. 实际研发中的体验与踩坑记录最后聊点做这套系统时的真实体会想到哪儿写到哪儿。版本依赖是最大的隐性时间杀手。新开项目时IDEA里Spring Initializr默认推荐的SpringBoot版本往往会比较新我顺手就选了最新的3.1.x结果公司的内网私服上没有对应版本的starter依赖拉包就拉了半天。后面还是退回到2.7.x一切太平。搜索引擎上能查到的教程、踩坑记录大部分都基于老版本。做业务的程序员不是搞基础框架的没有必要永远站在新版本的最前线。Vue项目打包后布局异常这个热词我当年也搜过。现象是开发环境一切正常build完之后某些页面错位、字体图标显示不出来。原因几乎总是静态资源路径问题Vite的base配置默认是/如果你把前端部署在域名根路径下还好一旦部署到类似http://ip:8080/workorder/这种子路径下资源就全部404了。解决方法是构建时指定base: /workorder/或者干脆让Nginx把前端服务在根路径下就不需要改base。还有个前端问题Vue路由参数传一个对象或数组时直接放进router.push的参数里刷新页面后就拿不到了因为URL只能存在字符串。正确做法是用query参数序列化或者配合Pinia把复杂对象存到store里。工单列表跳转到详情页传一个工单ID就够了不要传整个工单对象过去保持URL简洁也方便分享。从时间安排来看如果把整套系统控制在两个月内做完我建议把大部分精力放在工单核心流程上。很多初学者容易被新技术吸引花两周时间研究集成OnlyOffice、接视频流m3u8播放、接地图API这些都是锦上添花的功能做得再多工单流转的核心逻辑不过关系统照样没法用。核心流程做得扎实基础页面整洁代码分层清晰答辩和评审讲出来的价值感完全不一样。最后再分享一个小技巧工单看板这部分可以在后端把统计数据接口设计成按维度查询的综合查询让前端传groupBy参数而不是前端分别调类型统计、趋势统计、人员统计三个接口。这样接口更少数据库聚合一次完成等数据量上来之后做缓存也更加集中。这个设计思路不仅仅适用于工单系统任何带报表统计的后台都可以参考。