工作流包括业务流和审批流等业务流程。
在一个流程系统中,任务间往往存在复杂的依赖关系,为保证pipeline的正确执行,就是要解决各任务间依赖的问题,这样DAG结合拓扑排序是解决存在依赖关系的一类问题的利器。DAG ( Directed Acyclic Graph),有向无环图,是指任意一条边有方向,且不存在环路的图。如果把依赖关系的问题建模成 DAG, 依赖关系成为 Graph 中的 Directed Edge, 然后通过拓扑排序,不断遍历和剔除无依赖的节点(无前置节点的节点),可以达到快速解决依赖的目的。
Flowable 工作流使用
Flowable 项目中包括 BPMN(Business Process Model and Notation)引擎、CMMN(Case Management Model and Notation)引擎、DMN(Decision Model and Notation)引擎、表单引擎(Form Engine)等模块。也有许多Flowable 应用(Flowable Modeler、Flowable Admin、Flowable IDM 与 Flowable Task),并提供了直接可用的 UI 示例。
官方文档:https://tkjohn.github.io/flowable-userguide/#_introduction
0.首先需要初始化ProcessEngine流程引擎实例。这是一个线程安全的对象,因此通常只需要在一个应用中初始化一次。 ProcessEngine由ProcessEngineConfiguration实例创建。该实例可以配置与调整流程引擎的设置。
通常使用一个配置XML文件创建ProcessEngineConfiguration,如果是springboot项目直接在yml中配置数据源即可,启动时会自动实例化到ioc容器中,无需flowable.cfg.xml文件。 ProcessEngineConfiguration所需的最小配置,是数据库JDBC连接.
比如通过xml文件创建:可以在 resources 目录下创建一个 flowable.cfg.xml 文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="processEngineConfiguration" class="org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/flow1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC&nullCatalogMeansCurrent=true" /><property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="123456" />
<property name="databaseSchemaUpdate" value="true" />
<property name="asyncExecutorActivate" value="false" />
</bean>
</beans>
然后就可以直接获取流程引擎对象了: ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
Springboot集成
1.pom.xml添加依赖
<!-- SpringBoot整合flowable -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.6.0</version>
</dependency>
2.application.yml 配置数据源
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/flowable-boot?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
flowable:
async-executor-activate: false # 关闭定时任务
# 将databaseSchemaUpdate 设置为 true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本
database-schema-update: true
3.配置好后直接启动,服务就会自动初始化数据库,增加需要的表。所有的Flowable服务都暴露为Spring bean
@Resource
private ProcessEngine processEngine; // // 直接注入ProcessEngine 对象
// 以下四个模块的服务接口可以直接注入使用,也可以通过ProcessEngine获取
@Resource
private RepositoryService repositoryService;
@Resource
private RuntimeService runtimeService;
@Resource
private TaskService taskService;
@Resource
private HistoryService historyService;
另外resources/processes目录下的任何BPMN 2.0流程定义都会被自动部署。
resources/cases目录下的任何CMMN 1.1事例都会被自动部署。
resources/forms目录下的任何Form定义都会被自动部署。
4.创建bpmn20.xml文件
在resources下创建processes目录,并在其中创建示例流程定义(命名为holiday-request.bpmn20.xml) .bpmn20.xml是文件后缀名
开始事件:图中用细线圆圈来表示,是流程实例的开始点
箭头:表示节点之间的流转指向。
用户任务: 在图中用左上角有人的圆角矩形表示,这些是需要用户来操作的节点。图中有两个,第一个表示需要经理进行审批来同意或拒绝,第二个表示用户来确认销假。
排它网关: 用叉形符号填充的菱形表示,从该图中出来的箭头往往有多个,但只有一个满足条件,流程会沿着满足条件的方向流转。
自动化任务 :左上角有齿轮形状的的圆角矩形,表示自动执行的节点。图中上面的表示请假被经理同意后自动注册通知到外部系统,下面的表示请假被经理拒绝后自动发邮件通知给申请人。
结束事件: 图中用粗线圆圈表示,表示流程的结束。图中上面的结束事件表示请假成功结束,下面的表示请假失败结束。
一般来说,这样的流程定义使用可视化建模工具建立,如Flowable Designer(Eclipse)或 flowable-ui-modeler(flowable-ui Web应用),保存后可得到bpmn20.xml格式文件
FlowableUI 流程设计器
Flowable 提供了几个 web 应用,用于演示及介绍 Flowable 项目提供的功能:
Flowable IDM: 身份管理应用。为所有 Flowable UI 应用提供单点登录认证功能,并且为拥有IDM管理员权限的用户提供了管理用户、组与权限的功能。
Flowable Modeler: 让具有建模权限的用户可以创建流程模型、表单、选择表与应用定义。
Flowable Task: 运行时任务应用。提供了启动流程实例、编辑任务表单、完成任务,以及查询流程实例与任务的功能。
Flowable Admin: 管理应用。让具有管理员权限的用户可以查询 BPMN、DMN、Form 及 Content 引擎,并提供了许多选项用于修改流程实例、任务、作业等。管理应用通过 REST API 连接至引擎,并与 Flowable Task 应用及 Flowable REST 应用一同部署。
所有其他的应用都需要 Flowable IDM 提供认证。每个应用的 WAR 文件可以部署在相同的 servlet 容器(如Apache Tomcat)中,也可以部署在不同的容器中。由于每个应用使用相同的 cookie 进行认证,因此应用需要运行在相同的域名下。
可以使用docker快速部署。
Flowable UI默认的登录用户名是 admin,默认的登录密码是 test。
resources/processes/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"/>
<userTask id="approveTask" name="Approve or reject request"/>
<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="org.flowable.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="org.flowable.SendRejectionMail"/>
<sequenceFlow sourceRef="sendRejectionMail" targetRef="rejectEnd"/>
<endEvent id="approveEnd"/>
<endEvent id="rejectEnd"/>
</process>
</definitions>
(1)每一个步骤(在BPMN 2.0术语中称作活动(activity))都有一个id属性,为其提供一个在XML文件中唯一的标识符。所有的活动都可以设置一个名字,以提高流程图的可读性。
(2)活动之间通过顺序流(sequence flow)连接,在流程图中是一个有向箭头。在执行流程实例时,执行(execution)会从启动事件沿着顺序流流向下一个活动。
(3)离开排他网关(带有X的菱形)的顺序流很特别:都以表达式(expression)的形式定义了条件(condition) 。当流程实例的执行到达这个网关时,会计算条件,并使用第一个计算为true的顺序流。这就是排他的含义:只选择一个。当然如果需要不同的路由策略,可以使用其他类型的网关。
这里用作条件的表达式为${approved},这是${approved == true}的简写。变量’approved’被称作流程变量(process variable)。流程变量是持久化的数据,与流程实例存储在一起,并可以在流程实例的生命周期中使用。
(4).在这个例子里,我们需要在特定的地方(当用户任务提交时,或者以Flowable的术语来说,完成(complete)时)设置这个流程变量,因为这不是流程实例启动时就能获取的数据。
相关api操作
在Flowable中,数据库事务扮演了关键角色,用于保证数据一致性,并解决并发问题。当调用Flowable API时,默认情况下,所有操作都是同步的,并处于同一个事务下。
常用的几个JAVA class类介绍:
l ProcessDefinition
这个最好理解,就是流程的定义,也就相当于规范,每个 ProcessDefinition 都会有一个 id。
l ProcessInstance
这个就是流程的一个实例。简单来说,ProcessDefinition 相当于是类,而 ProcessInstance 则相当于是根据类 new 出来的对象。
l Activity
Activity 是流程标准规范 BPMN2.0 里面的规范,流程中的每一个步骤都是一个 Activity。
l Execution
Execution 的含义是流程的执行线路,通过 Execution 可以获得当前 ProcessInstance 当前执行到哪个 Activity了
l Task
Task 就是当前要做的工作。
5.部署流程定义(ProcessDefinition)
这里因为是放到了resources/processes目录下,所以启动项目时就自动部署了,无需在手动加载.bpmn20.xml并部署了。
将流程定义部署至Flowable引擎, 并可以通过API查询验证流程定义是否已经部署在引擎中:
@Resource
private ProcessEngine processEngine;
// 获取 RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// deploy()完成流程的实际部署操作,会创建一个ProcessDefinition流程定义
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("xxxx.bpmn20.xml")
.name("请假流程")
.deploy();
//删除部署的流程
repositoryService.deleteDeployment("45001", true); // true 会将流程下的任务也一并删除
//查询流程定义是否已部署到引擎中
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId("35001")
.singleResult();
// processDefinition.getDeploymentId()、getName()、getDescription()、getId()
6.启动流程实例[ProcessInstance]
启动流程实例,需要提供一些初始化流程变量。一般来说,可以通过呈现给用户的表单,或者在流程由其他系统自动触发时通过REST API,来获取这些变量。这个流程实例使用key启动。这个key就是BPMN 2.0 XML文件中设置的id属性
在流程实例启动后,会创建一个执行(Execution),并将其放在启动事件上。从这里开始,这个执行沿着顺序流移动到审批的用户任务,并执行用户任务行为。用户任务是一个等待状态(wait state),引擎会停止执行,返回API调用处。通过taskService.complete()来完成用户任务的执行。
// 获取启动流程的服务
RuntimeService runtimeService = processEngine.getRuntimeService();
// 构建流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("employee", "wsy");
variables.put("nrOfHolidays", 10);
variables.put("description", "生病了,去医院");
// 启动流程,getActivityId() 可以查看现在在流程的哪一个步骤(Activity)中
ProcessInstance holidayRequest = runtimeService.startProcessInstanceByKey("holidayRequest", variables); //"holidayRequest"是.bpmn20.xml中 <process>的id
//holidayRequest.getProcessDefinitionId()、 getActivityId()、 getId()
7.执行任务Task
//查看流程任务Task:
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
.processDefinitionId("holidayRequest:1:45003") // 流程ID
//.processDefinitionKey("holidayRequest") // 流程key
.taskAssignee("王经理") // 任务处理人
//.list();列出所有任务
.singleResult();
//task.getId() 、task.getAssignee()、getName()、getProcessDefinitionId()
//执行任务
Map<String, Object> map = new HashMap<>();
map.put("approved", true);
// 完成任务
taskService.complete(task.getId(), map);
8.查看历史信息
HistoryService historyService = processEngine.getHistoryService();
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
.processDefinitionId("holidayRequest:1:45003")
.finished() // 查询的历史记录的状态是已经完成
.orderByHistoricActivityInstanceEndTime().asc() // 指定按结束时间排序
.list();
for(HistoricActivityInstance history : list) {
System.out.println(history.getActivityName()+":"+history.getAssignee()+"--"+history.getActivityId()+":"+history.getDurationInMillis()+"毫秒");
}