最近工作需要使用Acticity框架处理审批业务,简单了解后能虽能很快的上手,但是对于Activity的整体认识并不够,特此花费很多精力全面的学习并记录。包含对很多的概念的第一次理解过程;对知识点的混淆地方的梳理;对实践过程中出现的bug;以及个人的一些思考。因此本文可以当作一个简易版Activirty学习工具书,可以收藏本文,按需查阅。
因为是初次接触,部分理解可能会存在歧义或不恰当的地方,诚邀批评指正!
Acativity是一个专门负责处理工作流程的框架。比如,上班时间需要申请一个病假,这个工作流程就是:创建病假申请单----->部门领导审批--------->经理审批------------>人事领导审批。那现在让我们实现这个业务,我们该怎么做呢?
我们会定义一个状态值,0代表在创建病假申请单处,1代表在部门领导审批处,2代表在经理审批处,3代表在人事领导审批处。然后实现各自的业务逻辑等等等。
实际上会发现,如果有的领导请假了;如果有很多个工作流程,有的处理完了,有的处理一半,是不是所有的资料此时就特别乱,根本无法梳理清楚,很难维护。因此,Activity框架诞生了,专门帮助我们处理这种情况。借助这个框架,就能很清楚的设计多种工作流,查看各自的执行过程,谁负责什么都会十分清晰。
好比让你直接去盖一栋楼,你很迷茫。现在给你一个框架,你可以管理建筑材料,建筑工人,建筑部门,查看各工种的干活状态。
参考资料,B站up:波哥是个憨憨
pom.xml文件
<dependencies>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-engineartifactId>
<version>5.22.0version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-springartifactId>
<version>5.22.0version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-bpmn-modelartifactId>
<version>5.22.0version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-bpmn-converterartifactId>
<version>5.22.0version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-json-converterartifactId>
<version>5.22.0version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-bpmn-layoutartifactId>
<version>5.22.0version>
<exclusions>
<exclusion>
<groupId>org.tinyjee.jgraphxgroupId>
<artifactId>jgraphxartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.activiti.cloudgroupId>
<artifactId>activiti-cloud-services-apiartifactId>
<version>7-201710-EAversion>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.28version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.6version>
dependency>
<dependency>
<groupId>commons-dbcpgroupId>
<artifactId>commons-dbcpartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
dependencies>
文件路径:/resources/activitis.cfg.xml
(activitis.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 class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration" id="processEngineConfiguration">
<property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql:///activiti?characterEncoding=utf-8&serverTimezone=UTC"/>
<property name="jdbcUsername" value="root"/>
<property name="jdbcPassword" value="1234"/>
<property name="databaseSchemaUpdate" value="true"/>
bean>
beans>
文件路径:/resources/log4j.properties
log4j.rootLogger=TRACE, stdout, R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
#log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
# Print the date in ISO 8601 format
#log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
#下面的是制定输出到打印台格式的内容
log4j.appender.stdout.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c -%m%n
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=example.log
log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.layout=org.apache.log4j.PatternLayout
#log4j.appender.R.layout.ConversionPattern=[%p] %t %c - %m%n -%d{yyyy-MM-dd HH:mm}
log4j.appender.R.layout.ConversionPattern=[%p] %t %d{yyyy-MM-dd HH:mm:ss,SSS} %c - {%m} %n
# Print only messages of level WARN or above in the package com.foo.
log4j.logger.com.foo=WARN
public class Test01 {
/**
* 生成Activiti的相关表结构
*/
@Test
public void test01(){
//目标:生成ProcessEngine的对象
//原理: 使用classpath下的activitis.cfg.xml中的配置创建ProcessEngine的对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
System.out.println(engine);
}
}
根据表名前缀分类
act_re:re表示 repository,这个前缀的表包含了流程定义和流程静态资源 (图片、规则、等等)
act_ru:ru 表示 runtime,这些表运行时,会包含流程实例、任务、变量、异步任务等流程业务进行中的数据。Activiti 只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。这样表就可以一直保持很小的体积,并且速度很快
act_hi:hi表示 history,这些表包含一些历史数据,比如历史流程实例、变量、任务等等
act_ge:ge 表示 general,通用数据表示存储一些通用数据。
我们已经清楚了activity引擎的作用:可以帮助我们处理工作流程。进一步通过分析创建的表,可以确定一个概念:若activity引擎执行一个工作流,内部一定是多个模块协助进行的。例如需要
的协助配合,才能完成一个工作流。
暂时不需要特别清楚这些内部这些模块有什么作用,先确认内部是有这些模块在默默协作的。那么身为程序员,我们想要人工去操作管理Activity资源,管理Activity流程运行,管理Activity任务等,应该如何操作?
其实很方便,框架将这些模块定义成接口,调用接口,就可以操作这些模块了。例如
每个接口中定义了很多的方法,需要哪个就调用哪个。例如我需要删除部署的Activity引擎,发现RepositoryService 接口中有个deleteDeployment(String var1)方法就可以实现
这五个常用的接口,基本涵盖了activity提供的主要方法。意思就是你需要使用activity干什么,都可以在这五个接口中找到定义好的方法,不需要你自己创建一个接口,然后实现业务,因为没必要。
这五个接口的命名有个共同点,都是XXXXXXService,因此我们统称为Service服务接口。
OK,以上就是希望对Service服务接口有个理解足矣。接下来简单说明一下,应该如何正确使用Service服务接口?
在目录2.5中,我们创建了ProcessEngine的对象---->engine
public class Test01 {
/**
* 生成Activiti的相关表结构
*/
@Test
public void test01(){
//目标:生成ProcessEngine的对象
//原理: 使用classpath下的activitis.cfg.xml中的配置创建ProcessEngine的对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
System.out.println(engine);
}
}
(目标:应该如何正确使用Service服务接口)
观察一下engine对象,发现内部提供了很多get方法。这些get方法是什么呢?
可以看到,这些get方法返回值都是接口类型
如getRepsoitoryService()方法的返回值是 RepsoitoryService接口类型
而前面我们知道RepsoitoryService接口中内置了很多方法,如deleteDeployment() -------删除Acativity引擎部署(如下图)
梳理一下:
getRepsoitoryService() 方法的返回值是 RepsoitoryService 接口
RepsoitoryService 接口中有deleteDeployment()
岂不是意味着 ,只要得到getRepsoitoryService() 返回值,就可以得到 deleteDeployment()
//使用RepsoitoryService对象A 接收 getRepsoitoryService()返回值
RepsoitoryService A = getRepsoitoryService();
//然后就可以调用使用RepsoitoryService的方法了
A.deleteDeployment();
-------------------
//那可以合成一句代码
getRepsoitoryService().deleteDeployment();
//其实惯性思维希望
engine.RepsoitoryService.deleteDeployment();
//但是RepsoitoryService是接口,无法直接调用,所以声明成一个方法
RepsoitoryService getRepsoitoryService()
//这样再看这行代码,就有思路了
engine.getRepsoitoryService().deleteDeployment();
上文,我们解释了getRepsoitoryService()方法从何而来?deleteDeployment()方法从何而来?如何这些方法如何使用?
简单介绍一下各个 Service 的实现类:
Activiti 的资源管理类,该服务负责部署流程定义,管理流程资源。在使用 Activiti 时,一开始需要先完成流程部署,即将使用建模工具设计的业务流程图通过 RepositoryService 进行部署
Activiti 的流程运行管理类,用于开始一个新的流程实例,获取关于流程执行的相关信息。流程定义用于确定一个流程中的结构和各个节点间行为,而流程实例则是对应的流程定义的一个执行,可以理解为 Java 中类和对象的关系
Activiti 的任务管理类,用于处理业务运行中的各种任务,例如查询分给用户或组的任务、创建新的任务、分配任务、确定和完成一个任务
Activiti 的历史管理类,可以查询历史信息。执行流程时,引擎会保存很多数据,比如流程实例启动时间、任务的参与者、完成任务的时间、每个流程实例的执行路径等等。这个服务主要通过查询功能来获得这些数据
Activiti 的引擎管理类,提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护
环境:idea 2018 插件actiBPM (此插件只能在2019版本之前搜索到)
审批者 | 负责内容 |
---|---|
zhangsan | 创建申请单 |
lisi | 经理审批 |
wangwu | 总经理审批 |
zjh | 财务审批 |
这里的流程定义,可以理解为创建一个出差申请模版,任何人只要需要出差,就必须申请一份出差申请单,这个出差申请单的证明周期包括:创建申请单-经理审批-总经理审批-财务审批。
文件名:Test01
/**
* 启动Activity部署
*/
@Test
public void test02(){
//前面多次解释一定要获取ProcessEngine对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//获取RepositoryService服务(包含创建部署,启动部署,删除部署等)
RepositoryService service = engine.getRepositoryService();
Deployment deploy = service.createDeployment() //创建部署
.addClasspathResource("bpmn/demo.bpmn") //添加bnmn资源
.name("出差申请流程") //
.deploy(); //部署流程
//流程部署的ID
System.out.println(deploy.getId());
//流程部署的名称
System.out.println(deploy.getName());
}
首次启动会出现一个bug或者乱码错误,解决方案链接
Activity引擎启动报错: 3 字节的 UTF-8 序列的字节 3 无效 及乱码 完美解决方案
- act_re_deployment:定义部署表,每部署一次就增加一条记录
- act_re_procdef:定义流程表,每部署一个新的流程就增加一条记录
- act_ge_bytearray:流程资源表。流程部署的bpmn文件和png图片会保存在该表中
至此,第五章节简单介绍了 创建bpmn文件---->启动activity引擎 的过程,并对运行出现的问题提出解决方案。
流程实例的意思是,用户A,用户B 两人需要出差,那么这两个人都需要得到一份出差申请单。
如何得到呢
所以流程实例是具有全生命周期的,不是只有创建申请单,或者只有经理审批,而是整个流程。可以想像成为了一个类而创建的实例。
/**
* 启动流程实例
*/
@Test
public void test04(){
//创建ProcessEngine对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//获取运行服务对象
RuntimeService runtimeService = engine.getRuntimeService();
//根据流程自定义的id启动流程
String id = "evection";
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(id);
//输出相关实例
System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例的ID:" + processInstance.getId());
System.out.println("当前活动的ID:" + processInstance.getActivityId());
}
解释一下这一部分内容:
这个evection是哪来的,是画图的时候定义的
其实也可以不写 String id = “evection”;
而是直接写成ProcessInstance processInstance = runtimeService.startProcessInstanceByKey( “evection”);
这是可以的。那为什么这里要多此一举呢?
首先是为了保险,如果我们有很多个流程图,这里不强调的话,很难确保启动的是不是指定的流程图。
实际上,无论何种方式你传入这个参数,执行的时候底层会拿着你传入的这个参数和你绘制图的时候定义的id比较,找不到的话抛出异常。
解释一下这几个ID
流程部署ID:创建好了出差申请单模版,只要启动部署一次,就会生成一个ID(如何启动参考5.1.3章节)
因此会出现这个情况
流程部署ID | 流程部署的名称 |
---|---|
52501 | 出差申请流程 |
52321 | 出差申请流程 |
52388 | 出差申请流程 |
56009 | 出差申请流程 |
流程定义ID:
流程定义ID | 流程定义的名称 |
---|---|
evection:1:52504 | 出差的申请单 |
流程实例ID:
为什么是zhangsan呢,因为shangsan负责:创建申请单,而创建申请单是第一个步骤。所以是zhangsan创建的流程实例。
就像前面说的,这个流程实例不是只含有【shangsan 创建申请单】,而是包括全生命周期,【 zhangsan 创建申请单-lisi 经理审批- wangwu 总经理审批-zjh 财务审批】。只不够shangsan在第一个,所以该流程实例它命名。
流程实例ID | 流程实例的名称 |
---|---|
55001 | shangsan |
当前活动ID:
整个流程一共需要4个步骤(这里不统计启动和结束),执行到了第一个步骤
当前活动ID | 当前活动的名称 |
---|---|
_3 | 创建申请单 |
我们想知道当前任务的情况
/**
* 查询任务
*/
@Test
public void test05(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
List<Task> list = taskService.createTaskQuery().processDefinitionKey("evection").taskAssignee("shangsan").list();
for (Task task : list) {
System.out.println("流程定义ID:" + task.getProcessDefinitionId());
System.out.println("当前任务名称:" + task.getName());
System.out.println("当前任务ID:" + task.getId());
System.out.println("当前任务负责人:" + task.getAssignee());
}
}
不知道能都理解这里的当前任务ID,任务负责人是 shangsan,他负责第一步骤:创建申请单,这一步骤的ID是:55004
上面的流程实例ID的前面结束过了,流程实例的名字是也shangsan哦
/**
* 执行任务流程到下一级
*/
@Test
public void test06(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
Task task = taskService.createTaskQuery()
.processDefinitionKey("evection")
.taskAssignee("shangsan")
.singleResult(); //单个结果
//完成当前任务(shangsan创建请假单)
taskService.complete(task.getId());
}
运行后,任务就到了经理审批
不信的话,我们看看记录历史任务执行步骤的表
将5.3代码的shangsan修改为lisi,查询一下当前任务
可以发现,多了一个流程,是因为我们多启动了一次部署
删除部署,就是删除这个东西
/**
* 删除部署
*/
@Test
public void test03(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService service = engine.getRepositoryService();
//若已经有实例启动,则会报错
//52501已经创建了实例
service.deleteDeployment("52501");
//即使有实例启动,也会删除(true:级联删除 false:非级联删除)
//service.deleteDeployment("52501",true);
}
service.deleteDeployment("60001");
因为虽然启动部署得到了60001,但是没有启动它的流程实例
强制删除会成功
service.deleteDeployment("60001",true);
假设一个场景,我们本地现在什么资源都没有,根本无法单独学习企业的activity项目。这些资源在哪里呢,在企业的activity数据库呢。所以我们需要连接企业的数据库,从数据库下载对应的 .bnmn 文件和绘制的流程 .png 图片
/**
* 读取数据库资源文件
*/
@Test
public void test07() throws IOException {
//1.创建 ProcessEngine对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//2.创建 存储服务对象
RepositoryService repositoryService = engine.getRepositoryService();
ProcessDefinition definition = repositoryService.createProcessDefinitionQuery() //获取查询器
.processDefinitionKey("evection") //根据流程定义名字
.singleResult(); // 单个结果
//3.获取名为evection的流程部署的ID
String deploymentId = definition.getDeploymentId();
//4.借助 存储服务对象,获取图片和Bpmn文件
InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, definition.getDiagramResourceName());
InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, definition.getResourceName());
//5.文件的保存
File filePng = new File("e:/evection.png"); //保存路径
File fileBpmn = new File("e:/evection.bpmn");
OutputStream outPng = new FileOutputStream(filePng);
OutputStream outBpmn = new FileOutputStream(fileBpmn);
//6.将获取的文件,复制到输出流
copy(pngInput,outPng);
copy(bpmnInput,outBpmn);
//7.关闭输入输出流
pngInput.close();
outPng.close();
bpmnInput.close();
outBpmn.close();
}
/**
* 历史任务查询
*/
@Test
public void test08(){
//创建PreocessEngine对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//创建 存储历史资源服务的对象
HistoryService historyService = engine.getHistoryService();
//
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery() //创建历史活动实例查询器
.processDefinitionId("evection:1:52504") //根据 流程定义ID
.list();
for (HistoricActivityInstance hi : list) {
System.out.println(hi.getActivityId()); //活动ID
System.out.println(hi.getActivityName()); //活动名称
System.out.println(hi.getActivityType()); //活动类型
System.out.println(hi.getAssignee()); //活动负责人
System.out.println(hi.getProcessDefinitionId()); //流程定义ID
System.out.println(hi.getProcessInstanceId()); //流程实例ID
System.out.println("-----------------");
}
}
执行结果
解释一下这里的活动类型
本次单元测试所关联的对应的表
第五章简单介绍了activity的功能使用,让初学者对activity有了基础的认识和实操能力
第六章将简单介绍工作中会如何使用activity
分析图中这三者之间的联系
可以发现,第五章我们已经能创建一个流程,并且执行一个流程了,这很好。但是和业务没有关系啊,比如 王冰冰 填写了请假单信息,将信息存储在了业务系统的EVECTION表,然后Activity引擎创建了一个流程实例。
但是这个流程实例和业务系统的EVECTION表没有建立联系啊,现在是相互独立的。若我们需要根据流程实例查看王冰冰的请假单信息,就无法实现。
所以,需要将业务系统的EVECTION表和Activity库建立联系。如何建立联系呢?
EVECTION表一定会有自己的唯一标识,将这个唯一标识存入到Activity库中,这样不就可以在Activity库中根据流程实例ID+唯一标识ID,锁定王冰冰的请假信息。
这个唯一标识传递到在Activity中后,专业名称叫:业务标识(BusinessKey)
此图证明Activity库中的act_ru_exection表中预留了BusinessKey字段,等待传递。
有了这个BusinessKey,就可以很多字段关联,例如
找到了BusinessKey字段,就能找到流程定义ID,流程实例ID,任务ID等等,那再看需求,例如可以继续根据流程实例ID,找到流程实例相关的信息。
/**
* businessKey唯一标识
*/
@Test
public void test01(){
//获取业务系统请假表的唯一标识
String businessKey = "10001"; //实际工作中,需要连接数据库,获取到这个表中的唯一标识,这里简化未演示,直接定义一个标识
//创建ProcessEngine对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//创建 运行服务的对象
RuntimeService runtimeService = engine.getRuntimeService();
//传入 唯一标识 到指定的 流程定义
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("evection", businessKey);
//在流程实例中获取传入的BusinessKey
System.out.println(processInstance.getBusinessKey());
}
在实际业务中,若需要变更流程,就需要暂停流程实例,即为挂起。变更结束后,再激活流程实例。
只需挂起流程定义,就会挂起流程定义下的所有流程实例
/**
* 全部流程实例挂起
*/
@Test
public void test02(){
//创建 ProcessEngine对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//创建 注册服务的对象
RepositoryService repositoryService = engine.getRepositoryService();
//创建 流程定义的对象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() //创建流程定义查询器
.processDefinitionKey("evection") //根据 流程定义名称
.singleResult();
//获取当前流程定义对象的状态(挂起/激活)
boolean suspended = processDefinition.isSuspended();
//获取 流程定义ID
String id = processDefinition.getId();
//若挂起则激活,若激活则挂起
if (suspended){
repositoryService.activateProcessDefinitionById(id //流程定义id
,true //是否激活
,null //激活时间
);
System.out.println("流程定义id:" + id + ",已激活");
}else{
repositoryService.suspendProcessDefinitionById(id
,true //是否挂起
,null //激活时间
);
System.out.println("流程定义id:" + id + ",已挂起");
}
}
结果打印
测试一下该流程定义下的流程实例是否挂起
执行一下该流程实例,看看能否在从lis经理审批---->wangwu总经理审批
发现报错,提示无法执行一个挂起的任务。
说明我们挂起成功
表中字段suspendsion_state(暂停状态)
- 1 激活
- 2 挂起
/**
* 单个流程挂起
*/
@Test
public void test03(){
//创建 ProcessEngine对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//创建 运行服务的对象
RuntimeService runtimeService = engine.getRuntimeService();
//流程实例Id
String Id = "55001";
//创建 流程实例的对象
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(Id)
.singleResult();
//获取当前运行实例的状态
boolean suspended = processInstance.isSuspended();
//获取运行实例的id
String id = processInstance.getId();
//若挂起则激活,若激活则挂起
if(suspended){
runtimeService.activateProcessInstanceById(id);
System.out.println("流程定义id:" + id + ",已激活");
}else {
runtimeService.suspendProcessInstanceById(id);
System.out.println("流程定义id:" + id + ",已挂起");
}
}
就是指定shangsan负责创建申请单,lisi负责经理审批,有三种方式,分别是固定分配,表达式分配,监听器分配。
新建文件demo_UEL.bpmn
(未成功)
新建测试文件Test03
启动部署 ----> 启动流程实例
/**
* 需要先启动Activity部署
*/
@Test
public void test01(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService service = engine.getRepositoryService();
Deployment deploy = service.createDeployment() //创建部署
.addClasspathResource("bpmn/demo_UEL.bpmn") //添加bnmn资源
.name("出差申请流程_UEL") //
.deploy(); //部署流程
System.out.println("流程部署的id:"+deploy.getId());
System.out.println("流程部署的名称:"+deploy.getName());
}
/**
* 使用表达式分配任务 并 启动流程实例
*/
@Test
public void test02(){
//创建ProcessEngine对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//获取运行服务对象
RuntimeService runtimeService = engine.getRuntimeService();
Map<String , Object> map = new HashMap<>();
map.put("assignee1","张三");
map.put("assignee2","李四");
map.put("assignee3","王五");
map.put("assignee4","王冰冰");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("evection_UEL",map);
//输出相关实例
System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例的ID:" + processInstance.getId());
System.out.println("当前活动的ID:" + processInstance.getActivityId());
}
/**
* 查询任务
*/
@Test
public void test04(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
Task task = taskService.createTaskQuery()
.processDefinitionKey("ExclusiveGateway")
.includeProcessVariables() //查询结果包含流程变量
.taskAssignee("李四")
.singleResult();
System.out.println("流程定义ID:" + task.getProcessDefinitionId());
System.out.println("当前任务名称:" + task.getName());
System.out.println("当前任务ID:" + task.getId());
System.out.println("当前任务负责人:" + task.getAssignee());
System.out.println("流程变量" + task.getProcessVariables());
}
有时候还需要获取BusinessKey,根绝这个去业务系统查相关数据
如何获取BusinessKey呢,思路如下
/**
* 获取BusinessKey
*/
@Test
public void test05(){
//创建ProcessEngine对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//创建任务服务 的对象
TaskService taskService = engine.getTaskService();
//创建运行服务 的对象
RuntimeService runtimeService = engine.getRuntimeService();
Task task = taskService.createTaskQuery()
.processDefinitionKey("ExclusiveGateway")
.includeProcessVariables() //查询结果包含流程变量
.taskAssignee("李四")
.singleResult();
System.out.println("流程定义ID:" + task.getProcessDefinitionId());
System.out.println("当前任务名称:" + task.getName());
System.out.println("当前任务ID:" + task.getId());
System.out.println("当前任务负责人:" + task.getAssignee());
System.out.println("流程变量" + task.getProcessVariables());
//获取流程实例ID
String processInstanceId = task.getProcessInstanceId();
//根据实例ID,获取流程实例的对象
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
//根据流程实例对象,获取Key
String businessKey = processInstance.getBusinessKey();
System.out.println("获取businessKey:" + businessKey);
}
在实际业务中,完成任务前需要校验任务的负责人是否具有该任务的办理权限
/**
* 执行任务流程到下一级
*/
@Test
public void test06(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
Task task = taskService.createTaskQuery()
.processDefinitionKey("evection")
.taskAssignee("shangsan")
.singleResult(); //单个结果
//就是添加一个判断,判断Test对象不为空
if(task != null){
//完成当前任务(shangsan创建请假单)
taskService.complete(task.getId());
}
}
场景描述:员工创建申请单,由部门经理审批,部门经理通过后,判断请假天数,小于三天以下的财务直接审批,大于等于三天的总经理审批。
实现一个流程变量案例:
解释一下${evention.num}。在下面我们会创建一个实体类:Evention。此类中其中一个参数叫做num,表示请假天数。在单元测试中,我们会创建Evection的对象:evection,通过对象调用参数num。
针对这个流程图,当我们传入的参数num = 2天时,执行的流程是 创建出差申请单–>部门经理审批—>财务审批。 不需要经过总经理审批。因为只有三天以上才需要总经理审批。
@Data
public class Evection implements Serializable {
private long id;
private String evectionName;
//重点在这:出差天数
private double num;
private Date beginDate;
private Date endDate;
private String destination;
private String reson;
}
/**
* 先启动Activity部署
*/
@Test
public void test01(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService service = engine.getRepositoryService();
Deployment deploy = service.createDeployment() //创建部署
.addClasspathResource("bpmn/test.bpmn") //添加bnmn资源
.name("出差申请流程-流程变量") //
.deploy(); //部署流程
System.out.println("流程部署的id:"+deploy.getId());
System.out.println("流程部署的名称:"+deploy.getName());
}
/**
* 启动流程实例,设置流程变量
*/
@Test
public void test02(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = engine.getRuntimeService();
//创建变量集合
Map<String ,Object> variables = new HashMap<>();
//创建出差对象
Evection evection = new Evection();
//设置请假天数
evection.setNum(2d);
//添加
variables.put("evection",evection);
variables.put("assignee1","老大");
variables.put("assignee2","老二");
variables.put("assignee3","老三");
variables.put("assignee4","老四");
//创建流程实例对象
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("evection-variable", variables);
//输出相关实例
System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例的ID:" + processInstance.getId());
System.out.println("流程实例的名称:" + processInstance.getName());
System.out.println("当前活动的ID:" + processInstance.getActivityId());
}
/**
* 执行任务流程到下一级
*/
@Test
public void test03(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
Task task = taskService.createTaskQuery() //创建任务查询器
.processDefinitionKey("evection-variable") //根据流程定义名字
.taskAssignee("老大") //根据任务负责人
.singleResult(); //单个结果(是因为结果只有一个,所以这里没用list接收)
//完成当前任务(shangsan创建请假单)
taskService.complete(task.getId());
}
第二次执行任务,从老二(部门经理审批)---->老四(财务审批),不经过老三(总经理审批)
/**
* 执行任务流程到下一级
*/
@Test
public void test03(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
Task task = taskService.createTaskQuery() //创建任务查询器
.processDefinitionKey("evection-variable") //根据流程定义名字
.taskAssignee("老二") //根据任务负责人
.singleResult(); //单个结果(是因为结果只有一个,所以这里没用list接收)
//完成当前任务(shangsan创建请假单)
taskService.complete(task.getId());
}
通过上述的案例,可以观察到如何使用流程变量的,这里总结一下
这些都是通过UEL表达式使用流程变量
通过上面的案例,我们对流程变量已经有了初步的认识,简单明白了流程变量的含义。接下来再了解更多的一点细节吧。
当我们使用流程变量时:在一个大型项目中,必然存在多个流程变量,那么就需要制定一些规则来约束流程变量的使用,保证各使用多个变量时不乱套。所以activity的发明者提出了使用作用域来约束使用。
作用域的概念:一个流程定义,可以有多个流程实例。流程定义由多个任务组成,因此执行流程实例,就是按序执行内部的一个个任务。当我们使用不同的方式定义一个变量的时候,这个变量的生命周期可能贯穿在整个流程实例中,也有可能仅仅在其中一个任务中发挥作用。
明白了流程变量的作用域的概念,这里实践演示一下如何使用的。
目的:定义的流程变量作用域是 流程实例
通过 Map
/**
* 启动流程时设置变量
**/
@Test
public void test01(){
//创建ProcessEngine对象,并创建运行服务的对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = engine.getRuntimeService();
//使用集合给流程变量赋值
Map<String, Object> variables = new HashMap<>();
variables.put("assignee1","zhangsan");
variables.put("assignee2","lisi");
//启动流程实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("evection", variables);
}
//输出相关实例
System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例的ID:" + processInstance.getId());
System.out.println("流程实例的名称:" + processInstance.getName());
System.out.println("当前活动的ID:" + processInstance.getActivityId());
如果设置的流程变量的 key 在流程实例中已存在相同的名字则后设置的变量替换前边设置的变量。
/**
* 在任务办理时设置流程变量
**/
@Test
public void test02() {
//创建ProcessEngine对象,并创建任务服务的对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
//在任务办理时设置流程变量
//若继续设置assignee1,则覆盖zhangsan,最终结果是 assingee1 = ZJH
Map<String, Object> variables = new HashMap<>();
variables.put("assignee1", "ZJH");
//7.3.1.1示例中已经设置了 assignne1 = zhangsan
Task task = taskService.createTaskQuery() //创建任务查询器
.processDefinitionKey("evection") //根据流程定义名字
.taskAssignee("zhangsan") //根据任务负责人
.singleResult(); //返回一条数据
//完成当前任务(ZJH创建请假单)
//注意这里的参数,比起前面多了一个 集合对象variables
taskService.complete(task.getId(), variables);
}
通过流程实例 id 设置全局变量, 它的作用域是整个流程实例 ,该流程实例必须未执行完成。
/**
* 通过当前流程实例id设置,将assignee4 值修改为wang
**/
@Test
public void test03() {
//流程实例id
String exectionId="80001";
//创建ProcessEngine对象,并创建运行服务的对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = engine.getRuntimeService();
Map<String, Object> variables = new HashMap<>();
variables.put("assignee4", "wang");
//第1个参数:流程实例id
//第2个参数:流程定义名称
//第3个参数:流程变量
runtimeService.setVariables(exectionId,variables); //因为这里传入了集合,所以是一次设置多个值
//Evection evection = new Evection();
//evection.setNum(3d); 设置请假天数
//runtimeService.setVariables(exectionId,"evection",variables); //因为这里传入了一个值
}
注意,任务必须是未执行的任务,可以在历史记录查看是不是未执行
/**
* 通过当前任务id设置
**/
@Test
public void test01() {
//创建ProcessEngine对象,并创建运行服务的对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = engine.getRuntimeService();
//定义流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("assignee4", "wang");
//获取任务id
Task task = taskService.createTaskQuery() //创建任务查询器
.processDefinitionKey("evection") //根据流程定义名字
.taskAssignee("zhangsan") //要查询的负责人
.singleResult();//返回一条
String taskId = task.getId();
// 设置local变量,作用域为该任务
taskService.setVariableLocal(taskId,variables);
//完成任务
taskService.complete(taskId);
}
/**
* 通过当前任务id设置
**/
@Test
public void test01() {
//创建ProcessEngine对象,并创建运行服务的对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = engine.getRuntimeService();
//定义流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("assignee4", "wang");
//获取当前代办任务id
Task task = taskService.createTaskQuery() //创建任务查询器
.processDefinitionKey("evection") //根据流程定义名字
.taskAssignee("zhangsan") //要查询的负责人
.singleResult();//返回一条
String taskId = task.getId();
// 设置local变量,作用域为该任务
taskService.setVariableLocal(taskId,variables);
}
考虑一下某种场景,我们前面定义了负责每个任务的审批者,例如张三负责 创建请假申请单。若张三请假了,岂不是流程无法执行,要么就得修改流程定义。这很麻烦的,所以实际场景中,同一个任务会有多个候选人进行审批,一个请假了,另外一个还在呢。
新建bpmn文件,如图,定义负责离职申请单的人是AA和BB。以此类推
启动任务
/**
* 启动Activity部署
*/
@Test
public void test01(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService service = engine.getRepositoryService();
Deployment deploy = service.createDeployment() //创建部署
.addClasspathResource("bpmn/Group.bpmn") //添加bnmn资源
.name("离职申请流程") //
.deploy(); //部署流程
System.out.println("流程部署的id:"+deploy.getId());
System.out.println("流程部署的名称:"+deploy.getName());
}
/**
* 启动流程实例
*/
@Test
public void test02(){
//创建ProcessEngine对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//获取运行服务对象
RuntimeService runtimeService = engine.getRuntimeService();
//根据流程自定义的id启动流程
String id = "evection_Group";
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(id);
//输出相关实例
System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例的ID:" + processInstance.getId());
System.out.println("当前活动的ID:" + processInstance.getActivityId());
/**
* 查询任务
*/
@Test
public void test03(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
List<Task> list = taskService.createTaskQuery().processDefinitionKey("evection_Group").taskCandidateOrAssigned("AA").list();
for (Task task : list) {
System.out.println("流程定义ID:" + task.getProcessDefinitionId());
System.out.println("当前任务名称:" + task.getName());
System.out.println("当前任务ID:" + task.getId());
System.out.println("当前任务负责人:" + task.getAssignee());
}
}
}
在打印结果和表中都发现,这里的候选审批者怎么是空的呢,我们明明定义了AA和BB呀。
别着急,往后看
前面我们只是创建了任务的审批候选人,后面还需要让候选人领取组任务,归还组任务等等。这里我们梳理一下,处理组任务时都需要哪些步骤。
候选人领取组任务后变为自己的个人任务
/**
* 领取任务:候选人领取组任务后变为自己的个人任务
*/
@Test
public void test04(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
String taskId = "117504";
//候选人
String userid = "AA";
//领取任务
Task task = taskService.createTaskQuery() //创建任务查询器
.taskId(taskId) //根据任务Id
.taskCandidateUser(userid) //根据候选人查询
.singleResult();
//若找到了候选人AA
if(task != null){
//领取
taskService.claim(taskId, userid);
System.out.println("领取成功");
}
/**
* 查询任务
*/
@Test
public void test03(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
List<Task> list = taskService.createTaskQuery().processDefinitionKey("evection_Group").taskCandidateOrAssigned("AA").list();
for (Task task : list) {
System.out.println("流程定义ID:" + task.getProcessDefinitionId());
System.out.println("流程实例ID:" + task.getProcessInstanceId());
System.out.println("当前任务名称:" + task.getName());
System.out.println("当前任务ID:" + task.getId());
System.out.println("当前任务负责人:" + task.getAssignee());
}
}
/**
* 完成个人任务
*/
@Test
public void test05(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
String taskId = "117504";
taskService.complete(taskId);
System.out.println("完成任务:" + taskId);
}
若拾取任务后,后悔了,不想完成该任务,就需要退回
现在让CC领取了任务,然后退还任务
/**
* 归还任务
*/
@Test
public void test06(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
String taskId = "120002";
//候选人
String userId = "CC";
//查看任务
Task task = taskService.createTaskQuery() //创建任务查询器
.taskId(taskId) //根据任务Id
.taskAssignee(userId) //根据候选人查询(注意这里调用的方法)
.singleResult();
//若找到了候选人CC
if(task != null){
//将候选人设置为null,则表示归还
taskService.setAssignee(taskId, null);
System.out.println("归还成功");
}
}
若领取任务后,后悔了,不想完成该任务,也可以指定其他人接手此任务
现在让CC领取了任务,然后转手给DD
/**
* 交接任务
*/
@Test
public void test07(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
String taskId = "120002";
//候选人
String userId = "CC";
//查看任务
Task task = taskService.createTaskQuery() //创建任务查询器
.taskId(taskId) //根据任务Id
.taskAssignee(userId) //根据候选人查询(注意这里调用的方法)
.singleResult();
//若找到了候选人CC
if(task != null){
//将候选人设置为指定的人,则表示此人负责该任务
taskService.setAssignee(taskId, "DD");
System.out.println("交接成功");
}
}
前面我们使用流程变量的时候,直接在连线上写条件,为了更规范化,引入网关的概念,用于处理条件分支。下面分别介绍三种网关,其实特别简单。
创建Gateway.bpmn文件
当num=3时,路径是 创建请假申请单–>总经理–>人事经理
只走一条路径,若都不满足,则抛出异常
可以用代码测试一下,这些网关是否都有用,也能复习前面的知识点。
/**
* 启动Activity部署
*/
@Test
public void test01(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService service = engine.getRepositoryService();
Deployment deploy = service.createDeployment() //创建部署
.addClasspathResource("bpmn/Gateway.bpmn") //添加bnmn资源
.name("离职申请流程") //
.deploy(); //部署流程
System.out.println("流程部署的id:"+deploy.getId());
System.out.println("流程部署的名称:"+deploy.getName());
}
/**
* 启动流程实例时,设置流程变量
*/
@Test
public void test02(){
//创建ProcessEngine对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//获取运行服务对象
RuntimeService runtimeService = engine.getRuntimeService();
//请假天数设置为3天
Evection evection = new Evection();
evection.setNum(3d);
HashMap<String , Object> variables = new HashMap<>();
variables.put("evection",evection);
//根据流程自定义的id启动流程
String id = "ExclusiveGateway";
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(id,variables);
//输出相关实例
System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例的ID:" + processInstance.getId());
System.out.println("当前活动的ID:" + processInstance.getActivityId());
}
/**
* 执行任务流程到下一级
*/
@Test
public void test03(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
Task task = taskService.createTaskQuery() //创建任务查询器
.processDefinitionKey("ExclusiveGateway") //根据流程定义名字
.taskAssignee("张三") //根据任务负责人
.singleResult(); //单个结果(是因为结果只有一个,所以这里没用list接收)
//完成当前任务(shangsan创建请假单)
taskService.complete(task.getId());
}
/**
* 查询任务
*/
@Test
public void test04(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
List<Task> list = taskService.createTaskQuery().processDefinitionKey("ExclusiveGateway").taskAssignee("李四").list();
for (Task task : list) {
System.out.println("流程定义ID:" + task.getProcessDefinitionId());
System.out.println("当前任务名称:" + task.getName());
System.out.println("当前任务ID:" + task.getId());
System.out.println("当前任务负责人:" + task.getAssignee());
}
}
一系列操作后,发现确实是预期的执行顺序!
同时执行多条线路,所以不用写条件,写了也会被忽略掉
执行线路:创建请假申请单–>(总经理+部门经理)–>人事经理
简单概括一下这里的代码操作过程,可能会好奇如何同时执行两个任务呢?
例如
1.创建了流程实例。此时的任务是 (张三:创建请假申请单)
2.执行张三的任务。此时的任务是 (李四:总经理)和(王五:部门经理)
3.执行李四的任务。此时的任务是 (王五:部门经理)
4.执行王五的任务。此时的任务是 (王冰冰:人事经理)
因此说明,需要我们手动分别执行并行的任务,才能得到汇总结果
当num=3时,执行线路:创建请假申请单–>(总经理+监管部门)—>人事经理
当num=1时,执行线路:创建请假申请单–>(部门经理+监管部门)—>人事经理