SpringBoot+Vue+MyBatis工作流管理系统源码深度解析
2026/9/9 22:41:54 网站建设 项目流程

企业级项目源码拿到手之后,最怕的不是代码复杂,而是不知道从哪里开始读、哪些模块是核心、哪些地方藏着坑。这套 SpringBoot + Vue + MyBatis + MySQL 的工作流管理系统,我前前后后完整过了一遍,从数据库表设计到前端流程设计器,从审批链路的流转逻辑到 MyBatis 动态 SQL 的写法,把关键点全部拆开整理成了这篇笔记。无论你是打算拿它做毕设、二次开发,还是想学习企业级项目的完整架构,这篇都能帮你省下大量摸索时间。

1. 项目全貌与技术选型底层逻辑

1.1 这套系统到底解决了什么问题

先说说这类"企业级工作流管理系统"的典型业务场景。我在实际项目里遇到最多的需求是:报销审批、请假流程、采购申请、合同会签、用章申请。这些流程有个共同点——都涉及多级审批、条件分支、驳回重提、转办委派。如果不用工作流引擎,纯靠业务代码硬写,每一类流程都要单独写状态机和审批逻辑,改一次流程就要改一遍代码,维护成本极重。

这个项目做的就是把这些流程能力通用化。核心思路是把"流程定义"和"业务数据"解耦,审批节点、流转条件、办理人都配置在流程定义里,业务系统只负责提交申请和接收审批结果。这样一来,新增一类审批流程基本不需要动 Java 代码,只改配置就行。对于想学习企业级项目结构的同学来说,这套系统最值得研究的不是某个具体的业务功能,而是它的抽象分层方式。

1.2 为什么是 SpringBoot + Vue + MyBatis + MySQL 这套组合

这套技术栈放在今天依然是中小型企业项目的主流选择,没有之一。SpringBoot 解决了 Spring 框架配置繁琐的问题,内置 Tomcat,打 jar 包就能跑,开发效率极高。Vue 在前端生态里上手曲线平缓,组件化开发方式配合 Element UI 这类组件库,后台管理系统的页面开发速度非常快。

MyBatis 在这个项目里的角色比较关键。它比 JPA 灵活,SQL 完全由开发者掌控,尤其在多表关联查询和复杂报表统计上,写原生 SQL 的效率和可控性都远高于自动生成的查询。MySQL 则是数据存储层的稳妥之选,配合 InnoDB 引擎的行级锁和事务支持,完全能承载几百人规模企业的并发审批场景。

1.3 项目核心模块划分

按我的理解,这套系统整体分五个模块:流程定义管理(流程图配置、节点属性设置)、流程实例管理(发起、挂起、终止)、任务中心(待办、已办、抄送)、审批操作(同意、驳回、转办、委托)以及系统管理(用户、角色、权限、部门)。这五个模块之间存在严格的依赖关系,只有先建好流程定义,才能发起流程实例;只有实例运行到某个节点,才会生成对应的待办任务。

这个依赖关系是整个系统的骨架,读源码的时候如果抓住这条主线,就不会在细枝末节里迷路。我后面会按这条主线逐层展开,把每一层的表结构、接口设计和核心代码逻辑都过一遍。

2. 数据库设计:工作流链路的数据地基

2.1 流程定义与节点模型设计

先看最核心的几张表。流程定义表(act_process_definition)保存的是流程的基本信息,比如流程编码、名称、版本号、定义 JSON 内容。流程节点表(act_process_node)记录每个节点属于哪个流程定义、节点类型是开始/审批/条件/结束、节点名称、审批人类型和审批人配置。

节点表的设计里有个细节值得注意:审批人配置这一列存的是 JSON 字符串,而不是外键关联用户表。这样设计的原因在于,一个节点的审批人可能是具体用户、指定角色、指定部门主管,甚至是发起人自选,如果用字段来穷举这些类型,表结构会非常僵化。存 JSON 的做法牺牲了一点查询性能,但换来了极大的灵活性,也是 Activiti、Flowable 等成熟工作流引擎普遍采用的方式。

2.2 流程实例与任务表设计

