当前位置: 首页 > news >正文

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。

流程定义就像是类,流程实例则像是具体的类对象。
整个过程如下:

  1. 使用设计器设计的bpmn20.xml是流程模型,对流程模型进行部署,即得到了procdef流程定义,并保存到数据库。
  2. 对procdef流程定义进行startProcessInstanceById()启动,即可新建一个procinst流程实例并保存到数据库。
  3. 实例创建后,会推进到第一个审批节点并创建对应的Task保存到数据库。此时数据库里只有最新节点的Task。
  4. 对于当前的Task,执行complete()即可审批完成,这样当前Task会从数据库中删除,然后创建下一个节点的Task。
  5. 当所有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-namecom.mysql.jdbc.Drivercom.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_deploymentID_。核心属性有2个:ID_KEY_。分别表示流程定义ID和流程定义KEY。KEY是自定义的,需要人为保证唯一。
  • act_ge_bytearray :流程资源表,流程部署的 bpmn文件和png图片会保存在该表中。其DEPLOYMENT_ID_属性与act_re_deploymentID_属性相关联。

因此,部署后就与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_procdefID_属性。
  • 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-ac45ef0e17fd1312702c-613d-11f0-89e7-ac45ef0e17fd任务ID不同
NAME_人事审批经理审批任务名称不同
TASK_DEF_KEY_sid-1E848FE5-A27D-4327-9CDF-B50F1F2406FAsid-0FD5F025-98F1-4CBA-82B4-765834A6F1B8任务定义KEY不同
ASSIGNEE_zhangsanlisi任务负责人不同
CREATE_TIME_2025-07-1511:20:022025-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);

这样会做两步工作:

  1. Map中的变量添加到全局变量,并保存到ACT_RU_VARIABLE表中。
  2. 启动流程,需要创建Task,从ACT_RU_VARIABLE表中取到了${admin}变量值,从而顺利创建第一个Task。

对于taskService.complete(),考虑的不仅是当前节点,还需要考虑下一个Task。因为若下一Task的属性需要用表达式动态取值,那么表达式对应的变量必须已存在。
变量有两个赋值的时机:

  • runtimeService.startProcessInstanceById()时直接将变量存为全局变量。
  • 对前Task调用taskService.complete()时将下一Task所需的变量传入,存储为临时变量,绑定到当前执行实例Execution。

后者往往用于需要在审批过程中动态选择数据的场景,例如动态选择下一步的审批人。

容器类方法

同理,要完成第二个节点的审批,就必须让flowable读取到容器类Mastername()的返回值。
定义Master类,添加@Component放入容器,并实现getName()方法:

@Component
public class Master {public String getName() {System.out.println("执行 Master.getName()");return "wangwu";}
}![请添加图片描述](https://i-blog.csdnimg.cn/direct/f2084158844a42a4b8addab849935ccd.png)

然后直接调用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的接口来维护UserGroup及二者的关联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,包含账户相关的信息,如用户,用户组等。
http://www.xdnf.cn/news/1460269.html

相关文章:

  • 【c/c++】深度DFS
  • MATLAB平台实现人口预测和GDP预测
  • 美国教授提出的布鲁姆法,结合AI直击学术科研痛点,写作与创新效率直接翻倍!
  • 漫谈《数字图像处理》之实时美颜技术
  • Java并行计算详解
  • 解决 Rollup failed to resolve import “vue3-json-viewer/dist/index.css“ from xxx
  • 【Docker】P1 前言:容器化技术发展之路
  • JS本地存储
  • Java String vs StringBuilder vs StringBuffer:一个性能优化的探险故事
  • C++学习记录(6)string部分操作的模拟实现
  • push pop 和 present dismiss
  • Leetcode 206. 反转链表 迭代/递归
  • 拦截器和过滤器(理论+实操)
  • Websocket链接如何配置nginx转发规则?
  • NV169NV200美光固态闪存NV182NV184
  • 云数据库服务(参考自腾讯云计算工程师认证课程)更新中......
  • 阿里云 ESA 实时log 发送没有quta的解决
  • 【机器学习】HanLP+Weka+Java=Random Forest算法模型
  • 【CS32L015C8T6】配置单片机时基TimeBase(内附完整代码及注释)
  • Mysql杂志(九)
  • [frontend]WebGL是啥?
  • AI入坑: Trae 通过http调用.net 开发的 mcp server
  • 批量生成角色及动画-统一角色为Mixamo骨骼(一)
  • Qt实现2048小游戏:看看AI如何评估棋盘策略实现“人机合一
  • 对于数据结构:链表的超详细保姆级解析
  • Java Thread线程2—线程锁synchronized,Lock,volatile
  • Python学习3.0使用Unittest框架运行测试用例
  • 无人机防风技术难点解析
  • TDengine TIMETRUNCATE 函数用户使用手册
  • Netty从0到1系列之Buffer【下】