
企业级项目源码拿到手之后最怕的不是代码复杂而是不知道从哪里开始读、哪些模块是核心、哪些地方藏着坑。这套 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 idselectTodoList resultTypemap 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 testprocessName ! null and processName ! AND pi.process_name LIKE CONCAT(%, #{processName}, %) /if if teststartTime ! null AND pi.create_time gt; #{startTime} /if if testendTime ! 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 :iscurrentComponent根据节点类型切换不同的表单组件非常巧妙。4.2 待办中心与审批操作待办中心是前端交互最密集的模块。它有三个核心功能待办列表的分页查询、审批操作弹窗、流程图进度查看。待办列表直接用表格组件每行数据显示任务名称、发起人、发起时间、流程类型操作列放审批和查看详情两个按钮。审批弹窗里有两类操作同意时需要填写审批意见也可以勾选抄送给指定同事驳回时需要选择驳回类型——驳回到发起人还是驳回到指定节点。前端这些交互的注意点在于提交审批操作前必须有二次确认弹窗防止误点。我在实际项目里遇到过用户误触导致流程直接被驳回如果没有二次确认补救只能靠后台改数据体验非常差。流程图进度查看是基于设计器返回的实例当前节点 ID在流程图 JSON 上计算每个节点的状态高亮显示当前进度。实现思路是把流程图 JSON 的每个节点和实例的节点记录比对已经过的节点标记为绿色当前节点标记为橙色未到达节点保持灰色。4.3 路由权限与状态管理前端路由用 Vue Router并且结合动态路由实现了按钮级权限控制。用户登录后后端返回该用户的角色和权限码列表前端根据权限码动态添加路由没有权限的菜单直接不显示。这种做法的优势是安全控制前置到前端渲染层代码上也在路由守卫里做了二次校验防止用户直接改 URL 越权访问。状态管理用的 Vuexstore 里主要维护三类数据用户信息、应用配置、全局的流程字典数据。我比较认同源码里将流程字典缓存到 Vuex 的做法流程类型、节点类型、审批状态这类枚举值在很多页面都会用到放在全局状态里可以避免每个页面都发一次请求。5. 环境搭建、部署与参数补充说明5.1 本地环境搭建全流程后端启动前需要确认环境版本。项目基于 JDK 1.8 开发SpringBoot 版本建议使用 2.3.x 或 2.5.x这两个版本都比较稳定。MySQL 建议 5.7 或 8.0需要注意数据库连接 URL 的参数差异8.0 版本需要额外指定serverTimezoneAsia/Shanghai否则会报时区错误。数据库初始化的方式我建议直接用项目自带的 SQL 脚本通过 Navicat 或 MySQL Workbench 导入。导入完成后检查一下几个核心表的数据量如果流程定义表、用户表有初始数据说明导入成功。创建数据库时记得指定 utf8mb4 字符集不然存储中文字段会乱码。前端启动前先确认 Node 版本Vue 2 项目建议 Node 14 或 16。在项目根目录执行npm install npm run devnpm 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 打包与部署细节本地开发没问题之后部署阶段有几个细节值得记录。项目打包用 Mavenmvn 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 TZAsia/Shanghai \ workflow-server:latest-v /etc/localtime和-e TZAsia/Shanghai这两项配置非常关键否则容器内时间会是 UTC 时区所有审批记录的创建时间都会差 8 个小时排查起来非常痛苦。6. 常见问题与排查记录6.1 高频问题速查表我把这个项目里最容易出现的问题整理成了表格大部分都是我在跑通项目和二次开发时实际遇到过的现象原因解决方案MyBatis 查询返回 null未开启驼峰映射开启map-underscore-to-camel-case保存流程定义提示节点不合法开始/结束节点缺失或重复检查 JSON 中节点类型配置审批通过后没有生成下一个任务条件表达式写错或节点间没连线检查 edges 的 target 指向启动报端口占用8080 或 8081 被占用修改配置文件的 server.portnpm 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 层。