流程实例表(act_process_instance)记录一次具体的审批流程。发起人、流程定义 ID、当前节点 ID、流程状态(运行中/已通过/已驳回/已撤销)、发起时间、结束时间都在这里。任务表(act_task)则更细化,一条流程实例走到不同节点会生成多条任务记录,每条记录包含任务名称、办理人、任务状态、所属实例 ID。

这两张表是 1:N 的关系。设计上的关键点在于任务表必须冗余流程实例 ID 和当前节点 ID,理由是待办查询是最频繁的操作,如果每次都要通过关联查询去拿实例信息,数据库压力会翻倍。用空间换时间,在企业级应用里是常规操作。

2.3 审批记录表与抄送表

审批记录表(act_approval_record)是最能体现系统成熟度的一张表。每次审批动作都会插入一条记录,字段包括审批人、审批结果(同意/驳回/转办)、审批意见、审批时间、任务 ID、实例 ID。这张表既是审计追踪的依据,也是流程回溯的数据来源。

抄送表(act_cc_record)则独立于审批链路,用于记录知会信息。一个典型的场景是:财务审批通过后,需要抄送给出纳,但出纳不需要做审批操作,只是收到通知。把抄送和任务分开,可以避免抄送人误操作审批按钮,权限边界也更干净。

3. 后端核心功能实战拆解

3.1 流程定义解析与部署机制

流程定义的部署是这个系统最复杂的地方。前端流程设计器保存的是一份 JSON,结构大概是 nodes 数组和 edges 数组,nodes 里有 id、名称、类型、审批人配置,edges 里有 source、target、condition 表达式。

后端拿到这份 JSON 后,首先要做的是格式校验和节点合法性校验。比如开始节点只能有一个、结束节点必须存在、条件分支的条件表达式非空、所有边的 source 和 target 都必须对应存在的节点。校验通过后,JSON 内容存入流程定义表,节点数据解析后批量插入流程节点表。

这里必须注意流程定义的版本控制。实际业务中经常出现流程调整的情况,如果直接修改旧定义,处于运行中状态的流程实例就会产生混乱。所以流程定义表里有一个 version 字段,每次修改都生成新版本,已经发起的实例继续走旧版本,新实例才使用新版本。

3.2 发起流程与任务生成逻辑

发起流程的接口设计可以概括为三步。第一步创建流程实例记录,状态置为运行中。第二步解析流程定义 JSON,找到开始节点后续的第一个审批节点。第三步根据审批人配置解析出实际办理人,插入任务表。

审批人配置的解析是这个环节的难点。如果配置的是具体用户 ID,直接查询即可;如果配置的是角色,需要查角色和用户的关联表;如果配置的是"发起人的直属上级",则需要通过部门表查询上级关系。我在源码里看到这块用了一个策略模式,节点类型和审批人类型分别对应不同的解析器,新增审批人类型时只需要添加一个实现类,这种设计非常值得借鉴。

3.3 审批、驳回与流转控制

任务审批的接口设计是整个系统的核心逻辑所在。前端点击"同意"按钮后,后端执行的流程是:校验任务状态为待办、更新任务状态为已办、插入审批记录、查询当前节点的所有出边、判断条件表达式、决定下一个要流转的节点。

这里最容易出问题的就是驳回逻辑。简单的驳回是直接回到发起人,复杂一点的驳回是驳回到指定节点,系统要走回退流程。不管哪种方式,都要注意待办任务的清理和后续所有待办任务的关闭,否则会出现一个流程多条任务同时处于待办状态的脏数据。

条件分支的流转控制也值得单独说。比如报销金额大于 5000 走财务经理审批,否则直接到出纳。这个判断在后端以节点出边的 condition 字段值为依据,用 SpEL 表达式或者简单的表达式解析器来判断当前流程变量的值,匹配到哪条边就走哪个节点。

3.4 MyBatis 动态 SQL 在查询模块中的妙用

这批热词里我最想展开的就是 MyBatis 动态 SQL,因为在工作流系统里,查询条件的不确定性太常见了。待办列表的查询就是一个典型场景:可能按标题模糊查、按发起时间范围查、按流程类型查、按紧急程度查,这些条件全部组合起来,如果写死 SQL 根本不现实。

