跳到主要内容SpringBoot 整合 Flowable 实现工作流实战指南 | 极客日志Javajava
SpringBoot 整合 Flowable 实现工作流实战指南
基于 SpringBoot 集成 Flowable 引擎实现请假审批流程的方法。涵盖项目依赖配置、BPMN 流程图绘制、核心 Java API 使用及任务审批逻辑。通过实例演示如何启动流程、查询任务、完成审批及处理拒绝分支,帮助开发者快速掌握工作流引擎在业务场景中的落地应用。
暖阳1 浏览 1、流程引擎介绍
Flowable 是一个使用 Java 编写的轻量级业务流程引擎。它可用于部署 BPMN2.0 流程定义,创建流程实例,进行查询,访问运行中或历史的流程实例与相关数据等。
Java 领域另一个常见的流程引擎是 Activiti,两者在核心概念上非常相似,掌握其中一个通常能较快上手另一个。
下面直接进入代码实现环节。
2、创建项目
首先创建一个 Spring Boot 项目,引入 Web 和 MySQL 驱动两个依赖。
项目创建成功后,引入 flowable 依赖:
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.2</version>
</dependency>
该依赖会做一些自动化配置,默认情况下,位于 resources/processes 目录下的流程文件都会被自动部署。
接下来在 application.yaml 中配置数据库连接信息。当项目启动时会自动初始化数据库,将来流程引擎运行时的数据会被自动持久化到数据库中。
spring:
datasource:
username: root
password: 123
url: jdbc:mysql:///flowable?serverTimezone=Asia/Shanghai&useSSL=false
配置完成后启动项目。项目启动成功之后,flowable 数据库中会自动创建相关的表,流程引擎相关的数据都会保存到这些表中。
3、画流程图
绘制流程图是流程引擎使用的关键步骤。IDEA 自带了一些可视化插件,例如 Flowable BPMN visualizer,安装后可以在 resources 目录下新建 processes 目录,并在其中新建 BPMN 文件(后缀固定为 .bpmn20.xml)。
右键选择 View BPMN Diagram 即可打开可视化页面进行绘制。
以请假流程为例,流程逻辑如下:员工发起请假 -> 组长审核 -> (通过) 经理审核 -> (通过) 结束;(拒绝) 发送失败通知并结束。
对应的 XML 文件内容如下,其中包含了一些流程细节:
<process name="ask_for_leave" isExecutable="true">
<![CDATA[${checkResult=='通过'}]]>
<![CDATA[${checkResult=='拒绝'}]]>
<![CDATA[${checkResult=='通过'}]]>
<![CDATA[${checkResult=='拒绝'}]]>
<userTask name="请假" flowable:assignee="#{leaveTask}"/>
<userTask name="组长审核" flowable:assignee="#{zuzhangTask}"/>
<userTask name="经理审核" flowable:assignee="#{managerTask}"/>
<exclusiveGateway/>
<exclusiveGateway/>
<endEvent name="结束"/>
<startEvent name="开始"/>
<sequenceFlow sourceRef="startLeave" targetRef="leaveTask"/>
<sequenceFlow sourceRef="leaveTask" targetRef="zuzhangTask"/>
<sequenceFlow sourceRef="zuzhangJudeTask" targetRef="managerTask" name="通过">
<conditionExpression xsi:type="tFormalExpression">
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="zuzhangJudeTask" targetRef="sendMail" name="拒绝">
<conditionExpression xsi:type="tFormalExpression">
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="managerTask" targetRef="managerJudgeTask"/>
<sequenceFlow name="通过" sourceRef="managerJudgeTask" targetRef="endLeave">
<conditionExpression xsi:type="tFormalExpression">
</conditionExpression>
</sequenceFlow>
<sequenceFlow name="拒绝" sourceRef="managerJudgeTask" targetRef="sendMail">
<conditionExpression xsi:type="tFormalExpression">
</conditionExpression>
</sequenceFlow>
<serviceTask flowable:exclusive="true" name="发送失败提示" isForCompensation="true" flowable:class="org.javaboy.flowable.AskForLeaveFail"/>
<sequenceFlow sourceRef="sendMail" targetRef="askForLeaveFail"/>
<endEvent name="请假失败"/>
<sequenceFlow sourceRef="zuzhangTask" targetRef="zuzhangJudeTask"/>
</process>
结合 XML 文件解释一下涉及到的 Flowable 组件:
<process>:表示一个完整的工作流程。
<startEvent>:工作流的起点。
<endEvent>:工作流的终点。
<userTask>:代表任务审核节点(如组长、经理),flowable:assignee 属性指定处理人 ID。
<serviceTask>:服务任务,可执行任意业务逻辑。
<exclusiveGateway>:排他网关,相当于流程图中的判断菱形框。
<sequenceFlow>:连接各个节点的线条。
关于流程图的绘制细节,这里补充几点关键说明:乍一看图比较复杂,但只要理顺各个属性之间的关系,很快就能理解其运作机制。
4、开发接口
接下来编写几个接口来体验流程引擎。在正式编码前,先熟悉几个核心类。
4.1 Java 类梳理
- ProcessDefinition:流程定义,相当于规范,每个定义都有唯一 ID。
- ProcessInstance:流程实例,相当于根据定义创建的对象。
- Activity:流程标准规范 BPMN2.0 中的步骤单元。
- Execution:流程的执行线路,用于获取当前实例执行到了哪个 Activity。
- Task:当前待处理的任务。
4.2 查看流程图
为了方便调试,先准备一个接口用于查看流程图的实时执行情况。
@RestController
public class HelloController {
@Autowired
RuntimeService runtimeService;
@Autowired
TaskService taskService;
@Autowired
RepositoryService repositoryService;
@Autowired
ProcessEngine processEngine;
@GetMapping("/pic")
public void showPic(HttpServletResponse resp, String processId) throws Exception {
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
if (pi == null) {
return;
}
List<Execution> executions = runtimeService.createExecutionQuery().processInstanceId(processId).list();
List<String> activityIds = new ArrayList<>();
List<String> flows = new ArrayList<>();
for (Execution exe : executions) {
List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
activityIds.addAll(ids);
}
BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0, false);
OutputStream out = null;
byte[] buf = new byte[1024];
int length = 0;
try {
out = resp.getOutputStream();
while ((length = in.read(buf)) != -1) {
out.write(buf, 0, length);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
这是一个辅助工具,用于生成流程执行时的可视化图片,后续调试时会用到。
4.3 开启一个流程
String staffId = "1000";
@Test
void askForLeave() {
HashMap<String, Object> map = new HashMap<>();
map.put("leaveTask", staffId);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("ask_for_leave", map);
runtimeService.setVariable(processInstance.getId(), "name", "javaboy");
runtimeService.setVariable(processInstance.getId(), "reason", "休息一下");
runtimeService.setVariable(processInstance.getId(), "days", 10);
logger.info("创建请假流程 processId:{}", processInstance.getId());
}
由员工发起请假流程,map 中的 leaveTask 是在 XML 流程文件中提前定义好的变量名,用于指定任务发起人。同时设置了一些额外信息,如姓名、原因、天数。ask_for_leave 对应 XML 中定义的 process 名称。
执行该单元测试方法后,控制台会打印流程 ID。使用该 ID 访问上述 /pic 接口,可以看到流程已到达'请假'节点。
4.4 将请求提交给组长
String zuzhangId = "90";
@Test
void submitToZuzhang() {
List<Task> list = taskService.createTaskQuery().taskAssignee(staffId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended());
Map<String, Object> map = new HashMap<>();
map.put("zuzhangTask", zuzhangId);
taskService.complete(task.getId(), map);
}
}
利用 staffId 查询当前员工的任务,遍历任务并调用 taskService.complete 方法提交。注意在 map 中指定下一位处理人(组长)的 ID。
提交完成后再次查看流程图,可以看到流程已流转至'组长审批'节点。
4.5 组长审批
@Test
void zuZhangApprove() {
List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("组长 {} 在审批 {} 任务", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
map.put("managerTask", managerId);
map.put("checkResult", "通过");
taskService.complete(task.getId(), map);
}
}
通过组长的 ID 查询任务,同意时需指定经理 ID,即流程下一步的处理人。
@Test
void zuZhangReject() {
List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("组长 {} 在审批 {} 任务", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
map.put("checkResult", "拒绝");
taskService.complete(task.getId(), map);
}
}
拒绝时直接设置 checkResult 为拒绝即可,流程将进入分支逻辑。
4.6 经理审批
经理审批逻辑类似,但作为最后一步,无需指定下一位处理人。
@Test
void managerApprove() {
List<Task> list = taskService.createTaskQuery().taskAssignee(managerId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("经理 {} 在审批 {} 任务", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
map.put("checkResult", "通过");
taskService.complete(task.getId(), map);
}
}
@Test
void managerReject() {
List<Task> list = taskService.createTaskQuery().taskAssignee(managerId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("经理 {} 在审批 {} 任务", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
map.put("checkResult", "拒绝");
taskService.complete(task.getId(), map);
}
}
4.7 拒绝流程处理
如果组长或经理拒绝,流程会进入相应的分支处理。首先在 XML 流程文件中定义了服务任务:
<serviceTask flowable:exclusive="true" name="发送失败提示" isForCompensation="true" flowable:class="org.javaboy.flowable.AskForLeaveFail"/>
对应的处理类 AskForLeaveFail 实现了 JavaDelegate 接口:
public class AskForLeaveFail implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("请假失败。。。");
}
}
当流程走到被拒绝分支时,会自动执行该方法。开发者可以在这里添加邮件通知、短信提醒等具体业务逻辑。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online