简介:一套Spring Boot 2.6.13、MySQL 8与Flowable 6.8.1整合的工作流应用工程,面向需要快速落地流程审批、任务管理等场景的Java后端开发者,重点解决流程引擎集成、关系型数据持久化与数据库环境搭建三方面痛点,适合作为企业级应用开发的学习模板或起步脚手架。压缩包共29个文件,整体约382.9MB,包含Java源码、pom.xml构建配置、yml与properties配置文件、编译后的class与可运行jar,并含MySQL 8安装程序msi,兼顾源代码研读、参数调优与一键部署等多种用途。该工程已吸引109人学习,可作实践参考。借助自带的MySQL安装程序,开发者无需单独安装数据库即可启动完整环境,省去繁琐配置;结合Flowable的BPMN 2.0流程建模能力,可对照示例完成从流程定义、任务派发到与Spring Boot接口联调的全过程,并方便后续按业务需求扩展审批流、会签、条件分支等模块。整体结构清晰,便于按需检索代码与配置。
1. 为什么 Spring Boot 2.6.13 + MySQL8 + Flowable 6.8.1 是当前做审批流最省心的组合
很多团队一提到工作流引擎就头皮发麻,总觉得要引入一堆中间件,要么版本对不上,要么部署环境复杂。但这套组合恰恰相反——它最大的价值在于把「流程引擎」和「业务系统」塞进了同一个工程里,版本之间又是经过大量生产环境验证过的稳定搭配。我见过太多人卡在搭建阶段,最后才发现是版本不匹配,而不是业务逻辑难写。
这套组合能解决的问题很直接:你需要给业务系统补上审批流、会签、驳回、多实例任务这些能力,但不想为它单独部署一套重量级平台。Flowable 6.8.1 作为一个嵌入式工作流引擎,直接在 Spring Boot 进程里跑,表结构自动建,API 现成可用。而 MySQL8 配合装好的安装程序,连数据库环境都顺手解决了,对于内网部署、以及刚接手别人项目的新人来说,少了最让人头疼的一步。
适合谁用?正在做 OA 类项目、企业内部系统、或者任何需要「状态机 + 人工审核 + 流程追踪」功能的 Java 开发者。下文我会把从建工程到跑通第一条审批流的完整路径写清楚,包括参数、配置和几个容易翻车的细节。
2. 从零搭建:Spring Boot 工程结构与 MySQL8 安装程序的关键作用
2.1 为什么选 Spring Boot 2.6.13 而不是更新的 3.x 版本
很多人在选版本时有个误区:越新越好。但在工作流引擎这个场景,情况恰恰相反。Flowable 6.8.1 对 Spring Boot 3.x 的支持是通过单独的 starter 来适配的,而 2.6.13 是 2.6.x 的最后一个版本,生命周期完整,社区反馈充分,和 Flowable 6.8.1 的兼容性是被大量项目验证过的。
另外一个现实原因是,很多公司现有的项目就停在 Spring Boot 2.x,你要做的只是把 Flowable 加进去,而不是连带着升级整个微服务架构。改动范围越小,风险就越可控。Spring Boot 2.6.13 的另一个好处是它对 Java 8 和 Java 11 都支持得很好,而不少老项目的 JDK 就卡在 8 上,这点很关键。
Spring Boot 2.6.13 也有它的独有行为需要注意,比如对循环依赖默认禁止、路径匹配策略从 AntPathMatcher 切到了 PathPatternParser。这些改动会直接影响 Flowable 的某些 URL 匹配行为,后面集成时会具体说。
2.2 最小工程骨架:pom.xml 里必须出现的依赖
我在搭建最小可用工程时,一般保持依赖极简,用最少的 maven 坐标跑通引擎,再逐步加自己不熟悉的配置。
<properties> <java.version>8</java.version> <spring-boot.version>2.6.13</spring-boot.version> <flowable.version>6.8.1</flowable.version> </properties> <dependencies> <!-- Web 支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Flowable 核心:流程定义、任务、历史 --> <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter</artifactId> <version>${flowable.version}</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies>这里有两个细节值得说。第一,不用单独引入flowable-spring-boot-starter-process,因为flowable-spring-boot-starter已经包含了流程引擎、任务、历史、IDM 这几个最常用的模块。第二,MySQL 驱动的版本不需要手写,Spring Boot 2.6.13 的依赖管理已经把它锁到了 8.0.26 左右,这个版本配 MySQL8 正是稳定区。
还有一点,flowable-spring-boot-starter会启用 Flowable 所有模块的自动配置。如果你只想用流程引擎加任务,不想引入表单、DMN、CMMN 这些模块,后面可以在配置里关掉,减少启动耗时和内存占用,这在低配置服务器上效果明显。
2.3 自带 MySQL8 安装程序:理解两个阶段的不同作用
标题里提到「自带 mysql8 安装程序」,这句话在真实项目里要拆成两种形态理解。第一种是开发环境一键脚本,通常是.bat或.sh,负责在本地把 MySQL 8 的 zip 包解压、初始化 data 目录、注册 Windows 服务或 Linux systemd 服务、设置 root 初始密码。第二种是生产环境的静默安装配置,配合响应文件来做的,避免手工安装的交互步骤。
我一般会在工程根目录放一个mysql-setup/文件夹,里面至少包含:
# 初始化 MySQL8 数据目录(Linux/macOS 下) mysqld --initialize-insecure --basedir=/opt/mysql8 --datadir=/opt/mysql8/data初始化加--initialize-insecure表示 root 账号初始为空密码,后面通过 SQL 去改密码,比--initialize(随机密码)更省事,尤其适合无人值守的安装脚本。Windows 下对应的命令是mysqld --initialize-insecure --basedir=D:\\mysql8\\ --datadir=D:\\mysql8\\data,原理一样。
这个安装程序的意义在于,它让整个项目从「数据库、引擎、业务代码」变成了一个整体分发的方案。新同事或客户拿过去,不用自己去装数据库、记一堆安装步骤,跑完脚本环境就齐了。
3. Flowable 6.8.1 与 Spring Boot 融合:配置参数与自动建表策略
3.1 最关键的 5 个配置项,少一个都会让你后面难受
把 Flowable 接进 Spring Boot 之后,application.yml里有一组参数是必须理解的。先看一段我在项目里反复调优后的配置:
spring: datasource: url: jdbc:mysql://localhost:3306/flowable?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver flowable: database-schema-update: true async-executor-activate: false history-level: audit db-history-used: true process-definition-location-prefix: classpath*:/processes/ process-definition-location-suffixes: - .bpmn20.xml - .bpmn逐项拆开说。database-schema-update设为true的意思是流程引擎启动时自动检查并更新表结构,第一次运行会自动把 60 多张表建好。生产环境我会改成false,用专门的脚本去迁移,避免运行时不小心改了表结构。
async-executor-activate这里设为false是我个人的偏好。Flowable 的异步执行器会启动定时线程去扫 Job 表,如果不做边界事件超时、异步消息这些高级功能,开着反而多耗资源。如果流程里需要定时器或异步延续,再把它打开。
history-level设为audit是稳妥选择。它记录了流程实例、任务、活动的完整历史,同时又不像full那样把变量每次变更的细节都存下来,适合绝大多数业务系统——既能查谁在什么时候批过什么,又不至于让历史表膨胀太快。
关于process-definition-location-prefix,它指定了引擎自动部署流程模型时的扫描路径。上面配置用的是classpath*:/processes/,意味着我只要把.bpmn20.xml文件丢到resources/processes/目录下,项目启动时 Flowable 就会自动部署这些流程定义,不需要手动调 API。
3.2 自动建表成功后的验证动作:连不上或表不全时怎么看
配置写完后,第一次启动要重点观察日志。一个健康的启动过程会在日志里看到类似Flowable 6.8.1的 banner,然后是一串create table ACT_GE_BYTEARRAY、create table ACT_RE_PROCDEF这样的 DDL 输出。如果看到Tables already exist或者跳过建表,说明 data 目录里的库已经有了结构。
还有一个容易骗人的点:Flowable 默认会用自己的 IDM 模块创建用户、组相关表,比如ACT_ID_USER和ACT_ID_GROUP。如果你不打算用 Flowable 原生的用户体系,而是想让流程任务和现有系统的用户体系对接,可以在配置里关掉 IDM:
flowable: idm: enabled: false关掉之后不会建ACT_ID_*这几张表,候选组、候选人功能会受影响,但单纯的流程引擎、任务分配、审批逻辑不受影响。我的建议是:如果你已经有一个完善的用户系统,就该关掉,避免两套用户数据互相干扰。
验证是否成功的另一个关键手段是看数据库。连上 MySQL8 后执行use flowable; show tables;,你会看到以ACT_为前缀的表,按用途分几个族:ACT_RE_*(流程定义和模型)、ACT_RU_*(运行时的实例、任务、变量)、ACT_HI_*(历史数据)、ACT_GE_*(通用数据)。如果表齐了,说明引擎和数据库的链路是通的,接下来就可以部署流程了。
4. 部署并跑通第一条审批流:从 BPMN 文件到完整 Java API 调用
4.1 用带注释的 BPMN 文件完成流程定义:一个简易请假审批
理论讲完,下面是动手环节。我在项目里的习惯是先画一个最简单的「单人审批」流程,走通后再往里面加会签、条件网关和驳回。
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://flowable.org/bpmn"> <process id="leaveApproval" name="请假审批流程" isExecutable="true"> <!-- 流程开始:无触发,由API显式启动 --> <startEvent id="start" name="开始" /> <!-- 第一个用户任务:员工提交申请(实际业务中可能由前一个表单任务承接) --> <userTask id="apply" name="提交请假申请" flowable:assignee="${taskService.getVariable(task.getId(), \"applyUser\")}"> <extensionElements> <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler" flowable:initiator="applyUser" /> </extensionElements> </userTask> <!-- 部门经理审批节点 --> <userTask id="managerApprove" name="部门经理审批" flowable:candidateGroups="deptManager" /> <!-- 条件排他网关:根据审批结果决定走向 --> <exclusiveGateway id="gateway" name="审批结果网关" /> <!-- 同意则流程结束 --> <sequenceFlow id="flow1" sourceRef="apply" targetRef="managerApprove" /> <sequenceFlow id="flow2" sourceRef="managerApprove" targetRef="gateway" /> <userTask id="end" name="结束" /> <sequenceFlow id="flow4" sourceRef="gateway" targetRef="end"> <conditionExpression xsi:type="tFormalExpression"> <![CDATA[${approved == true}]]> </conditionExpression> </sequenceFlow> <endEvent id="endEvent" name="流程结束" /> <sequenceFlow id="flow5" sourceRef="end" targetRef="endEvent" /> </process> </definitions>这份 BPMN 文件有几个值得注意的地方。第一,用户任务的flowable:assignee我写的是动态表达式,从一个名为applyUser的流程变量里取指派人,这就避免了把用户 ID 写死在流程定义里。第二,flowable:candidateGroups可以指定一组候选角色,这类任务会进入候选组列表,由组里的任何一个人认领。实际项目里我会优先用候选组而不是固定指派,适应「领导休假、其他人代审」的常见需求。
第三,条件网关里的表达式${approved == true}依赖一个名为approved的布尔流程变量,提交审批时必须把它作为变量传进去。很多人第一次跑不通流程,就是漏掉了这个变量。顺便一提,BPMN 文件里isExecutable="true"必须显式加上,否则 Flowable 会把它当作描述性定义而跳过部署。
4.2 Java 侧完整调用链:启动流程、查询任务、完成任务
文件放到resources/processes/目录后启动项目,Flowable 会自动部署。下面这段代码是我在模拟项目X里反复使用的模板,涵盖了从启动到结束的完整操作:
@Service public class LeaveApprovalService { private final RuntimeService runtimeService; private final TaskService taskService; public LeaveApprovalService(RuntimeService runtimeService, TaskService taskService) { this.runtimeService = runtimeService; this.taskService = taskService; } /** * 启动请假流程,并设置初始变量 */ public String startLeaveProcess(String applyUserId, Integer days) { Map<String, Object> variables = new HashMap<>(); variables.put("applyUser", applyUserId); variables.put("days", days); variables.put("approved", false); // 初始值设为 false ProcessInstance instance = runtimeService .startProcessInstanceByKey("leaveApproval", variables); return instance.getId(); } /** * 查询某个用户作为候选组可办理的任务 */ public List<Task> queryTasksByCandidateGroup(String groupId) { return taskService.createTaskQuery() .taskCandidateGroup(groupId) .orderByTaskCreateTime() .desc() .list(); } /** * 审批通过或驳回 */ public void completeTask(String taskId, boolean approved) { Map<String, Object> taskVariables = new HashMap<>(); taskVariables.put("approved", approved); // 这里的 true 表示完成任务并设置局部变量 taskService.complete(taskId, taskVariables); } }这段代码的每一行都对应着 Flowable 的核心 API。startProcessInstanceByKey用的是流程定义里<process id="leaveApproval">这个 key,而不是文件名。如果你改过流程 ID,但代码里还写旧值,会直接报ProcessDefinitionNotFoundException。变量applyUser会在启动时被设置,用户任务的动态 assignee 表达式正是从它取值。
queryTasksByCandidateGroup是典型的「查待办」场景,它查的是ACT_RU_TASK表里候选组匹配的任务,查询条件走的是 Flowable 自己的查询器,底层空间很小,不会有 SQL 注入风险。complete方法把任务变量的 map 传进去,这些变量会被写入任务的局部变量,但上面排他网关读取的approved就是在这里传入的。
还有个细节:taskService.complete()之后,任务就从运行态消失,流程走到下一个节点。如果流程被驳回,实际会走一条单独的驳回路径,而不是把同一个任务复活。这是 Flowable 和某些国产流程引擎最大的不同——它更贴合 BPMN 2.0 规范,驳回是「另起一个任务」而不是「任务回到原处」。
4.3 模型设计器做的图,为什么部署后偶尔显示为空白或缺失坐标
用 Flowable 自带的流程设计器(在 Web 界面里拖拽画图)时,有概率遇到一个问题:设计器里画得好好的流程图,部署到引擎里之后,在流程追踪页面上变成空白或者节点叠在一起。原因是 BPMN 文件里如果缺少diagram interchange信息,也就是图形坐标数据,引擎运行时无法还原出可视化的流程图。
出现这种情况多半是手动编辑了 BPMN XML,或者通过接口把模型转 XML 时丢失了图形信息。解决办法是,部署前确认 BPMN 文件里包含<BPMNDiagram>段。如果你用的是代码部署,可以在 XML 里补一段标准的坐标描述;如果你只是手动改 XML,最简单的方法是放到 Flowable 官方 Modeler 里打开再保存一次,坐标信息会被自动补充。
5. Flowable 6.8.1 实战避坑指南:5 个你一定会遇见的麻烦
5.1 MySQL8 时区问题导致连接时报错
- 现象:项目启动时数据源初始化报错,错误内容类似
The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。 - 原因:MySQL8 的 JDBC 驱动对时区参数要求更严格,而 Spring Boot 2.6.13 默认的驱动版本里时区校准逻辑和 MySQL 服务端配置不一致。
- 解决:JDBC URL 显式指定
serverTimezone=Asia/Shanghai,像我在 3.1 节配置里写的那样。另一个办法是在 MySQL8 服务端设置default-time-zone = '+08:00'并重启服务。
5.2 Flowable 自动部署重复注册流程定义
- 现象:每次重启项目,
ACT_RE_PROCDEF表里出现多个版本的同一个流程,版本号递增,旧版本没有被自动清理。 - 原因:Flowable 的自动部署机制是根据文件内容和上次部署的
deployment做比对。但如果你改了 BPMN 文件里的任何文本——哪怕只是加了个空格——引擎都会认为这是一个新版本。 - 解决:如果只是想开发调试,可以在配置里把
flowable.check-process-definitions设为false来关闭启动时的自动部署检查。但更推荐的做法是,用固定的deployment名称或代码部署并复用 Deployment ID,生产环境则走 SQL 脚本或专门的发布流程来管理版本。
5.3 任务的候选人查不到数据
- 现象:流程模型里明明设置了
flowable:candidateGroups="deptManager",但调用taskQuery.taskCandidateGroup("deptManager")时查不到任务。 - 原因:任务可能已经被其他人认领,或者
candidateGroups属性写错了位置。还有一个容易踩的坑是:如果你没关掉 Flowable IDM,但业务代码又没往 IDM 里写用户和组的数据,候选组的字段可能根本没被填上。 - 解决:确认任务在
ACT_RU_IDENTITYLINK表里存在类型为candidate的记录。排查时直接查库:select * from ACT_RU_IDENTITYLINK where TASK_ID_ = '你的任务ID'。如果没有记录,回去检查流程定义里的属性名是不是写成了candidateGroup(少了 s 的复数)。
5.4 流程变量在网关处读取不到或为 NULL
- 现象:任务完成时明明传了变量
approved=true,但流程走到排他网关时报错或者走了默认路径。 - 原因:变量作用域不一致。
taskService.complete(taskId, variables)传入的变量默认是任务局部变量,如果网关判断用的是流程实例级别的变量,有可能读到null。另一种情况是,变量名拼写不一致。 - 解决:在使用网关条件前,确认变量层级。用
runtimeService.getVariable(processInstanceId, "approved")取到的才是全局流程变量。如果任务局部变量盖住了全局变量,可以在 complete 时同时设置局部和全局:taskService.complete(taskId, variables, globalVariables)。再不行就在 BPMN 里给条件表达式加一个默认值判断。
5.5 Spring Boot 2.6.x 路径匹配策略导致 Flowable 接口 404
- 现象:Flowable 自带的管理接口或 REST 接口通过
/service/**访问时返回 404,但在早些版本是正常的。 - 原因:Spring Boot 2.6 开始默认用
PathPatternParser替代AntPathMatcher,/**通配符的匹配规则有变化,导致某些路径没被正确映射。 - 解决:在
application.yml里把路径匹配策略改回旧模式:
spring: mvc: pathmatch: matching-strategy: ant_path_matcher这个配置在 Flowable 6.8.1 的官方文档和社区讨论里都被反复提及,属于典型的升级副作用。如果你不涉及自定义路径匹配,这一项可以不管;但如果你同时集成了 Swagger 或 Knife4j,那就一定要显式配置,否则接口列表会加载不出来。
6. 进阶技巧:把流程历史查询性能提升 10 倍的索引设计与数据归档策略
前面内容已经能让你跑通一条流程了。当流程实例数量从几百涨到几十万,你会发现ACT_HI_*系列表成了查询瓶颈。此时不看引擎的 API 优化,而是直接落到 MySQL8 的索引和数据生命周期上。
我一般在流程实例量过十万的时候就执行一次索引优化。Flowable 6.8.1 自带了一些基础索引,但它的设计是面向通用场景的,没有照顾到你的特定查询模式。最常见的优化动作是给ACT_HI_TASKINST表加上基于PROC_INST_ID_和END_TIME_的联合索引,因为绝大多数历史查询是「查某条流程下的所有历史任务,按结束时间排序」。
另一个很常用的做法是定期归档。Flowable 官方没有提供现成的归档功能,我在某公司交付项目时是写了一个定时任务,把ACT_HI_*表里结束超过 90 天的流程实例数据同步到归档库,然后从主库删除。这里要特别提醒,删除历史数据必须严格按照PROC_INST_ID_关联删除,因为历史表之间没有外键约束,漏删任何一张表都会导致流程历史显示不完整。
这个工作流的调试技巧也值得分享:每次启动流程前,我会先看ACT_RE_PROCDEF表里最新部署的流程定义 key 和版本号。排查任务问题时,第一步永远是看ACT_RU_TASK和ACT_HI_TASKINST的ASSIGNEE_和END_TIME_,能立刻定位到任务卡在哪个节点、谁处理过。
个人习惯是,不管项目多急,第一次写 Flowable 集成时都要先在本地把完整的启动、查询、完成链路跑通,再往业务里嵌。那些跳过这一步直接改公司老项目的人,后面花在排查环境问题上的时间,往往比节省下来的多得多。希望这套方案能帮你把流程引擎稳稳地落到项目里。
本文还有配套的精品资源,点击获取