flowable基础入门
概念
flowable用于实现工作流,即定义并推动一个流程按节点进行。
flowable中有如下概念:
- process definition:流程定义。即定义了流程由哪些组成。
- process instance:流程实例。流程定义实例化后的对象,即一个具体的流程。一个流程定义可多次实例化,从而生成多个实例,代表同时有多个同类流程在进行。流程实例的生命周期随流程启动创建,随流程结束销毁。
- execution:执行实例。表示流程实例中当前正在执行的具体路径或分支节点,是运行时指针。对于线性流程,1个流程实例有1个执行实例;对于并行分支流程,多个分支同步推进,每个分支都会生成一个独立的执行实例。因此,1个流程实例可能有n个执行实例。执行实例的生命周期在流程流转中动态创建和销毁(如分支开始时创建,分支结束时销毁)。
- task:流程任务/审批节点。一个执行实例包含多个节点,除了开始和结束,其中间的审批节点均使用task表示。 注意Task是关联到执行实例的,而非直接关联流程实例。
流程实例和执行实例,本质上都是Execution
,都保存在ACT_RU_EXECUTION表中。该表是一个树形结构,包含PARENT_ID_
属性。流程实例就是一个流程的根节点,其PARENT_ID_
为null;而执行实例是流程实例的子节点,其PARENT_ID_
为流程实例ID。
流程定义就像是类,流程实例则像是具体的类对象。
整个过程如下:
- 使用设计器设计的bpmn20.xml是流程模型,对流程模型进行部署,即得到了procdef流程定义,并保存到数据库。
- 对procdef流程定义进行startProcessInstanceById()启动,即可新建一个procinst流程实例并保存到数据库。
- 实例创建后,会推进到第一个审批节点并创建对应的Task保存到数据库。此时数据库里只有最新节点的Task。
- 对于当前的Task,执行complete()即可审批完成,这样当前Task会从数据库中删除,然后创建下一个节点的Task。
- 当所有Task执行完成,流程实例执行到结束节点,整个流程结束。
所有的中间数据在历史记录表中都会有记录。
变量
flowable中有三种变量:全局变量、局部变量、临时变量。
全局变量绑定到流程实例(Process Instance),生命周期与流程实例一致。
局部变量绑定到执行实例(Execution)或任务(Task),生命周期与绑定的对象相同。
流程实例持有全局变量(跨节点共享)。
执行实例可持有局部变量(仅当前分支有效)。
同一作用域下变量名不可重复;不同作用域下变量名可重复。
局部变量名可与全局变量名相同,会优先读取局部变量。
同作用域下,多次设置同名变量,后设置的会覆盖先设置的。
维度 | 全局变量 (Process Variables) | 局部变量 (Local Variables) | 临时变量 (Transient Variables) |
---|---|---|---|
作用域 | 整个流程实例 (ProcessInstance ) | 特定任务 (Task ) 或执行实例 (Execution ) | 当前事务或会话 |
生命周期 | 随流程实例结束而终止 | 随任务/执行实例结束而终止 | 事务结束即消失 |
存储位置 | ACT_RU_VARIABLE (运行时)ACT_HI_VARINST (历史) | ACT_RU_VARIABLE (关联 TASK_ID_ 或 EXECUTION_ID_ ) | 内存,不持久化 |
变量名冲突 | 同流程实例下不可重复 | 不同作用域可同名(优先覆盖全局变量) | 无限制 |
典型场景 | 请假天数、流程状态 | 审批意见、分支临时数据 | 计算中间值、缓存 |
查询方式 | runtimeService.getVariables() | taskService.getVariableLocal() | execution.getTransientVariables() |
数据追溯 | 支持历史表长期审计 | 任务结束后仅历史表可查 | 不可追溯 |
性能影响 | 持久化操作,有数据库开销 | 持久化操作,作用域小开销较低 | 无IO操作,性能最优 |
注意区分使用场景:跨节点传递数据(如负责人、审批结果)时应使用全局变量;任务内部临时计算、隔离数据应使用局部变量。
因此,流程定义文件bpmn20.xml中如 ${admin}
这类表达式默认从全局变量解析,不能放在局部变量或临时变量中。
SpringBoot引入flowable
在pom.xml中引入依赖:
<dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter</artifactId><version>6.8.1</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.26</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.16</version>
</dependency>
然后在MySql数据库中创建一个空schema,命名为flowable
。
在工程的application.yml中添加配置:
spring:application:name: demodatasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/flowable?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456hikari:minimum-idle: 5idle-timeout: 600000maximum-pool-size: 10auto-commit: truepool-name: MyHikariCPmax-lifetime: 1800000connection-timeout: 30000connection-test-query: SELECT 1flowable:async-executor-activate: false # 关闭定时任务# database-schema-update 设置为 true ,则当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构更新至新版本database-schema-update: true
注意MySql的驱动(driver-class-name
)需要根据安装的MySql进行调整。当安装的MySQL版本小于8.0时,driver-class-name
应使用com.mysql.jdbc.Driver
;对于8.0及以上版本,driver-class-name
应使用com.mysql.cj.jdbc.Driver
。
这样flowable就引入完成了。启动工程,会发现数据库flowable
创建了许多相关表。
SpringBoot这样引入的flowable会将ProcessEngine及四个模块的Service都添加到容器对象,需要使用时可直接注入:
@Resource
private ProcessEngine processEngine;
@Resource
private RepositoryService repositoryService;
@Resource
private RuntimeService runtimeService;
@Resource
private TaskService taskService;
@Resource
private HistoryService historyService;
ProcessEngine
是最核心的流程引擎,四个模块的Service对象除了这样注入,也可以通过ProcessEngine
获取。
下文中默认使用这里注入的对象。
使用
流程设计
可使用官方提供的flowable-UI来设计流程。
6.x版本的Release是包含flowable-UI的,下载地址:
https://github.com/flowable/flowable-engine/releases
注意需选择与pom.xml中引入依赖相同版本的release下载。
下载后解压,包含一个wars文件夹,里面包含两个文件:flowable-rest.war和flowable-ui.war。
下载Tomcat的zip版本并解压(注意不要放在中文目录下),将flowable-ui.war放到*\apache-tomcat-9.0.107\webapps*目录下:
- 打开*\apache-tomcat-9.0.107\conf\logging.properties*,找到
java.util.logging.ConsoleHandler.encoding = UTF-8
,将其值改为GBK
。 - 打开*\apache-tomcat-9.0.107\bin\startup.bat*,Tomcat控制台会运行,等待执行完成,这样flowable-ui.war会解压。
关闭Tomcat控制台,打开*\apache-tomcat-9.0.107\webapps\flowable-ui\WEB-INF\classes\flowable-default.properties*,找到如下内容:
#
# DATABASE
##spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:~/flowable-db/engine-db;AUTO_SERVER=TRUE;AUTO_SERVER_PORT=9093;DB_CLOSE_DELAY=-1#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.url=jdbc:mysql://127.0.0.1:3306/flowable?characterEncoding=UTF-8#spring.datasource.driver-class-name=org.postgresql.Driver
#spring.datasource.url=jdbc:postgresql://localhost:5432/flowable#spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
#spring.datasource.url=jdbc:sqlserver://localhost:1433;databaseName=flowablea#spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
#spring.datasource.url=jdbc:oracle:thin:@localhost:1521:FLOWABLE#spring.datasource.driver-class-name=com.ibm.db2.jcc.DB2Driver
#spring.datasource.url=jdbc:db2://localhost:50000/flowablespring.datasource.username=flowable
spring.datasource.password=flowable
在这里指定flowable-UI的数据库及用户名和密码。
注意若修改为MySql:
- 需要根据运行环境的MySQL版本设置
spring.datasource.driver-class-name
为com.mysql.jdbc.Driver
或com.mysql.cj.jdbc.Driver
。 spring.datasource.url
根据实际情况修改。- 找到mysql的驱动
mysql-connector-java-8.0.26.jar
,复制到\apache-tomcat-9.0.107\lib
目录下。
在flowable-default.properties中可以找到访问的默认设置:
server.port=8080
server.servlet.context-path=/flowable-ui
flowable.idm.app.admin.user-id=admin
flowable.idm.app.admin.password=test
因此启动成功后访问:
http://127.0.0.1:8080/flowable-ui/
输入用户名admin
,密码test
即可登录。
登录后进入建模器应用程序,在这里可以创建流程并在线编辑。
注意模型key,应人为保证全局唯一,支持使用该值查询流程。
这里建一个请假流程holiday,发起人发起后需经过人事审批和经理审批:
其中:
- 启动事件,发起人写
malou
。 - 添加一个审批节点,名称设置为
人事审批
,点击分配用户,选择固定用户,填写zhangsan
。 - 添加一个审批节点,名称设置为
经理审批
,点击分配用户,选择固定用户,填写lisi
。 - 添加结束事件。
注意核心的审批节点需要指定分配用户
属性,即谁负责该节点的审批。
模型创建完成后,可点击下载按钮来导出到BPMN2。
下载后,得到一个holiday.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:flowable="http://flowable.org/bpmn" 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" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef" exporter="Flowable Open Source Modeler" exporterVersion="6.8.1"><process id="holiday" name="holiday" isExecutable="true"><documentation>holiday</documentation><startEvent id="startEvent1" flowable:initiator="malou" flowable:formFieldValidation="true"></startEvent><userTask id="sid-1E848FE5-A27D-4327-9CDF-B50F1F2406FA" name="人事审批" flowable:assignee="zhangsan" flowable:formFieldValidation="true"><extensionElements><modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete></extensionElements></userTask><sequenceFlow id="sid-2C451B03-25B3-4CAC-8813-23D7A3481F85" sourceRef="startEvent1" targetRef="sid-1E848FE5-A27D-4327-9CDF-B50F1F2406FA"></sequenceFlow><userTask id="sid-0FD5F025-98F1-4CBA-82B4-765834A6F1B8" name="经理审批" flowable:assignee="lisi" flowable:formFieldValidation="true"><extensionElements><modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete></extensionElements></userTask><sequenceFlow id="sid-EA047001-C851-4A8C-9630-D46A735F0D2B" sourceRef="sid-1E848FE5-A27D-4327-9CDF-B50F1F2406FA" targetRef="sid-0FD5F025-98F1-4CBA-82B4-765834A6F1B8"></sequenceFlow><endEvent id="sid-5391D82A-12EB-4085-9170-CDBE2F4703F0"></endEvent><sequenceFlow id="sid-0DD79C83-B371-4A61-A06B-EAAE98477AC0" sourceRef="sid-0FD5F025-98F1-4CBA-82B4-765834A6F1B8" targetRef="sid-5391D82A-12EB-4085-9170-CDBE2F4703F0"></sequenceFlow></process><bpmndi:BPMNDiagram id="BPMNDiagram_holiday"><bpmndi:BPMNPlane bpmnElement="holiday" id="BPMNPlane_holiday"><bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1"><omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="sid-1E848FE5-A27D-4327-9CDF-B50F1F2406FA" id="BPMNShape_sid-1E848FE5-A27D-4327-9CDF-B50F1F2406FA"><omgdc:Bounds height="80.0" width="100.0" x="175.0" y="138.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="sid-0FD5F025-98F1-4CBA-82B4-765834A6F1B8" id="BPMNShape_sid-0FD5F025-98F1-4CBA-82B4-765834A6F1B8"><omgdc:Bounds height="80.0" width="100.0" x="320.0" y="138.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="sid-5391D82A-12EB-4085-9170-CDBE2F4703F0" id="BPMNShape_sid-5391D82A-12EB-4085-9170-CDBE2F4703F0"><omgdc:Bounds height="28.0" width="28.0" x="465.0" y="164.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNEdge bpmnElement="sid-2C451B03-25B3-4CAC-8813-23D7A3481F85" id="BPMNEdge_sid-2C451B03-25B3-4CAC-8813-23D7A3481F85" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0"><omgdi:waypoint x="129.9499984899576" y="178.0"></omgdi:waypoint><omgdi:waypoint x="174.9999999999917" y="178.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="sid-EA047001-C851-4A8C-9630-D46A735F0D2B" id="BPMNEdge_sid-EA047001-C851-4A8C-9630-D46A735F0D2B" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0"><omgdi:waypoint x="274.9499999999907" y="178.0"></omgdi:waypoint><omgdi:waypoint x="319.9999999999807" y="178.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="sid-0DD79C83-B371-4A61-A06B-EAAE98477AC0" id="BPMNEdge_sid-0DD79C83-B371-4A61-A06B-EAAE98477AC0" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0"><omgdi:waypoint x="419.95000000000005" y="178.0"></omgdi:waypoint><omgdi:waypoint x="465.0" y="178.0"></omgdi:waypoint></bpmndi:BPMNEdge></bpmndi:BPMNPlane></bpmndi:BPMNDiagram>
</definitions>
生命周期
部署流程
将holiday.bpmn20.xml复制到工程的resource文件夹下。
然后调用方法来部署流程:
@Test
public void deploy() {// 流程部署Deployment deploy = repositoryService.createDeployment().addClasspathResource("holiday.bpmn20.xml").name("请假流程").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}
部署完成后,会有3个表新增数据:
- act_re_deployment::流程定义部署表,每部署一次就增加一条记录。
- act_re_procdef :流程定义表(process definitionI),部署每个新的流程定义都会在这张表中增加一条记录。其ID_是由多部分使用
:
连接组成:xml中<process>
的id
+act_re_procdef的REV_
+act_re_deployment的ID_
。核心属性有2个:ID_
和KEY_
。分别表示流程定义ID和流程定义KEY。KEY是自定义的,需要人为保证唯一。 - act_ge_bytearray :流程资源表,流程部署的 bpmn文件和png图片会保存在该表中。其
DEPLOYMENT_ID_
属性与act_re_deployment的ID_
属性相关联。
因此,部署后就与bpmn源文件无关了,每次启动流程用的都是存在数据库里的文件。
可以看到本次部署的流程定义ID为595f9ade-6124-11f0-892b-ac45ef0e17fd
。
一次部署,可部署多个流程定义。也就是流程定义中多条数据的DEPLOYMENT_ID_
可能是相同的。
部署完成后可进行查询:
@Test
public void getProcessDefinition() {ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId("595f9ade-6124-11f0-892b-ac45ef0e17fd").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.getId() = " + processDefinition.getId());
}
也可进行删除:
repositoryService.deleteDeployment("595f9ade-6124-11f0-892b-ac45ef0e17fd", true);
流程启动
当流程启动时,会创建一个流程实例,对应地在act_hi_procinst表中添加一条记录,该记录即实例的唯一记录,也是实例的历史记录。当实例在执行过程中有任何状态的变化,都会将改动同步到该条记录中。
而实例具体的执行是借助Task节点来进行的,实例当前的待办节点保存在act_ru_task表中。
要启动流程实例有两种方法:
- RuntimeService.startProcessInstanceById():根据流程定义ID启动。即act_re_procdef的
ID_
属性。 - RuntimeService.startProcessInstanceByKey():根据流程定义key启动。即创建模型时人为填入的key。
推荐ID启动的方式。
@Test
public void startProcess() {// 启动流程ProcessInstance holiday = runtimeService.startProcessInstanceById("holiday:1:59dd0841-6124-11f0-892b-ac45ef0e17fd");System.out.println("holiday.getProcessDefinitionId() = " + holiday.getProcessDefinitionId());System.out.println("holiday.getActivityId() = " + holiday.getActivityId());System.out.println("holiday.getId() = " + holiday.getId());
}
RuntimeService.startProcessInstanceById()
可以携带参数,从而实现自定义的处理。
启动后,会在act_ru_task中创建一条数据来存储待办,在act_ru_execution会记录流程的执行记录,在act_hi_procinst表中保存历史记录。
这里为了方便查看,将生成的act_ru_task待办数据转成json:
{"table": "flowable.act_ru_task","data": {"ID_": "984bcb04-612a-11f0-a013-ac45ef0e17fd","REV_": 1,"EXECUTION_ID_": "98498110-612a-11f0-a013-ac45ef0e17fd","PROC_INST_ID_": "984959fe-612a-11f0-a013-ac45ef0e17fd","PROC_DEF_ID_": "holiday:1:59dd0841-6124-11f0-892b-ac45ef0e17fd","TASK_DEF_ID_": null,"SCOPE_ID_": null,"SUB_SCOPE_ID_": null,"SCOPE_TYPE_": null,"SCOPE_DEFINITION_ID_": null,"PROPAGATED_STAGE_INST_ID_": null,"NAME_": "人事审批","PARENT_TASK_ID_": null,"DESCRIPTION_": null,"TASK_DEF_KEY_": "sid-1E848FE5-A27D-4327-9CDF-B50F1F2406FA","OWNER_": null,"ASSIGNEE_": "zhangsan","DELEGATION_": null,"PRIORITY_": 50,"CREATE_TIME_": "2025-07-15 11:20:02","DUE_DATE_": null,"CATEGORY_": null,"SUSPENSION_STATE_": 1,"TENANT_ID_": "","FORM_KEY_": null,"CLAIM_TIME_": null,"IS_COUNT_ENABLED_": 1,"VAR_COUNT_": 0,"ID_LINK_COUNT_": 0,"SUB_TASK_COUNT_": 0}
}
其中有个属性ASSIGNEE_
存储该待办指派的审批人。
若要查询某流程下张三所有的待办:
@Test
public void listTask() {List<Task> list = taskService.createTaskQuery().processDefinitionId("holiday:1:59dd0841-6124-11f0-892b-ac45ef0e17fd") // 流程Id.taskAssignee("zhangsan") // 任务处理人.list();for(Task task : list) {System.out.println("task.getId() = " + task.getId());System.out.println("task.getAssignee() = " + task.getAssignee());System.out.println("task.getName() = " + task.getName());System.out.println("task.getDescription() = " + task.getDescription());System.out.println("task.getProcessDefinitionId() = " + task.getProcessDefinitionId());}
}
查询条件可根据实际情况调整。
通常地,张三是系统的登录用户,使用该方法来查询张三所有的待办事项并列出,由张三选择其中一项进行处理。
流程审批
一个Task对应一个审批节点,而在整个流程中通常会有多个审批节点。
当流程执行到某个节点时,创建该节点的Task。当该节点审批完成,删除该节点Task的数据,并创建下一节点的Task。当整个流程的所有节点都审批完毕,那么该流程最后一个task被删除,且不再创建新的Task。 也就是说对于上面这种简单的线性流程,同一时间只会有一个Task。而整个流程从开始到结束会创建并删除2个Task。当流程执行完成,所有Task均会被删除。
调用TaskService.complete()
来完成审批:
@Test
public void taskComplete() {Task task = taskService.createTaskQuery().processDefinitionId("holiday:1:59dd0841-6124-11f0-892b-ac45ef0e17fd").taskAssignee("zhangsan").singleResult();// 完成任务taskService.complete(task.getId());
}
由于本次测试只启动了一个流程,从而只创建了一个Task,所以这里可以这样查询。
调用该方法后,该流程当前的节点Task完成,act_ru_task中对应的记录会删除,并针对下一个节点Task创建一条新的记录:
{"table": "flowable.act_ru_task","data": {"ID_": "1312702c-613d-11f0-89e7-ac45ef0e17fd","REV_": 1,"EXECUTION_ID_": "98498110-612a-11f0-a013-ac45ef0e17fd","PROC_INST_ID_": "984959fe-612a-11f0-a013-ac45ef0e17fd","PROC_DEF_ID_": "holiday:1:59dd0841-6124-11f0-892b-ac45ef0e17fd","TASK_DEF_ID_": null,"SCOPE_ID_": null,"SUB_SCOPE_ID_": null,"SCOPE_TYPE_": null,"SCOPE_DEFINITION_ID_": null,"PROPAGATED_STAGE_INST_ID_": null,"NAME_": "经理审批","PARENT_TASK_ID_": null,"DESCRIPTION_": null,"TASK_DEF_KEY_": "sid-0FD5F025-98F1-4CBA-82B4-765834A6F1B8","OWNER_": null,"ASSIGNEE_": "lisi","DELEGATION_": null,"PRIORITY_": 50,"CREATE_TIME_": "2025-07-15 13:32:18.861","DUE_DATE_": null,"CATEGORY_": null,"SUSPENSION_STATE_": 1,"TENANT_ID_": "","FORM_KEY_": null,"CLAIM_TIME_": null,"IS_COUNT_ENABLED_": 1,"VAR_COUNT_": 0,"ID_LINK_COUNT_": 0,"SUB_TASK_COUNT_": 0}
}
对比一下前后两个Task的差异:
字段名 | 第一个Task | 第二个Task | 差异说明 |
---|---|---|---|
ID_ | 984bcb04-612a-11f0-a013-ac45ef0e17fd | 1312702c-613d-11f0-89e7-ac45ef0e17fd | 任务ID不同 |
NAME_ | 人事审批 | 经理审批 | 任务名称不同 |
TASK_DEF_KEY_ | sid-1E848FE5-A27D-4327-9CDF-B50F1F2406FA | sid-0FD5F025-98F1-4CBA-82B4-765834A6F1B8 | 任务定义KEY不同 |
ASSIGNEE_ | zhangsan | lisi | 任务负责人不同 |
CREATE_TIME_ | 2025-07-1511:20:02 | 2025-07-1513:32:18.861 | 创建时间不同 |
此时查询出第二个Task并再次调用TaskService.complete()
来完成第二个Task的审批,act_ru_task中对应的记录会删除。但下一节点就是流程结束了,所以不会再创建新的节点Task。
于是整个流程到这里就结束了。
正常地,是由前端用户从所有列出的Task中选择一个,将其ID传到服务端来进行审批。
对于已完成的流程,可查询该流程的历史记录:
@Test
public void listHistoricActivity() {List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery().processDefinitionId("holiday:1:59dd0841-6124-11f0-892b-ac45ef0e17fd").finished() // 查询的历史记录的状态是已经完成.orderByHistoricActivityInstanceEndTime().asc() // 指定按结束时间排序.list();for(HistoricActivityInstance history : list) {System.out.println(history.getActivityName()+":"+history.getAssignee()+"--"+history.getActivityId()+":"+history.getDurationInMillis()+"毫秒");}
}
动态处理
当调用RuntimeService.startProcessInstanceById()
或TaskService.complete()
时,若流程节点数据中以${}
形式定义了变量,那么flowable会尝试采用各种形式对变量进行解析:
- 已定义的变量
- 容器类
现在,使用flowable-UI新建一个holiday流程,包含两个节点:人事、经理。这两个节点的分配用户都选择固定值,并分别填入:
${admin}
:变量。${master.name()}
:容器类方法。为了与变量区分,必须添加()
。也可以写作${master.name}
。
已定义的变量
按步骤部署流程并启动。但此时会报错,因为启动时会创建第一个Task,但第一个Task的审批人是${admin}
,系统并不知道这个参数是多少。
此时定义一个Map
,放入admin
属性,并作为参数传给startProcessInstanceById()
:
Map<String, Object> variables = new HashMap<>();
variables.put("admin", "lisi");// 启动流程
ProcessInstance holiday = runtimeService.startProcessInstanceById("holiday:1:fed5a751-6153-11f0-9bdf-ac45ef0e17fd", variables);
这样会做两步工作:
- 将
Map
中的变量添加到全局变量,并保存到ACT_RU_VARIABLE
表中。 - 启动流程,需要创建Task,从
ACT_RU_VARIABLE
表中取到了${admin}
变量值,从而顺利创建第一个Task。
对于taskService.complete()
,考虑的不仅是当前节点,还需要考虑下一个Task。因为若下一Task的属性需要用表达式动态取值,那么表达式对应的变量必须已存在。
变量有两个赋值的时机:
- 在
runtimeService.startProcessInstanceById()
时直接将变量存为全局变量。 - 对前Task调用
taskService.complete()
时将下一Task所需的变量传入,存储为临时变量,绑定到当前执行实例Execution。
后者往往用于需要在审批过程中动态选择数据的场景,例如动态选择下一步的审批人。
容器类方法
同理,要完成第二个节点的审批,就必须让flowable读取到容器类Master
的name()
的返回值。
定义Master
类,添加@Component
放入容器,并实现getName()
方法:
@Component
public class Master {public String getName() {System.out.println("执行 Master.getName()");return "wangwu";}
}
然后直接调用complete()
,无需额外传参:
Task task = taskService.createTaskQuery().processDefinitionId("holiday:1:fed5a751-6153-11f0-9bdf-ac45ef0e17fd").singleResult();// 完成任务
taskService.complete(task.getId());
这样flowable会从容器类中直接读取对应属性。
监听器
监听器即当事件触发时,对流程数据进行处理。
首先在工程中创建一个监听器,从TaskListener
派生:
import org.flowable.task.service.delegate.DelegateTask;
import org.flowable.task.service.delegate.TaskListener;public class MyListener implements TaskListener {@Overridepublic void notify(DelegateTask delegateTask) {System.out.println("进入MyListener:" + delegateTask.getEventName());if (EVENTNAME_CREATE.equals(delegateTask.getEventName())) {delegateTask.setAssignee("zhaoliu");}}
}
在flowable-UI设计时,选中一个节点,点击并设置其任务监听器:
新建一个事件,事件类型选择create
,类设置为添加的监听类。
这样,当前一个节点调用TaskService.complete()
时,无需额外传参,flowable创建新节点时会自动触发create
事件。
挂起和激活
挂起和激活分两种情况:
- 流程定义的挂起和激活
- 流程实例的挂起和激活
流程定义的挂起和激活
当一个流程定义暂时不再使用时,可对其进行挂起;当重新启用时,可对其进行激活。
act_re_procdef.SUSPENSION_STATE_
为挂起状态,其中1表示激活,2表示挂起。
/*** 切换流程的挂起状态*/
@Test
public void switchProcessSuspended(){String processDefinitionId = "holiday:1:59dd0841-6124-11f0-892b-ac45ef0e17fd";// 流程定义ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();// 当前的流程是否挂起boolean suspended = processDefinition.isSuspended();if(suspended) {// 当前流程挂起,进行微活System.out.println("激活流程");repositoryService.activateProcessDefinitionById(processDefinitionId);} else {// 当前流程激活,进行挂起System.out.println("挂起流程");repositoryService.suspendProcessDefinitionById(processDefinitionId);}
}
一个流程挂起后:
- 无法启动该流程来创建新的实例。
- 挂起前启动的实例不受影响,可以正常推进。
流程实例的挂起和激活
当一个流程实例需要暂停时,可对其进行挂起;当需要恢复正常时,可对其进行激活。
但在flowable中,实例仅仅做一个状态的记录,流程推动是通过Task实现的,所以挂起和激活状态实际是保存在Task记录中,所以要查询一个实例是否挂起,需要先获取该实例的Task,然后查询Task的状态。
act_ru_task.SUSPENSION_STATE_
为挂起状态,其中1表示激活,2表示挂起。
但实际挂起的确实是实例,因此虽然挂起状态是通过Task查询,挂起/激活操作却是针对实例进行。
@Test
public void switchTaskSuspensionState(){String processDefinitionId = "holiday:1:59dd0841-6124-11f0-892b-ac45ef0e17fd";// 流程实例节点Task task = taskService.createTaskQuery().processDefinitionId(processDefinitionId).singleResult();// 流程实例节点是否挂起boolean suspended = task.isSuspended();if(suspended) {// 实例节点挂起,所以实例挂起,对实例进行微活System.out.println("激活实例");runtimeService.activateProcessInstanceById(task.getProcessInstanceId());} else {// 实例节点激活,所以实例激活,对实例进行挂起System.out.println("挂起实例");runtimeService.suspendProcessInstanceById(task.getProcessInstanceId());}
}
审批人和候选人
一个Task,只能由审批人(assignee)进行审批。
在使用flowable-UI设计流程模型的时候,可以将Task的审批人留空,同时指定多个候选人。
候选人不是审批人,候选人也不能对Task进行审批。
多个候选人之中有且仅有一个可通过拾取操作taskService.claim(taskId, userId)
变为审批人。这样Task就可以由该审批人进行审批。
拾取操作是可逆的,可以通过taskService.unclaim(taskId)
来将Task的审批人重新变为候选人,这样其他候选人就可以变为审批人。
一个Task的审批人无论是否存在,都可以调用taskService.setAssignee(taskId, userId)
对Task的审批人进行重新指派。
用户和用户组
flowable的数据库中包含用户和用户组表。
可以调用IdentityService
的接口来维护User
、Group
及二者的关联Membership
。
一个User
关联了Group
之后,可以通过identityService
来查询Group
的信息。
而借助Group
又能查询所有关联的User
信息。
因此flowable-UI设计流程模型的时候,可以将Task的审批人留空,同时指定多个候选组Group
。这样通过候选组即可查到所有的候选人,然后其中一个进行拾取操作变为审批人,就回到了正常的流程中。
网关
网关就是流程产生分支或汇聚的交点。
网关有四类:排他网关、并行网关、包容网关、事件网关。
网关涉及到两个概念:
- 分支:从此处将一个输入分为多个输出。所有网关都有分支。
- 汇聚:从此处将多个输入合为一个输出。排他网关/事件网关无汇聚;并行网关/包容网关有汇聚。
分支和汇聚若都存在,则应成对出现,即并行网关的分支和汇聚都应是并行网关;包容网关的分支和汇聚都应是包容网关。
场景
- 排他网关:适用于二选一或多选一的决策场景(如审批流程)。
- 并行网关:适用于需要同时执行多个任务的场景(如并行审批、分阶段任务)。
- 包容网关:适用于部分条件满足即可继续的场景(如多角色任选其一处理)。
- 事件网关:适用于依赖外部事件触发的场景(如超时处理、消息通知)。
判断
网关的判断是写在分支上的,而非网关节点。
前缀含义
flowable共有5类前缀:
- ACT_RE:RE表示repository,即资源文件,如流程定义和流程静态资源(图片,规则,等等)。
- ACT_RU:RU表示 runtime,即运行时表,如流程实例、任务、变量、异步任务等。运行时表中的数据都是临时的,在流程执行过程中进行保存,当流程结束后就会删除。
- ACT_HI:HI示 history,即历史数据表,如历史流程实例,变量,任务等。
- ACT GE:GE 表示 general,即通用数据。
- ACT_ID:ID表示identity,包含账户相关的信息,如用户,用户组等。