Flowable是什么
-
Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据,等等。这个章节将用一个可以在你自己的开发环境中使用的例子,逐步介绍各种概念与API。
-
Flowable可以十分灵活地加入你的应用/服务/构架。可以将JAR形式发布的Flowable库加入应用或服务,来嵌入引擎。 以JAR形式发布使Flowable可以轻易加入任何Java环境:Java SE;Tomcat、Jetty或Spring之类的servlet容器;JBoss或WebSphere之类的Java EE服务器,等等。 另外,也可以使用Flowable REST API进行HTTP调用。也有许多Flowable应用(Flowable Modeler, Flowable Admin, Flowable IDM 与 Flowable Task),提供了直接可用的UI示例,可以使用流程与任务。
-
所有使用Flowable方法的共同点是核心引擎。核心引擎是一组服务的集合,并提供管理与执行业务流程的API。 下面的教程从设置与使用核心引擎的介绍开始。后续章节都建立在之前章节中获取的知识之上。
-
官网使用手册
链接: https://tkjohn.github.io/flowable-userguide/#_getting_started_2
下载使用flowableUI
目的:在flowableUI中画bpmn20图,生成bpmn20.xml文件
百度网盘下载
链接:https://pan.baidu.com/s/1EKOSIl9ZVUpF0VGmte0utw?pwd=wd0s
提取码:wd0s
启动flowableUI
解压开后复制flowable-6.7.2\wars目录下的两个war包
将war包放入tomcat的webapps目录下
启动tomcat:在bin目录下点击startup.bat
flowableUI使用(只操作创建bpmn20.xml文件)
- 进入建模器应用程序—>创建流程
- 创建流程
- 分配用户
- 点击固定值–>分配填入用户–>例如张三–>另一个usertask以此类推
- 保存
- 下载bpmn20.xml,点击显示详情信息–>导出bomn2–>结束
Flowable项目使用
创建工程
通过idea创建或者eclipse创建一个maven工程
引入依赖
- 需要添加两个依赖:
Flowable流程引擎。使我们可以创建一个ProcessEngine流程引擎对象,并访问Flowable API。
数据库驱动依赖。生成flowable流程表
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-engine</artifactId>
<version>6.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
引入测试包
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
如果控制台没有详细的日志的话,可以使用log4j作为slf4j的实现
- 在pom添加依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
- 在src/main/resources文件夹下添加log4j.properties文件,并写入下列内容:
log4j.rootLogger=DEBUG, CA
log4j.appender.CA=org.apache.log4j.ConsoleAppender
log4j.appender.CA.layout=org.apache.log4j.PatternLayout
log4j.appender.CA.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n
RepositoryService、RuntimeService、TaskService、HistoryService的使用
创建流程引擎
- 首先要做的就是初始化ProcessEngine流程引擎实例。这是一个线程安全的对象,因此通常只需要在一个应用中初始化一次。ProcessEngine由ProcessEngineConfiguration实例创建。该实例可以配置与调整流程引擎的设置。通常使用一个配置xml文件创建ProcessEngineConfiguration,也可以通过编程方式创建它。
- 新建Test方法:
/**
* 获取流程引擎对象
*/
@Test
public void testProcessEngine() {
//获取ProcessEngineConfiguration对象
ProcessEngineConfiguration processEngineConfiguration = new StandaloneProcessEngineConfiguration();
//配置相关的数据库信息
processEngineConfiguration.setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn?serverTimezone=UTC")
.setJdbcUsername("root")
.setJdbcPassword("123456")
.setJdbcDriver("com.mysql.cj.jdbc.Driver");
//如果数据库中表结构不存在就新建
processEngineConfiguration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
//通过ProcessEngineConfiguration 构建我们需要的ProcessEngine对象
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
}
- 执行该程序,控制台不出错的话,就是操作完成了。查看下数据库,生成了34张act表。
部署流程定义
- 我们要构建的流程是一个非常简单的请假流程。Flowable引擎需要流程定义为BPMN 2.0格式,这是一个业界广泛接受的XML标准。 在Flowable术语中,我们将其称为一个流程定义(process definition)。一个流程定义可以启动多个流程实例(process instance)。流程定义可以看做是重复执行流程的蓝图。 在这个例子中,流程定义定义了请假的各个步骤,而一个流程实例对应某个雇员提出的一个请假申请。
- BPMN 2.0存储为XML,并包含可视化的部分:使用标准方式定义了每个步骤类型(人工任务,自动服务调用,等等)如何呈现,以及如何互相连接。这样BPMN 2.0标准使技术人员与业务人员能用双方都能理解的方式交流业务流程。
- 我们要使用的流程定义为:
来自官网文档图
- 简单的说明一下这个流程:
- 假定启动流程需要提供一些信息,例如雇员名字、请假时长以及说明。当然,这些可以单独建模为流程中的第一步。但是如果将他们作为流程的"输入信息",就能保证只有在实际请求时才会建立一个流程实例。否则将提交作为流程的第一步,用户可能在提交之前改变主意并取消,但流程实例已经创建了。在某些场景中,就可能影响重要的指标(例如提交了多少申请,但未完成),取决于业务目标。
- 图中左侧的圆圈叫做启动事件(start event)。这是一个流程实例的起点。
第一个矩形是一个用户任务(user task)。这是流程中用户操作的步骤。在这里例子中,后续经理需要批准或者驳回用户的申请。 - 带叉的菱形是一个排他网关(exclusive gateway)。后面还有其他的网关介绍。这里的排他网关取决于经理的操作,然后将流程实例转至批准或者驳回路径。
- 如果批准,则通过另一个用户任务(user task),将经理的决定通知给申请人。当然也可以是别的通知方式(发邮件等)。
- 如果驳回,可以直接发邮件通知申请人。
- 这样的流程定义使用可视化建模工具建立,如Flowable Designer(Eclipse)或Web应用(flowableUI)。
- 与上面展示的流程图对应的BPMN 2.0 XML在下面显示。请注意这只包含了“流程部分”。如果使用图形化建模工具,实际的XML文件还将包含“可视化部分”,用于描述图形信息,如流程定义中各个元素的坐标(所有的图形化信息包含在XML的BPMNDiagram标签中,作为definitions标签的子元素)。
- 在src/main/resources文件夹下创建名为holiday-request.bpmn20.xml的文件,这个名字随意,这里当前需要设计的流程定义的,请假流程申请
<?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="Holiday Request" isExecutable="true">
<startEvent id="startEvent"/>
<sequenceFlow sourceRef="startEvent" targetRef="approveTask"/>
<!--flowable:assignee userTask审批人-->
<userTask id="approveTask" name="Approve or reject request" flowable:assignee="lisi"/>
<sequenceFlow sourceRef="approveTask" targetRef="decision"/>
<exclusiveGateway id="decision"/>
<sequenceFlow sourceRef="decision" targetRef="externalSystemCall">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${approved}
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="decision" targetRef="sendRejectionMail">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${!approved}
]]>
</conditionExpression>
</sequenceFlow>
<serviceTask id="externalSystemCall" name="Enter holidays in external system"
flowable:class="com.flowable.demo.CallExternalSystemDelegate"/>
<sequenceFlow sourceRef="externalSystemCall" targetRef="holidayApprovedTask"/>
<userTask id="holidayApprovedTask" name="Holiday approved"/>
<sequenceFlow sourceRef="holidayApprovedTask" targetRef="approveEnd"/>
<serviceTask id="sendRejectionMail" name="Send out rejection email"
flowable:class="com.flowable.demo.SendRejectionMail"/>
<sequenceFlow sourceRef="sendRejectionMail" targetRef="rejectEnd"/>
<endEvent id="approveEnd"/>
<endEvent id="rejectEnd"/>
</process>
</definitions>
-
xml文件介绍
- 每一个步骤(在BPMN 2.0术语中称作活动(activity))都有一个id属性,为其提供一个在XML文件中唯一的标识符。所有的活动都可以设置一个名字,以提高流程图的可读性。
- 活动之间通过**顺序流(sequence flow)**连接,在流程图中是一个有向箭头。在执行流程实例时,执行(execution)会从启动事件沿着顺序流流向下一个活动。
- 离开排他网关(带有X的菱形)的顺序流很特别:都以表达式(execution)的形式定义了条件(condition)。当流程实例的执行到达这个网关时,会计算条件,并使用第一个计算为true的顺序流。这就是排他的含义:只选择一个。当然如果有需要不同的策略,也可以使用其他类型的网格。
- 这里用作条件的表达式为a p p r o v e d ∗ ,这是 ∗ {approved},这是*approved∗,这是∗{approved == true}*的简写。变量approved被称作流程变量(process variable)。流程变量是持久化的数据,与流程实例存储在一起并可以在流程实例的生命周期中使用。在这个例子中,我们需要在特定的地方(当经理用户任务提交时,或者以Flowable的术语来说,完成(complete)时)设置这个流程变量,因为这不是流程实例启动时就能获取的数据。
-
现在我们已经有了流程BPMN 2.0 XML文件,下来需要将它***部署(deploy)***到引擎中。部署一个流程定义意味着:
- 流程引擎会将XML文件存储在数据库中,这样可以在需要的时候获取它。
- 流程定义转换为内部的、可执行的对象模型,这样使用它就可以启动流程实例。
-
将流程定义部署至Flowable引擎,需要使用*RepositoryService,其可以从ProcessEngine对象获取。使用RepositoryService,可以通过XML文件的路径创建一个新的部署(Deployment),并调用deploy()*方法实际执行:
- 测试类中添加测试部署方式:
private ProcessEngineConfiguration processEngineConfiguration;
/**
* 初始化封装下,不然每个测试方法都要写,很麻烦
*/
@Before
public void init() {
//获取ProcessEngineConfiguration对象
processEngineConfiguration = new StandaloneProcessEngineConfiguration();
//配置相关的数据库信息
processEngineConfiguration.setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn?serverTimezone=UTC")
.setJdbcUsername("root")
.setJdbcPassword("123456")
.setJdbcDriver("com.mysql.cj.jdbc.Driver");
//如果数据库中表结构不存在就新建
processEngineConfiguration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
}
/**
* 部署流程
* RepositoryService介绍:
* 查询引擎现有的部署与流程定义。
* 暂停或激活部署中的某些流程,或整个部署。暂停意味着不能再对它进行操作,激活刚好相反,重新使它可以操作。
* 获取各种资源,比如部署中保存的文件,或者引擎自动生成的流程图。
* 获取POJO版本的流程定义。它可以用Java而不是XML的方式查看流程。
*/
@Test
public void deployment() {
//获取ProcessEngine对象
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
//获取部署接口
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("holiday-request.bpmn20.xml")
.name("请假流程")
.deploy();
System.out.println("deploy.getId():" + deploy.getId());
System.out.println("deploy.getName():" + deploy.getName());
System.out.println("deploy.getKey():" + deploy.getKey());
System.out.println("deploy.getCategory():" + deploy.getCategory());
System.out.println("deploy.getTenantId():" + deploy.getTenantId());
}
- key,name自己根据业务定义
deploy.getId():70001
deploy.getName():请假流程
deploy.getKey():null
deploy.getCategory():null
deploy.getTenantId():
-
查看下数据库:
- act_re_deployment:流程定义部署表,没部署一次就会增加一条记录
- 这里的ID=67501,就是我们代码部署生成的记录
- act_re_procdef:流程定义表,部署新的流程定义就会新增一条记录,表中DELOYMENT_ID就是act_re_deployment的主键ID
- act_ge_bytearray:流程资源表,流程部署的bpmn文件和png文件都会存在该表
-
现在可以通过API查询验证流程定义已经部署在引擎中(并学习一些API)。通过RepositoryService创建的ProcessDefinitionQuery对象实现。
//查询流程定义的信息
@Test
public void queryDeployment() {
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId("67501")
.singleResult();
System.out.println("processDefinition.getDeploymentId() = " + processDefinition.getDeploymentId());
System.out.println("processDefinition.getName() = " + processDefinition.getName());
System.out.println("processDefinition.getDescription() = " + processDefinition.getDescription());
System.out.println("processDefinition.getKey() = " + processDefinition.getKey());
System.out.println("processDefinition.getId() = " + processDefinition.getId());
}
processDefinition.getDeploymentId() = 70001
processDefinition.getName() = Holiday Request
processDefinition.getDescription() = null
processDefinition.getKey() = holidayRequest
processDefinition.getId() = holidayRequest:1:70003
启动流程实例
-
现在已经在流程引擎中部署了流程定义,因此可以使用这个流程定义作为“蓝图”启动流程实例。
-
一个部署成功的流程定义可以启动多个流程实例,就好比请假申请单,是可以多个员工都可以去填写的。
-
要启动流程实例,通常需要提供一些初始化流程变量。一般来说,可以通过呈现给用户的表单,或者在流程由其他系统自动触发时通过REST API,来获取这些变量。这样才能达到一个比较全面的数据交互。
-
接下来,我们使用RuntimeService启动一个流程实例。收集的数据作为一个java.util.Map实例传递,其中的键就是之后用于获取变量的标识符。这个流程实例使用key启动。这个key就是BPMN 2.0 XML文件中设置的id属性,在这个例子里是holidayRequest,也就是act_re_procdef表中的KEY_字段
//启动流程实例
@Test
public void startProcess() {
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
//获取启动对象
RuntimeService runtimeService = processEngine.getRuntimeService();
//初始化流程变量,这些变量实际是从页面表单传过来的,员工填写请假申请单的一些相关表单数据
Map<String, Object> var = new HashMap<>();
var.put("employee", "张三");
var.put("description", "累了想请假");
//启动流程实例
//holidayRequest就是流程定义的ID,在xml或者`act_re_procdef`的`KEY_`字段都能查到
ProcessInstance holidayRequest = runtimeService.startProcessInstanceByKey("holidayRequest", var);
System.out.println("holidayRequest.getProcessDefinitionId() = " + holidayRequest.getProcessDefinitionId());
}
-
执行成功后,观察数据库表:
act_ru_task:任务信息表,会新增一条任务记录 -
act_ru_variable:流程实例的流程变量信息
可以看到代码中,map存放的变量数据 -
act_ru_execution:流程执行的过程信息
-
act_hi_procinst:流程实例历史信息
-
act_hi_taskinst:流程任务历史信息
-
act_hi_actinst:流程实例执行历史
查询任务
-
在实际的应用中,会为用户提供界面化操作,让他们可以登录并查看任务列表。可以看到作为流程变量存储的流程实例数据,并决定后续如何操作。现在通过API的方式调用查询任务列表。
-
在开始的创建的holiday-request.bpmn20.xml中并没有为用户任务配置处理人,即某个员工发起了请假流程申请,但是后续并没有领导去审核。我们在这里需要加上任务的处理人
<!--在审核任务的节点上加上处理人,通过flowable:assignee属性设置,目前是直接写死的,后续可以动态设置-->
<userTask id="approveTask" name="Approve or reject request" flowable:assignee="lisi"/>
- 流程实例执行完成后,去act_ru_task任务表去查看ASSIGNEE_字段,可以看到当前任务的处理人就是刚刚flowable:assignee设置的
- 查询lisi的任务:
/**
* 查询任务
*/
@Test
public void queryTask() {
//先获取ProcessEngine对象
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
//获取TaskService对象
TaskService taskService = processEngine.getTaskService();
// 查询任务
List<Task> taskList = taskService
//创建查询
.createTaskQuery()
//根据流程定义查询
.processDefinitionKey("holidayRequest")
//根据任务人查询
.taskAssignee("lisi")
.list();
for (Task task : taskList) {
System.out.println("task.getProcessDefinitionId() = " + task.getProcessDefinitionId());
System.out.println("task.getId() = " + task.getId());
System.out.println("task.getAssignee() = " + task.getAssignee());
System.out.println("task.getName() = " + task.getName());
}
}
- 查询结果
task.getProcessDefinitionId() = holidayRequest:1:70003
task.getId() = 72507
task.getAssignee() = lisi
task.getName() = Approve or reject request
完成任务
- 上面流程已经发起成功了,任务成功流转到lisi来处理,现在通过lisi来完成任务,确定任务的下一步走向
-
按照事先定好的流程图,下一步操作是通过排他网关处理,这里暂时不用管网关具体是什么,我们只需要这一步经理lisi的操作需要通过网关来流转到不同的走向,这里目前有两个走向,通过和拒绝,我们先拒绝这个任务
-
在流程的xml文件可以看到serviceTask中flowable:class=com.flowable.demo.SendRejectionMail,这个意思是,当我们拒绝后会触发Send out rejection email 这个任务标签的实现类,这个类需要我们自己定义然后去实现JavaDelegate
-
定义一个这样的类:flowable:class的路径及类根据自己的创建的来设置,这里就按照com.flowable.demo.SendRejectionMail来创建了
-
package com.flowable.demo;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
public class SendRejectionMail implements JavaDelegate {
/**
* flowable中的触发器
* @param delegateExecution
*/
@Override
public void execute(DelegateExecution delegateExecution) {
//触发执行的逻辑 发送邮件
System.out.println("SendRejectionMail:被拒绝了");
}
}
- list完成任务:
/**
* 完成任务
*/
@Test
public void completeTask() {
//获取ProcessEngine对象
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
//再获取lisi的任务
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processDefinitionKey("holidayRequest").taskAssignee("lisi").singleResult();
//完成该任务,同时添加该任务的流程变量信息
Map<String, Object> variables = new HashMap<>();
//这个approved变量就是流程里面排他网关节点设置的,通过给这个流程变量值后判断流程的下一步走向
variables.put("approved", false);//这里false是拒绝
//完成任务,complete需要的参数:任务ID,流程节点中的流程变量
taskService.complete(task.getId(), variables);
}
- 执行代码,可以看到拒绝流程自定义的JavaDelegate触发了
- 按照当前的流程设计,拒绝之后,流程也就结束了,再去查看act_ru_task表已经没有该任务数据了
历史任务
-
当流程处于执行过程中,或者已经执行结束了,我们都可以查询到该流程实例的历史数据文章来源:https://www.toymoban.com/news/detail-802666.html
-
可以通过ProcessEngine获取HistoryService创建历史活动查询。文章来源地址https://www.toymoban.com/news/detail-802666.html
/**
* 流程实例的历史任务
*/
@Test
public void historyTask() {
//获取ProcessEngine对象
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
//获取historyService
HistoryService historyService = processEngine.getHistoryService();
List<HistoricActivityInstance> activityInstances = historyService
.createHistoricActivityInstanceQuery()
//按流程实例ID查询,这个流程定义是启动流程后有的
.processDefinitionId("holidayRequest:1:70003")
//查询已完成的
.finished()
//按照结束时间排序
.orderByHistoricActivityInstanceEndTime().asc()
.list();
for (HistoricActivityInstance activityInstance : activityInstances) {
System.out.println(historicActivityInstance.getActivityId() + ":" + historicActivityInstance.getActivityName() +
":" + historicActivityInstance.getAssignee() + ":" + historicActivityInstance.getDurationInMillis() + "毫秒");
}
}
startEvent:null:null:2毫秒
approveTask:Approve or reject request:lisi:9388974毫秒
decision:null:null:15毫秒
sendRejectionMail:Send out rejection email:null:10毫秒
rejectEnd:null:null:2毫秒
删除任务
/**
* 删除任务
*/
@Test
public void deleteTask() {
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
TaskService taskService = processEngine.getTaskService();
taskService.deleteTask("xxx");
}
删除流程
- 当流程不需要的时候可以将其删除
/**
* 删除流程定义
*/
@Test
public void deleteProcess() {
//获取ProcessEngine对象
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
//获取RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
//删除流程定义
//根据流程部署ID删除,如果流程未启动可以直接通过流程部署ID删除,否则流程启动后该删除会报异常
//根据流程部署ID删除,同时设置级联删除,true就是表示级联删除,即使流程已经启动也会删除成功及所关联的数据
repositoryService.deleteDeployment("xxx", true);
}
流程挂起与激活
//流程的挂起和激活
@Test
public void suspendedProcess() {
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
//获取对应的流程定义信息
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionId("holidayRequest:1:70003")
.singleResult();
//获取当前的流程定义的 状态信息
boolean suspended = processDefinition.isSuspended();
if (suspended) {
//当前流程被挂起了
//激活流程
System.out.println("激活流程" + processDefinition.getId() + ":" + processDefinition.getName());
repositoryService.activateProcessDefinitionById("holidayRequest:1:70003");
} else {
//当前流程是激活状态
//挂起当前流程
repositoryService.suspendProcessDefinitionById("holidayRequest:1:70003");
System.out.println("挂起流程" + processDefinition.getId() + ":" + processDefinition.getName());
}
}
到了这里,关于工作流Flowable入门教程:flowableUI的安装使用,RepositoryService、RuntimeService、TaskService、HistoryService的使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!