SpringBoot集成Flowable工作流引擎:告别手写状态机,实现可视化流程设计
如果你正在开发一个OA、CRM或任何需要审批流程的系统并且正在为如何优雅地实现“提交-审核-流转-归档”这一套逻辑而头疼那么这篇文章就是为你准备的。传统的手写状态机代码不仅耦合度高、难以维护每次流程变更都意味着一次代码重构。而工作流引擎正是为了解决这个痛点而生。但很多开发者对工作流引擎望而却步认为它过于复杂学习曲线陡峭。今天我想告诉你一个事实在 SpringBoot 的生态下集成一个成熟的工作流引擎并实现可视化流程设计其上手难度可能远低于你想象。核心的障碍往往不在于引擎本身而在于如何将引擎与你的业务系统无缝对接以及如何提供一个让非技术人员也能理解的流程设计界面。本文将聚焦于一个非常具体且实用的场景在 SpringBoot 项目中集成 Flowable 工作流引擎并引入 bpmn-js 这个强大的前端流程编辑器构建一个从流程设计到执行的全栈解决方案。我们将彻底告别手写复杂 XML 配置文件的时代通过拖拽式设计器让业务人员也能参与流程定义。文章分为上下两篇本篇上将带你完成环境搭建、引擎集成、数据库初始化以及第一个流程的创建与执行为你打下坚实的理论基础和实践基础。1. 这篇文章真正要解决的问题在业务系统开发中流程类需求如请假、报销、订单审核非常普遍。最初的实现方式可能是这样的在数据库里设计一张process表用status字段记录当前状态如0-待提交1-待经理审核2-待总监审核3-已完成4-已驳回然后在代码里写满if-else或switch-case来判断状态流转。这种做法在简单场景下尚可但一旦流程变得复杂并行审批、条件分支、回退、子流程代码就会迅速膨胀成难以维护的“屎山”。更棘手的是当业务方提出“我们想调整一下报销流程先让财务初审再给部门经理批”时你面临的很可能是一次伤筋动骨的重构。工作流引擎如 Flowable、Activiti的引入正是为了将流程逻辑从业务代码中解耦出来。引擎负责流程的流转、任务分配、状态持久化而你的业务代码只需要关注在每一个节点上要处理的业务数据。这带来了几个核心价值可维护性流程定义通常是一个 XML 文件独立于代码修改流程无需重启服务在某些配置下。可视化流程可以图形化展示当前进度一目了然便于排查问题和向业务方演示。灵活性支持复杂的流程模式并行网关、包含网关、事件驱动等满足绝大多数业务场景。历史追溯引擎自动记录完整的流程执行历史便于审计和复盘。然而新的问题随之而来如何定义这个流程早期的做法是直接编写 BPMN 2.0 标准的 XML 文件这对于开发人员来说不仅枯燥易错而且无法直观理解。因此一个可视化的流程设计器成为了打通工作流落地“最后一公里”的关键。这就是我们引入 bpmn-js 的原因它是一个基于 Web 的、功能强大的 BPMN 2.0 流程图绘制与编辑库。本文将解决的核心问题是如何在一个标准的 SpringBoot Web 项目中整合 Flowable 引擎与 bpmn-js 设计器实现从“画流程图”到“流程跑起来”的完整闭环。你会得到一个可运行的项目骨架理解每个组件的职责并掌握流程定义、部署、启动、查询、完成任务等核心 API 的使用。2. 基础概念与核心原理在开始动手之前我们需要统一几个关键概念这有助于理解后续的每一步操作。BPMN 2.0 (Business Process Model and Notation)这是业务流程建模与标注的行业标准。你可以把它理解为流程图的“语法”。它定义了一套图形元素如圆角矩形代表任务菱形代表网关圆圈代表事件和对应的 XML 结构。Flowable 引擎能够解析和执行符合 BPMN 2.0 标准的流程定义文件.bpmn20.xml 文件。Flowable一个轻量级、高性能的 Java 工作流引擎是 Activiti 项目的一个分支。它完全支持 BPMN 2.0 标准提供了丰富的 API 用于流程部署、执行、任务管理和历史查询。其核心特点是“嵌入性”可以像使用一个库一样集成到你的 SpringBoot 应用中而不是作为一个独立的重型中间件。bpmn-js一个由 Camunda 公司也是流程引擎领域的重要参与者开源的前端 JavaScript 库用于在浏览器中可视化编辑和渲染 BPMN 2.0 流程图。它提供了类似 Visio 的拖拽、连线、属性编辑等交互功能。我们将用它来构建我们的流程设计器前端页面。核心交互原理整个系统的数据流是这样的设计阶段用户在前端使用 bpmn-js 绘制流程图。保存/部署阶段前端将流程图导出为 BPMN 2.0 XML 字符串通过 HTTP API 发送给后端 SpringBoot 应用。后端处理SpringBoot 应用接收 XML通过 Flowable 的RepositoryService将其部署到引擎中。部署过程会将 XML 解析为可执行的对象并持久化到数据库。运行阶段业务系统通过 Flowable 的RuntimeService启动一个流程实例通过TaskService查询和办理用户任务。状态展示前端可以再次调用后端 API获取某个运行中流程实例的当前节点图并由 bpmn-js 高亮显示。理解了这个闭环我们就知道接下来要搭建的是一个包含前端设计器和后端引擎服务的完整应用。3. 环境准备与前置条件我们将创建一个标准的 SpringBoot 2.7.x 项目这是目前一个稳定且广泛使用的版本。请确保你的开发环境满足以下要求JDK: 1.8 或更高版本推荐 JDK 11 或 17。Maven: 3.6 或更高版本用于依赖管理。IDE: IntelliJ IDEA 或 Eclipse本文演示使用 IDEA。数据库: MySQL 5.7 或 8.0。Flowable 支持多种数据库我们选择最常用的 MySQL。Node.js (可选): 如果你计划对 bpmn-js 进行深度定制或打包需要 Node.js 环境。本文主要使用其 CDN 引入方式所以非必须。首先使用 Spring Initializr (https://start.spring.io) 或 IDEA 自带的 Spring Boot 项目创建向导生成一个项目骨架。关键依赖选择Spring Web: 提供 RESTful API 支持。Spring Boot DevTools: 开发工具方便热重启。Lombok: 简化 Java Bean 代码。MySQL Driver: MySQL 数据库连接驱动。生成项目后我们手动在pom.xml中添加 Flowable 的核心依赖。4. 核心依赖与项目配置打开项目的pom.xml文件在dependencies部分添加以下关键依赖!-- Flowable Spring Boot Starter: 核心工作流引擎 -- dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter/artifactId version6.8.0/version !-- 建议使用较新稳定版 -- /dependency !-- Spring Boot JDBC Starter: Flowable 依赖JDBC操作数据库 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency !-- MySQL 连接驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- 以下为Spring Initializr可能已生成的基础依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency版本说明这里使用了 Flowable 6.8.0你可以根据官方发布情况调整。注意 SpringBoot 版本与 Flowable 版本的兼容性一般 SpringBoot 2.7.x 与 Flowable 6.x 兼容性良好。接下来配置数据库连接。编辑src/main/resources/application.yml文件spring: datasource: url: jdbc:mysql://localhost:3306/flowable_demo?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneAsia/Shanghai username: root # 替换为你的数据库用户名 password: yourpassword # 替换为你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver # Flowable 特定配置 flowable: # 禁用异步执行器适用于演示和简单场景避免复杂配置 async-executor-activate: false # 是否检查流程定义文件BPMN XML的合法性开发阶段可设为true check-process-definitions: true # 数据库 schema 更新策略 # false不自动创建或更新表生产环境谨慎使用 # true启动时检查必要时更新常用 # create-drop启动创建关闭删除仅测试 database-schema-update: true关键配置解读database-schema-update: true这是开发阶段的神器。当应用首次启动时Flowable 会自动在指定的数据库flowable_demo中创建它所需的所有表大约60多张。你无需手动执行任何 SQL 脚本。在生产环境中建议设置为false并通过 Flyway 或 Liquibase 等工具进行严格的数据库版本管理。请确保 MySQL 中已存在名为flowable_demo的数据库或你自定义的名称并且用户有足够的权限。5. 流程引擎自动配置与表结构初探完成依赖和配置后启动你的 SpringBoot 应用。如果控制台没有报错并且看到类似ProcessEngine configuration initialized的日志说明 Flowable 已经成功集成并初始化了流程引擎。此时连接到你的 MySQL 数据库查看flowable_demo库你会发现多出了大量以ACT_为前缀的表。这些表由 Flowable 自动创建用于存储流程相关的各种数据。主要分为以下几类ACT_RE_*(REpository): 存储静态的流程定义和部署资源信息。例如ACT_RE_PROCDEF存储流程定义ACT_RE_DEPLOYMENT存储部署记录。ACT_RU_*(RUntime): 存储运行时的流程实例、任务、变量等数据。这些是流程正在执行时产生的数据流程结束后会被清理或转移到历史表。ACT_HI_*(HIstory): 存储历史数据如已完成的流程实例、任务、变量等。用于审计和查询。ACT_GE_*(GEneral): 通用数据表如字节数组资源存储上传的 BPMN XML 和图片。了解这些表的前缀有助于你在调试时快速定位问题。例如当流程启动失败时可以查看ACT_RU_EXECUTION和ACT_RU_TASK当需要查询历史记录时可以查看ACT_HI_PROCINST。6. 第一个流程从 XML 定义到 API 执行在引入炫酷的可视化设计器之前我们先通过最“原始”的方式——手写 XML——来理解一个流程是如何被定义和执行的。这能帮助我们建立对 BPMN 2.0 元素和 Flowable API 的直观感受。6.1 创建 BPMN 2.0 XML 文件在src/main/resources目录下新建一个processes文件夹。Flowable 默认会自动扫描该目录下的.bpmn20.xml文件并进行部署。在processes文件夹内创建第一个流程定义文件simple-leave.bpmn20.xml。内容如下?xml version1.0 encodingUTF-8? definitions xmlnshttp://www.omg.org/spec/BPMN/20100524/MODEL xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:flowablehttp://flowable.org/bpmn targetNamespacehttp://flowable.org/bpmn xsi:schemaLocationhttp://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd !-- 定义一个流程id是流程的唯一标识在代码中用于启动流程 -- process idsimpleLeaveProcess name简单请假流程 isExecutabletrue !-- 开始事件 -- startEvent idstartEvent name开始/startEvent !-- 用户任务员工提交请假申请 -- !-- flowable:assignee${applicant} 表示任务执行人由变量 applicant 动态指定 -- userTask idsubmitLeaveTask name提交请假申请 flowable:assignee${applicant}/userTask !-- 用户任务经理审批 -- userTask idmanagerApproveTask name经理审批 flowable:assigneemanager/userTask !-- 排他网关根据审批结果决定流向 -- exclusiveGateway iddecisionGateway name审批决定/exclusiveGateway !-- 结束事件审批通过 -- endEvent idendEventApproved name审批通过结束/endEvent !-- 结束事件审批驳回 -- endEvent idendEventRejected name审批驳回结束/endEvent !-- 顺序流连接各个元素 -- !-- 从开始事件到提交任务 -- sequenceFlow idflow1 sourceRefstartEvent targetRefsubmitLeaveTask/sequenceFlow !-- 从提交任务到经理审批 -- sequenceFlow idflow2 sourceRefsubmitLeaveTask targetRefmanagerApproveTask/sequenceFlow !-- 从经理审批到决策网关 -- sequenceFlow idflow3 sourceRefmanagerApproveTask targetRefdecisionGateway/sequenceFlow !-- 从决策网关到“通过”结束事件带有条件表达式 -- sequenceFlow idflow4 sourceRefdecisionGateway targetRefendEventApproved conditionExpression xsi:typetFormalExpression !-- 当变量 approvalResult 等于 approved 时走此路径 -- ![CDATA[${approvalResult approved}]] /conditionExpression /sequenceFlow !-- 从决策网关到“驳回”结束事件带有条件表达式 -- sequenceFlow idflow5 sourceRefdecisionGateway targetRefendEventRejected conditionExpression xsi:typetFormalExpression !-- 当变量 approvalResult 等于 rejected 时走此路径 -- ![CDATA[${approvalResult rejected}]] /conditionExpression /sequenceFlow /process /definitionsXML 关键元素解析process: 定义一个完整的流程。startEvent/endEvent: 流程的开始和结束节点。userTask: 用户任务节点代表需要人工处理的任务。flowable:assignee属性指定任务的办理人可以是固定值如manager或表达式如${applicant}。exclusiveGateway: 排他网关相当于流程图中的菱形决策框根据条件决定下一步走向。sequenceFlow: 顺序流连接两个元素。可以包含conditionExpression来定义流转条件。${...}: 这是 Flowable 的统一表达式语言UEL用于在运行时获取或设置流程变量。6.2 编写流程控制层 API现在我们需要创建 Spring MVC 的 Controller 来提供启动流程、查询任务、完成任务等 API。首先Flowable 通过自动配置为我们注入了一系列核心 Service Bean我们直接注入使用即可。创建一个ProcessController.javapackage com.example.flowabledemo.controller; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.*; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; import java.util.Map; RestController RequestMapping(/api/process) Slf4j public class ProcessController { // 注入Flowable的核心服务类 Autowired private RuntimeService runtimeService; // 负责流程实例的启动、运行、删除 Autowired private TaskService taskService; // 负责用户任务的操作如查询、完成、指派 Autowired private RepositoryService repositoryService; // 负责流程定义的部署、查询、删除 Autowired private HistoryService historyService; // 负责查询历史数据 /** * 启动一个请假流程实例 * param applicant 申请人ID (例如: zhangsan) * return 流程实例ID */ PostMapping(/startLeave) public String startLeaveProcess(RequestParam String applicant) { // 1. 设置流程变量 MapString, Object variables new HashMap(); variables.put(applicant, applicant); // 与XML中的 ${applicant} 对应 // 2. 启动流程实例 // 参数1: 流程定义的key即XML中process的id: simpleLeaveProcess // 参数2: 流程变量 ProcessInstance processInstance runtimeService.startProcessInstanceByKey(simpleLeaveProcess, variables); String processInstanceId processInstance.getId(); log.info(流程启动成功。流程实例ID: {}, processInstanceId); // 3. 启动后第一个任务提交请假申请会自动创建并分配给 applicant // 我们可以立即查询一下当前用户的任务 ListTask tasks taskService.createTaskQuery() .taskAssignee(applicant) // 查询指定办理人的任务 .processInstanceId(processInstanceId) .list(); if (!tasks.isEmpty()) { log.info(流程启动后生成待办任务: {}, tasks.get(0).getName()); } return 流程实例启动成功ID: processInstanceId; } /** * 查询某个用户的所有待办任务 * param assignee 用户ID * return 任务列表信息 */ GetMapping(/tasks) public ListMapString, Object getTasks(RequestParam String assignee) { ListTask tasks taskService.createTaskQuery() .taskAssignee(assignee) .orderByTaskCreateTime().desc() .list(); // 将Task对象转换为简单的Map方便前端展示 return tasks.stream().map(task - { MapString, Object taskInfo new HashMap(); taskInfo.put(taskId, task.getId()); taskInfo.put(taskName, task.getName()); taskInfo.put(processInstanceId, task.getProcessInstanceId()); taskInfo.put(createTime, task.getCreateTime()); return taskInfo; }).toList(); } /** * 完成一个任务例如提交申请或进行审批 * param taskId 任务ID * param approvalResult 审批结果 (approved 或 rejected)对于提交任务此参数可为空 * return 操作结果 */ PostMapping(/completeTask) public String completeTask(RequestParam String taskId, RequestParam(required false) String approvalResult) { // 在完成任务时可以设置或更新流程变量 MapString, Object variables new HashMap(); if (approvalResult ! null !approvalResult.isEmpty()) { variables.put(approvalResult, approvalResult); // 与XML中的 ${approvalResult} 对应 } // 完成任务并传递变量 taskService.complete(taskId, variables); log.info(任务 {} 已完成。审批结果: {}, taskId, approvalResult); return 任务处理成功; } /** * 部署一个流程定义通常用于接收前端bpmn-js传来的XML字符串 * 本例演示手动部署一个已存在的XML文件 */ PostMapping(/deploy) public String deployProcess() { // 从classpath的processes目录部署simple-leave.bpmn20.xml文件 org.flowable.engine.repository.Deployment deployment repositoryService.createDeployment() .addClasspathResource(processes/simple-leave.bpmn20.xml) .name(简单请假流程部署) .deploy(); // 执行部署 log.info(流程部署成功。部署ID: {}, 部署名称: {}, deployment.getId(), deployment.getName()); return 部署成功ID: deployment.getId(); } }6.3 测试第一个流程启动 SpringBoot 应用。应用启动时Flowable 会自动扫描并部署processes/目录下的simple-leave.bpmn20.xml文件。你也可以手动调用部署 APIPOST /api/process/deploy。接下来我们使用 Postman 或 curl 按顺序测试整个流程启动流程POST http://localhost:8080/api/process/startLeave?applicantzhangsan响应会返回流程实例 ID例如流程实例启动成功ID: 25001。同时数据库ACT_RU_TASK表中会插入一条任务记录办理人 (ASSIGNEE_) 为zhangsan任务名为提交请假申请。查询待办任务GET http://localhost:8080/api/process/tasks?assigneezhangsan你会看到张三有一个待办任务。记下返回的taskId。完成“提交请假申请”任务POST http://localhost:8080/api/process/completeTask?taskId{上一步获取的taskId}注意此时不需要传递approvalResult参数。完成任务后流程会流转到下一个节点“经理审批”。此时查询zhangsan的任务列表将为空而查询manager的任务列表将出现新任务。经理审批完成任务并决策 首先查询经理的任务GET http://localhost:8080/api/process/tasks?assigneemanager获取到taskId后模拟审批通过POST http://localhost:8080/api/process/completeTask?taskId{经理任务的taskId}approvalResultapproved或者审批驳回POST http://localhost:8080/api/process/completeTask?taskId{经理任务的taskId}approvalResultrejected根据不同的approvalResult值流程会沿着 XML 中定义的条件路径flow4或flow5走向不同的结束事件。至此一个完整的手动定义、API 驱动的流程已经跑通。你可以在数据库中观察ACT_RU_*和ACT_HI_*表的数据变化直观地理解流程引擎是如何持久化每一个步骤的。7. 常见问题与排查思路在初次集成和运行 Flowable 时你可能会遇到以下典型问题问题现象可能原因排查方式解决方案应用启动失败报错Table flowable_demo.ACT_GE_PROPERTY doesnt exist1. 数据库连接失败。2.spring.datasource.url中的数据库名不存在。3.flowable.database-schema-update设置为false且表未初始化。1. 检查数据库服务是否启动。2. 检查application.yml中的数据库连接信息。3. 检查flowable.database-schema-update配置。1. 确保数据库服务运行。2. 手动创建数据库如CREATE DATABASE flowable_demo;。3. 开发环境设置为true让 Flowable 自动建表。调用startProcessInstanceByKey时报错no processes deployed with key xxx流程定义未部署或部署失败。1. 检查processes/目录下是否有对应的.bpmn20.xml文件。2. 检查 XML 文件语法是否正确特别是process的id属性。3. 查看启动日志是否有部署相关的错误。1. 确保文件在src/main/resources/processes/下。2. 使用 XML 校验工具检查文件。3. 调用/api/process/deploy手动部署并查看返回结果。任务查询 (taskQuery) 返回空列表但流程明明启动了1. 任务办理人 (assignee) 不匹配。2. 任务已被完成。3. 查询条件有误。1. 直接查询数据库ACT_RU_TASK表看任务是否存在及其ASSIGNEE_字段。2. 检查ACT_HI_TASKINST历史表看任务是否已进入历史。1. 确认代码中设置的assignee变量或固定值与查询时使用的assignee一致。2. 使用taskService.createTaskQuery().processInstanceId(xxx).list()查询该流程所有任务。流程没有按预期路径流转如网关条件不生效1. 流程变量未正确设置或变量名拼写错误。2. 条件表达式语法错误。3. 网关连线缺失条件。1. 在完成任务 (taskService.complete) 时打印或调试传入的variablesMap。2. 检查 XML 中conditionExpression内的表达式确保变量名和比较逻辑正确。1. 确保设置流程变量的 key 与表达式中的变量名完全一致区分大小写。2. 使用简单的表达式如${result yes}。流程实例启动后找不到第一个用户任务XML 中第一个userTask的assignee表达式 (${applicant}) 对应的变量未在启动时传入。检查启动流程的代码runtimeService.startProcessInstanceByKey的第二个参数variables是否包含了 key 为applicant的变量。确保启动流程时通过variablesMap 传入了applicant变量。8. 最佳实践与工程建议上篇小结在成功运行第一个流程之后我们有必要停下来思考一下在实际项目中应该如何更好地使用 Flowable。以下是一些在上篇阶段就可以确立的最佳实践数据库隔离Flowable 会创建数十张表强烈建议为工作流引擎使用独立的数据库或至少独立的 Schema不要与核心业务表混在一起。这有利于备份、迁移和性能管理。流程定义管理在开发环境可以使用classpath扫描自动部署。但在生产环境更推荐通过RepositoryServiceAPI 进行动态部署就像我们写的/deploy接口这样可以实现流程版本的热更新和回滚。每次部署都会生成新版本旧版本的流程实例可以继续运行直至结束。流程变量使用流程变量是连接业务数据与流程引擎的桥梁。建议为变量定义清晰的前缀或使用 Map 结构封装复杂对象。对于需要查询的变量可以考虑将其设置为流程实例的“业务键”businessKey方便通过业务ID直接关联流程。服务类注入RuntimeService,TaskService等是线程安全的可以直接在 Spring Bean 中注入使用。但要注意它们执行的操作通常会在同一个数据库事务中理解事务边界对业务逻辑很重要。异常处理Flowable 会抛出各种运行时异常如FlowableObjectNotFoundException,FlowableException。在 Controller 或 Service 层需要进行统一的异常捕获和转换返回友好的错误信息给前端。日志监控开启 Flowable 的 DEBUG 级别日志logging.level.org.flowableDEBUG可以在开发阶段详细了解引擎的内部操作但生产环境请调回 INFO 或 WARN 级别。在上篇中我们完成了 SpringBoot 与 Flowable 工作流引擎的基础集成并通过手写 XML 和编写 API 的方式实现了一个请假流程的完整生命周期管理。你已经掌握了工作流的核心概念流程定义、流程实例、任务、网关、变量。这些是理解任何工作流引擎的基石。然而手写 XML 毕竟不是长久之计。在下篇中我们将引入本文的另一位主角bpmn-js。我们将构建一个前端页面实现流程的可视化设计、部署、以及运行态的高亮展示。届时你将拥有一个从设计到运行的全栈工作流平台原型彻底告别“面向 XML 编程”的时代。下篇预告集成 bpmn-js 前端设计器、实现流程图的导入/导出、前端与后端部署/启动/查询 API 的对接、运行中流程图的实时高亮展示。

相关新闻

最新新闻

日新闻

周新闻

月新闻