MyBatis 的<if>标签就是解决方案。源码里待办查询的实现大概是这样的:

<select id="selectTodoList" resultType="map"> SELECT t.id AS taskId, t.task_name AS taskName, pi.process_name AS processName, pi.apply_user AS applyUser, pi.create_time AS createTime FROM act_task t LEFT JOIN act_process_instance pi ON t.instance_id = pi.id WHERE t.assignee_id = #{userId} AND t.task_status = 1 <if test="processName != null and processName != ''"> AND pi.process_name LIKE CONCAT('%', #{processName}, '%') </if> <if test="startTime != null"> AND pi.create_time &gt;= #{startTime} </if> <if test="endTime != null"> AND pi.create_time &lt;= #{endTime} </if> ORDER BY pi.create_time DESC </select>

这里有几个细节新手容易踩坑。一是字符串类型的条件判断必须加!= '',否则传入空字符串时test会误判为有效条件;二是时间比较时,大于号和小于号必须用&gt;&lt;转义,不然 XML 解析直接报错;三是 LIKE 查询用 CONCAT 拼接,避免 SQL 注入风险。如果只是把参数直接嵌进字符串,看似省事,实际上很容易被恶意构造参数攻击。

3.5 MyBatis 缓存机制在配置类查询上的应用

MyBatis 的二级缓存在这类系统中也有应用场景。比如流程定义、节点配置这类低频变更的数据,频繁查询数据库完全没有必要。源码里对流程定义查询配置了二级缓存,只要流程定义没有更新,查询结果就直接从缓存里取,性能提升非常明显。

不过缓存一定要设置合理的失效策略。我的经验是,更新操作必须显式清空缓存,相关表任何一条数据发生变化,涉及该表的缓存都要失效。否则就会出现流程定义明明改了,前端看到的还是旧版本的诡异问题。排查这种问题往往比写代码更耗时。

4. 前端 Vue 侧的实现要点

4.1 流程设计器的核心思路

前端最复杂的部分就是流程设计器。这是基于 Vue + 自研拖拽逻辑实现的,没有用现成的开源流程引擎 UI。核心做法是:左侧节点面板提供可拖拽的节点组件,中间画布区域监听 drop 事件,根据拖拽位置生成新节点,节点之间用 SVG 连线连接。

整个设计器的核心数据结构是一个 JSON 对象,包含 nodes 和 edges 两个数组。每次拖拽、连线、编辑节点属性,本质上都是在操作这个 JSON。这种数据驱动的设计让前后端交互变得非常简单,保存的时候直接把整个 JSON 传给后端,后端解析入库即可。

如果你打算二次开发这个设计器,建议重点关注节点属性面板的实现。选中一个节点后,右侧属性面板会根据节点类型动态渲染不同的表单——审批节点要配置审批人,条件节点要配置条件表达式,开始节点要配置发起人范围。这种动态表单的实现方式是 Vue 动态组件的典型应用,用<component :is="currentComponent">根据节点类型切换不同的表单组件,非常巧妙。

4.2 待办中心与审批操作

待办中心是前端交互最密集的模块。它有三个核心功能:待办列表的分页查询、审批操作弹窗、流程图进度查看。待办列表直接用表格组件,每行数据显示任务名称、发起人、发起时间、流程类型,操作列放"审批"和"查看详情"两个按钮。

审批弹窗里有两类操作:同意时需要填写审批意见,也可以勾选"抄送"给指定同事;驳回时需要选择驳回类型——驳回到发起人还是驳回到指定节点。前端这些交互的注意点在于,提交审批操作前必须有二次确认弹窗,防止误点。我在实际项目里遇到过用户误触导致流程直接被驳回,如果没有二次确认,补救只能靠后台改数据,体验非常差。

流程图进度查看是基于设计器返回的实例当前节点 ID,在流程图 JSON 上计算每个节点的状态,高亮显示当前进度。实现思路是把流程图 JSON 的每个节点和实例的节点记录比对,已经过的节点标记为绿色,当前节点标记为橙色,未到达节点保持灰色。

