一、介绍
纵览Gitee搜索Flowable开源项目,大多都是已开发好的项目,而笔者从零开始搭建属于自己的Flowable引擎,并且是可以拿到生产上使用的。
二、软件架构
Springboot + Flowable + modeler + idm + Mysql
SrpingBoot version:2.7.5
Flowable version:6.3.0
Mysql version:8.0.26
JDK:8
三、SpringBoot整合Flowable
3.1 使用spring initializer快速创建spring Boot项目
步骤:
1,打开idea,创建工程:file -> newproject ,选择spring initializer;
2,然后填写项目命名后,选择JDK8,Maven;
3,选择功能模块,选Web和lombok即可;
4,点Finish,就能够自动创建工程,并能导入相应依赖了;
5,Maven加载依赖后,运行主程序类看是否成功。
3.2 pom.xml文件引入依赖配置
引入Flowable依赖,这里选择Springboot-starter,毕竟是springboot项目。
添加必要的依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.flowable/flowable-spring-boot-starter -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.3.0</version>
</dependency>
</dependencies>
3.3 application.yml配置
在application.yml文件中加入以下配置。
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/spider-flowable?charset=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8
username: root
password: 12345678
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
flowable:
#关闭定时任务JOB
async-executor-activate: false
#将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
database-schema-update: true
3.4 运行项目
项目启动的时候检查如果数据库对应的表结构没有创建,会帮助我们先创建对应的表结构。到此Springboot整合Flowable就搞定了,
项目启动后,刷新数据库,你会发现多了61张表,都是以ACT为前缀的表名。
知道为什么Flowable自带的数据库的表名为啥是以ACT为前缀吗?
因为Flowable是基于Activiti6衍生出来的版本。
数据库结构如图:
四、定义流程文件
FLowable流程的运行,其实是用一个满足BPMN格式的XML文件来执行的,至于XML内容格式是怎么样的后续在学习。
在项目中的resources下新建一个processes文件夹,processes目录下的任何BPMN 2.0流程定义都会被自动部署。
1,在processes文件夹下新建holiday-request.bpmn20.xml文件
2,文件内容复制如下:
<?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:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
xmlns:flowable="http://flowable.org/bpmn"
typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://www.flowable.org/processdef">
<process id="holidayRequest" name="请假流程" isExecutable="true">
<!--开始事件-->
<startEvent id="startEvent"/>
<!--顺序流,从 startEvent 到 approveTask-->
<sequenceFlow sourceRef="startEvent" targetRef="approveTask"/>
<!--用户任务,需要 assignee 来审批-->
<userTask id="approveTask" name="同意或驳回请求" flowable:candidateGroups="managers" flowable:assignee="admin"/>
<!--顺序流,从 approveTask 到 decision -->
<sequenceFlow sourceRef="approveTask" targetRef="decision"/>
<!--排他网关-->
<exclusiveGateway id="decision"/>
<!--顺序流,从 decision 到 successCall-->
<sequenceFlow sourceRef="decision" targetRef="successCall">
<conditionExpression xsi:type="tFormalExpression">
<!--approved 为 true 时,该顺序流生效-->
<![CDATA[
${approved}
]]>
</conditionExpression>
</sequenceFlow>
<!--顺序流,从 decision 到 failedCall-->
<sequenceFlow sourceRef="decision" targetRef="failedCall">
<conditionExpression xsi:type="tFormalExpression">
<!--approved 为 false 时,该顺序流生效-->
<![CDATA[
${!approved}
]]>
</conditionExpression>
</sequenceFlow>
<!--服务任务,审批通过 -->
<serviceTask id="successCall" name="审批通过调用服务"
flowable:class="com.example.spiderFlowable.core.delegate.ApprovalSuccessDelegate"/>
<sequenceFlow sourceRef="successCall" targetRef="holidayApprovedTask"/>
<userTask id="holidayApprovedTask" name="审批通过"/>
<sequenceFlow sourceRef="holidayApprovedTask" targetRef="approveEnd"/>
<!--服务任务,审批驳回 -->
<serviceTask id="failedCall" name="审批驳回调用服务"
flowable:class="com.example.spiderFlowable.core.delegate.ApprovalFailDelegate"/>
<sequenceFlow sourceRef="failedCall" targetRef="rejectEnd"/>
<!--结束事件-->
<endEvent id="approveEnd"/>
<endEvent id="rejectEnd"/>
</process>
</definitions>
五、运行项目并测试
咱来测试一下功能,小试牛刀,更多功能在后面文章再细谈。
在test文件夹下新建测试类SpiderFlowableTest,代码如下。
package com.example.spiderFlowable;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author xiong.bo
* @version 1.0
* @date 2022/11/27 10:41 上午
*/
@SpringBootTest
public class SpiderFlowableTest {
@Autowired
private RepositoryService repositoryService;
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
/**
* 模拟-部署流程
* 说明:
* 流程定义有版本的概念,bpmn文件有改动,需要部署后才生效
*/
@Test
void createDeployment() {
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("processes/holiday-request.bpmn20.xml")
.deploy();
System.out.println(deployment.getId());
System.out.println(JSON.toJSONString(deployment));
}
/**
* 获取流程定义
*/
@Test
void getProcessDefinition() {
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId("17504")
.singleResult();
System.out.println("definitionId:" + processDefinition.getId());
System.out.println("definition:" + JSONUtil.toJsonStr(processDefinition));
}
/**
* 启动流程
* 模拟用户发起一个请假流程
*/
@Test
void startProcessDefinition() {
Map<String, Object> variables = new HashMap<>();
variables.put("employee", "李四");
variables.put("nrOfHolidays", "4");
variables.put("description", "外出请假");
//启动流程实例有多个方法,这里调用流程key的方法来启动
ProcessInstance holidayProcessInstance = runtimeService.startProcessInstanceByKey("holidayRequest", variables);
System.out.println("processInstanceId:" + holidayProcessInstance.getProcessInstanceId());
}
/**
* 获取任务
* 用户发起流程后,相关的人员能够查询该任务
*
*/
@Test
void getTask(){
List<Task> tasks = taskService.createTaskQuery()
.active()
.includeProcessVariables()
//值为admin是管理员可以查看所有的,测试
.taskCandidateOrAssigned("admin")
.list();
System.out.println("You have " + tasks.size() + " tasks:");
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
System.out.println((i + 1) + ") " + "taskId: " + task.getId() + ", taskName: " + task.getName());
}
}
/**
* 审批任务
* 说明:
* 1,变量approved同流程定义文件里面顺序流定义的变量
* 2,taskId是上一个获取用户任务的taskId值,也就是要指定哪一个用户任务往下执行
*/
@Test
void completeTask(){
Map<String, Object> variables = new HashMap<>();
variables.put("approved", true);
String taskId = "20008";
taskService.complete(taskId,variables);
}
/**
* 查看历史任务
* 说明:
* taskAssignee: 分配人
* finished:已完成状态的
*
*/
@Test
void historyTask(){
List<HistoricActivityInstance> hisList = historyService.createHistoricActivityInstanceQuery()
.taskAssignee("admin")
.finished()
.list();
hisList.stream().forEach(e -> System.out.println(JSONUtil.toJsonStr(e)));
}
}
1,部署流程
/**
* 模拟-部署流程
* 说明:
* 流程定义有版本的概念,bpmn文件有改动,需要部署后才生效
*/
@Test
void createDeployment() {
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("processes/holiday-request.bpmn20.xml")
.deploy();
System.out.println(deployment.getId());
System.out.println(JSON.toJSONString(deployment));
}
运行结果:
27501
ps: System.out.println(JSON.toJSONString(deployment));输出内容太多这里不贴上了
2,获取流程定义
/**
* 获取流程定义
*/
@Test
void getProcessDefinition() {
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId("17504")
.singleResult();
System.out.println("definitionId:" + processDefinition.getId());
System.out.println("definition:" + JSONUtil.toJsonStr(processDefinition));
}
运行结果:
definitionId:holidayRequest:5:17506
definition:{"name":"请假流程","key":"holidayRequest","version":5,"category":"http://www.flowable.org/processdef","deploymentId":"17504","resourceName":"processes/holiday-request.bpmn20.xml","tenantId":"","isGraphicalNotationDefined":false,"hasStartFormKey":false,"suspensionState":1,"derivedVersion":0,"id":"holidayRequest:5:17506","revision":1,"isInserted":false,"isUpdated":false,"isDeleted":false,"originalPersistentState":{"suspensionState":1,"category":"http://www.flowable.org/processdef"}}
3,启动流程
/**
* 启动流程
* 模拟用户发起一个请假流程
*/
@Test
void startProcessDefinition() {
Map<String, Object> variables = new HashMap<>();
variables.put("employee", "李四");
variables.put("nrOfHolidays", "4");
variables.put("description", "外出请假");
//启动流程实例有多个方法,这里调用流程key的方法来启动
ProcessInstance holidayProcessInstance = runtimeService.startProcessInstanceByKey("holidayRequest", variables);
System.out.println("processInstanceId:" + holidayProcessInstance.getProcessInstanceId());
}
运行结果:
processInstanceId:30001
4,获取用户任务
/**
* 获取任务
* 用户发起流程后,相关的人员能够查询该任务
*
*/
@Test
void getTask(){
List<Task> tasks = taskService.createTaskQuery()
.active()
.includeProcessVariables()
//值为admin是管理员可以查看所有的,测试
.taskCandidateOrAssigned("admin")
.list();
System.out.println("You have " + tasks.size() + " tasks:");
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
System.out.println((i + 1) + ") " + "taskId: " + task.getId() + ", taskName: " + task.getName());
}
}
运行结果:
You have 4 tasks:
1) taskId: 10008, taskName: 同意或驳回请求
2) taskId: 12508, taskName: 同意或驳回请求
3) taskId: 20008, taskName: 同意或驳回请求
4) taskId: 7508, taskName: 同意或驳回请求
5,审批任务
/**
* 审批任务
* 说明:
* 1,变量approved同流程定义文件里面顺序流定义的变量
* 2,taskId是上一个获取用户任务的taskId值,也就是要指定哪一个用户任务往下执行
*/
@Test
void completeTask(){
Map<String, Object> variables = new HashMap<>();
variables.put("approved", true);
String taskId = "20008";
taskService.complete(taskId,variables);
}
运行结果:
结果回调了 ApprovalSuccessDelegate ,打印出"审批通过了"
6,查看历史任务
/**
* 查看历史任务
* 说明:
* taskAssignee: 分配人
* finished:已完成状态的
*
*/
@Test
void historyTask(){
List<HistoricActivityInstance> hisList = historyService.createHistoricActivityInstanceQuery()
.taskAssignee("admin")
.finished()
.list();
hisList.stream().forEach(e -> System.out.println(JSONUtil.toJsonStr(e)));
}
运行结果:
{"activityId":"approveTask","activityName":"同意或驳回请求","activityType":"userTask","executionId":"20005","assignee":"admin","taskId":"20008","tenantId":"","processInstanceId":"20001","processDefinitionId":"holidayRequest:5:17506","startTime":1669521326282,"endTime":1669521596820,"durationInMillis":270538,"id":"20007","revision":2,"isInserted":false,"isUpdated":false,"isDeleted":false,"originalPersistentState":{"executionId":"20005","durationInMillis":270538,"endTime":1669521596820,"assignee":"admin","taskId":"20008"}}
{"activityId":"approveTask","activityName":"同意或驳回请求","activityType":"userTask","executionId":"30005","assignee":"admin","taskId":"30008","tenantId":"","processInstanceId":"30001","processDefinitionId":"holidayRequest:6:27503","startTime":1669643905412,"endTime":1669644033366,"durationInMillis":127954,"id":"30007","revision":2,"isInserted":false,"isUpdated":false,"isDeleted":false,"originalPersistentState":{"executionId":"30005","durationInMillis":127954,"endTime":1669644033366,"assignee":"admin","taskId":"30008"}}
最基本的完成一个流程的核心功能在上面列举了,先部署流程,再启动流程,再获取任务,再完成某一个任务。
六、总结
其实你会发现,流程引擎并没有那么神秘,它强大的功能,就是通过满足BPM2.0规范的xml文件和数据库进行流转的,后面文章在学习BPM2.0规范和Flowable数据库各个表的大致作用等。
以上代码已放到笔者的Gitee仓库地址:https://gitee.com/xiongbomy/spider-flowable.git
欢迎大家star和fork。
建议大家fork此项目到你个人仓库,方便你测试或者基于此开发属于你的流程引擎,当然,基于此进行二次扩展,再拿到公司当内部系统也是可以的,只要不商用拿去卖钱~
题外话,笔者在公司主要负责公司内部的流程引擎系统的开发,使用的是Flowable,后续会记录学习Flowable的笔记,一是让自己的知识形成体系化,二是让更多想要学习Flowable的同学们得到一点点帮助也好。