4.3 路由权限与状态管理

前端路由用 Vue Router,并且结合动态路由实现了按钮级权限控制。用户登录后,后端返回该用户的角色和权限码列表,前端根据权限码动态添加路由,没有权限的菜单直接不显示。这种做法的优势是安全控制前置到前端渲染层,代码上也在路由守卫里做了二次校验,防止用户直接改 URL 越权访问。

状态管理用的 Vuex,store 里主要维护三类数据:用户信息、应用配置、全局的流程字典数据。我比较认同源码里将流程字典缓存到 Vuex 的做法,流程类型、节点类型、审批状态这类枚举值在很多页面都会用到,放在全局状态里可以避免每个页面都发一次请求。

5. 环境搭建、部署与参数补充说明

5.1 本地环境搭建全流程

后端启动前需要确认环境版本。项目基于 JDK 1.8 开发,SpringBoot 版本建议使用 2.3.x 或 2.5.x,这两个版本都比较稳定。MySQL 建议 5.7 或 8.0,需要注意数据库连接 URL 的参数差异,8.0 版本需要额外指定serverTimezone=Asia/Shanghai,否则会报时区错误。

数据库初始化的方式,我建议直接用项目自带的 SQL 脚本,通过 Navicat 或 MySQL Workbench 导入。导入完成后检查一下几个核心表的数据量,如果流程定义表、用户表有初始数据,说明导入成功。创建数据库时记得指定 utf8mb4 字符集,不然存储中文字段会乱码。

前端启动前先确认 Node 版本,Vue 2 项目建议 Node 14 或 16。在项目根目录执行:

npm install npm run dev

npm install 安装依赖时如果遇到网络问题,可以切换到淘宝镜像源:

npm config set registry https://registry.npmmirror.com

后端启动时注意配置application.yml里的数据库连接信息,改成你自己的账号密码。端口配置默认 8080,前端开发服务器默认 8081,前端代码里已经配置了代理转发,将/api开头的请求转发到后端,这块不需要额外处理。

5.2 关键配置文件与参数优化

启动完成后,有基础能力的同学建议立刻手动调一下 MyBatis 的 SQL 日志开关。在application.yml中配置:

logging: level: com.example.workflow.mapper: debug

将 Mapper 接口所在的包日志级别调为 debug,就可以在控制台看到每条 SQL 的执行情况,排查问题效率翻倍。这也是热词里提到"mybatis配置打印"的具体做法。实际调试时你会发现,很多问题不看 SQL 根本定位不到原因,比如多表关联查出来的数据不对,控制台一眼就能看出左连接和内连接的差别。

另外一个实用配置是 MyBatis 的驼峰命名映射。如果数据库字段是下划线命名(如task_name),实体类是驼峰命名(如taskName),需要开启以下配置:

mybatis: configuration: map-underscore-to-camel-case: true

不开启的话,查询结果里的task_name字段无法自动映射到实体的taskName属性,返回的数据全是 null。这个坑我见过很多刚接触 MyBatis 的同事踩过,排查半天结果就是一行配置的事。

5.3 SpringBoot 打包与部署细节

本地开发没问题之后,部署阶段有几个细节值得记录。项目打包用 Maven:

mvn clean package -DskipTests

打包时会遇到一个常见问题:SpringBoot 版本和打包插件版本不匹配,导致 repackage 目标执行失败。解决办法是检查pom.xml里的spring-boot-maven-plugin版本,必须与 SpringBoot 父依赖版本一致。

如果部署到服务器时用的是 Docker,建议将启动命令写成:

docker run -d --name workflow-server \ -p 8080:8080 \ -v /etc/localtime:/etc/localtime:ro \ -e TZ=Asia/Shanghai \ workflow-server:latest

-v /etc/localtime-e TZ=Asia/Shanghai这两项配置非常关键,否则容器内时间会是 UTC 时区,所有审批记录的创建时间都会差 8 个小时,排查起来非常痛苦。

6. 常见问题与排查记录

6.1 高频问题速查表

我把这个项目里最容易出现的问题整理成了表格,大部分都是我在跑通项目和二次开发时实际遇到过的:

现象原因解决方案
MyBatis 查询返回 null未开启驼峰映射开启map-underscore-to-camel-case
保存流程定义提示节点不合法开始/结束节点缺失或重复检查 JSON 中节点类型配置
审批通过后没有生成下一个任务条件表达式写错或节点间没连线检查 edges 的 target 指向
启动报端口占用8080 或 8081 被占用修改配置文件的 server.port
npm install 报错Node 版本太低或镜像源慢升级到 Node 14+,切换镜像源
中文乱码数据库字符集不对统一使用 utf8mb4 并重启 MySQL
时间对了但待办列表顺序混乱缺少明确的排序字段按创建时间 DESC 排序,建议加索引

6.2 启动失败"端口被占用"怎么最快定位

这个问题的排查方式很简单,Windows 系统先执行:

netstat -ano | findstr 8080

拿到占用端口的 PID 后,打开任务管理器,在"详细信息"里找到对应 PID 的进程,确认不是系统关键进程后直接结束。Linux 服务器上使用:

lsof -i:8080

拿到 PID 后执行kill -9 PID即可。这种问题通常是因为上一次启动的进程没被完全关闭,我自己的习惯是把启动命令和停止命令都写到脚本里,避免手工杀进程时误杀其他服务。

6.3 审批流程卡住不流转的排查思路

审批通过后流程没有走到下一个节点,这是我见过最多的问题,也是最考验排查思路的。按顺序做三件事:第一步查任务的审批日志,确认审批动作有没有写进去;第二步查流程实例的当前节点 ID,看看有没有更新;第三步查节点表里这个节点的出边配置,看条件表达式是否正确。

最常见的原因是条件表达式和实际流程变量的值不匹配。比如编辑流程时配置了amount > 5000走 A 节点,结果发起申请时前端没有传amount这个流程变量,后端取值就变成了 null,条件判断直接失败。这就是为什么我在 3.3 节强调条件表达式校验必须做,而且发起的接口入参必须校验必填流程变量。

6.4 前端白屏或接口 404 的处理

前端页面能打开但没有数据,或者打开直接白屏,这个排查顺序要记住。先打开浏览器开发者工具的 Network 面板,看接口请求是否真的发出去,再看请求的 URL 是否有/api前缀,最后看控制台有没有报跨域错误。开发环境下前端配置了代理转发,如果代理路径配置错了,所有接口都会 404,但页面本身不会报错。

上线部署时如果前端打包后访问接口 404,注意检查 Nginx 的 location 配置。常见做法是把/api前缀的请求转发到后端服务:

location /api/ { proxy_pass http://127.0.0.1:8080/api/; }

这里有个极易踩的坑:proxy_pass后面有没有/,转发路径是拼接还是替换规则都不一样,配错了就是 404。

6.5 从源码阅读到二次开发的进阶建议

代码基本上跑通之后,如果要做二次开发,我建议按这个顺序去读源码:先读数据库表结构,把实体类和数据表对应上;再读 Mapper 接口和控制器的接口定义,理解每个接口的作用;然后重点阅读 Service 层,尤其是审批流转的核心逻辑;最后再看前端页面,理解每个按钮对应哪个接口。

读 Service 层的时候,建议把这几条核心链路单独拉出来仔细看:发起流程、审批通过、审批驳回、流程作废。这四条链路基本覆盖了 80% 的状态流转逻辑。其他的像用户管理、角色管理,本质上就是普通的 CRUD,理解难度不大。

我个人实际测试下来的感受是,这套项目在学习企业级项目架构方面的性价比很高,代码量适中,逻辑清晰,没有太多冗余设计。生产环境真正使用的话,还需要补充消息通知(短信/邮件/站内信)、流程超时提醒、操作日志审计等功能,这些都可以基于现有的表结构扩展。如果后续打算接功能更丰富的工作流引擎,也可以保留现有的业务表,把底层的流程引擎替换成 Flowable 或 Camunda,改动点主要集中在 Service 层。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询