文章目录
一、工作流和工作流引擎
1.什么是工作流
工作流通俗来讲就是业务上一个完整的审批流程。
例如:员工的请假流程、采购流程等。
2.什么是工作流引擎
工作流引擎实现了一个规范,规范要求:流程管理和状态字段无关,始终都读取业务流程的下一个节点。当业务更新的时候我们只需要更新流程图就可以了。从而实现了业务流程改变,不需要修改代码
例如:请假天数小于三天不需要经理审批,请假天数大于三天需要经历审批,那么这就会导致请假不同天数流程不一样。传统的基于状态值的方式,就需要改代码,而基于工作流引擎的方式就不需要修改代码。
流程自动推进原理:就拿请假模型来说,工作流引擎会用一张表来记录当前处在的节点。当填写完请假单后肯定是要轮到部门经理来审批了,所以我们一旦完成了请假单填写那么这条记录将会从这张表删除掉,并且会把下一个节点部门经理的信息插入到这张表中,当我们用部门经理的信息去这张表中查询的时候就能查出部门经理相关的审批的信息了,以此类推,这样层层递进,就实现了流程的自动递交了。
工作流引擎能够实现的效果:
审批流程发生变化,代码不需要跟着修改
流程能够自动往下完成
主流的框架有:Activiti、jBPM、Camunda 、Flowable 、国产的盘古BPM、云程
二、Acticiti7
1.Activiti介绍
Activity是一个工作流引擎,可以将复杂的业务流程抽取出来,使用专门的建模语言BPMN实现流程定义,业务流程按照预先定义的流程进行执行。从而减少业务系统由于流程变更进行系统升级改造的工作量,提高系统的健壮性,同时也减少了系统开发维护成本。
官网:https://www.activiti.org/
2.建模语言BPMN
BPM:即业务流程管理,是一种规范
BPMN:是BPM规范里面的一种建模符号,目前使用的是BPMN2.0
2.1 BPMN基本符号
-
事件 Event
分为三类:开始、中间、结束
开始:表示一个流程的开始
中间:一个流程中间的处理过程
结束:表示一个流程的结束
-
活动 Activities
活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程。
例如:请假审批里面,部门经理审批、总经理审批等都是一个活动
-
网关 GateWay
用来说明下一个任务该到哪里去,用来表示流程的分支与合并
常见的网关:排他网关、并行网关、包容网关、事件网关
排他网关:只有一条路径会被选择
并行网关:所有的路径会被同时选择
包容网关:可以同时执行多条线路,也可以在网关上设置条件
事件网关:专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态
-
流向 Flow
流是连接两个流程节点的连线
例如:填写请假单和部门经理审批这两个节点需要使用流向链接起来
流向的分类:顺序流、信息流、关联
顺序流:用一个带实心箭头的实心线表示,用于指定活动执行的顺序(也就是第一步做什么,第二步做什么)
信息流:用一条带箭头的虚线表示,用于描述两个独立的业务参与者(业务实体/业务角色)之间发送和接受的消息流动
关联:用一根带有线箭头的点线表示,用于将相关的数据、文本和其他人工信息与流对象联系起来。用于展示活动的输入和输出
2.2 流程示例图
3.Activiti数据库表介绍
Activiti 的运行支持必须要有这 25 张表的支持,主要是在业务流程运行过程中,记录参与流程的用户主体,用户组信息,以及流程的定义,流程执行时的信息,和流程的历史信息等等
3.1 表的命名规则和作用
Activiti 的表都以 act_ 开头,紧接着是表示表的用途的两个字母,也和 Activiti 所提供的服务的 API 对应,主要分为4类:
- ACT_RE:RE 表示 repository,这个前缀的表包含了流程定义和流程静态资源 (图片、规则、等等)
- ACT_RU:RU 表示 runtime,这些表运行时,会包含流程实例、任务、变量、异步任务等流程业务进行中的数据。Activiti 只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。这样表就可以一直保持很小的体积,并且速度很快
- ACT_HI:HI 表示 history,这些表包含一些历史数据,比如历史流程实例、变量、任务等等
- ACT_GE:GE 表示 general,通用数据
3.2 Activiti数据表介绍
表分类 | 表名 | 解释 |
---|---|---|
一般数据 | ||
[ACT_GE_BYTEARRAY] | 通用的流程定义和流程资源 | |
[ACT_GE_PROPERTY] | 系统相关属性 | |
流程历史记录 | ||
[ACT_HI_ACTINST] | 历史的流程实例 | |
[ACT_HI_ATTACHMENT] | 历史的流程附件 | |
[ACT_HI_COMMENT] | 历史的说明性信息 | |
[ACT_HI_DETAIL] | 历史的流程运行中的细节信息 | |
[ACT_HI_IDENTITYLINK] | 历史的流程运行过程中用户关系 | |
[ACT_HI_PROCINST] | 历史的流程实例 | |
[ACT_HI_TASKINST] | 历史的任务实例 | |
[ACT_HI_VARINST] | 历史的流程运行中的变量信息 | |
流程定义表 | ||
[ACT_RE_DEPLOYMENT] | 部署单元信息 | |
[ACT_RE_MODEL] | 模型信息 | |
[ACT_RE_PROCDEF] | 已部署的流程定义 | |
运行实例表 | ||
[ACT_RU_EVENT_SUBSCR] | 运行时事件 | |
[ACT_RU_EXECUTION] | 运行时流程执行实例 | |
[ACT_RU_IDENTITYLINK] | 运行时用户关系信息,存储任务节点与参与者的相关信息 | |
[ACT_RU_JOB] | 运行时作业 | |
[ACT_RU_TASK] | 运行时任务 | |
[ACT_RU_VARIABLE] | 运行时变量表 |
4.Activiti常用Service接口
简单介绍一下各个 Service 的实现类:
-
RepositoryService
Activiti 的资源管理类,该服务负责部署流程定义,管理流程资源。在使用 Activiti 时,一开始需要先完成流程部署,即将使用建模工具设计的业务流程图通过 RepositoryService 进行部署
-
RuntimeService
Activiti 的流程运行管理类,用于开始一个新的流程实例,获取关于流程执行的相关信息。流程定义用于确定一个流程中的结构和各个节点间行为,而流程实例则是对应的流程定义的一个执行,可以理解为 Java 中类和对象的关系
-
TaskService
Activiti 的任务管理类,用于处理业务运行中的各种任务,例如查询分给用户或组的任务、创建新的任务、分配任务、确定和完成一个任务
-
HistoryService
Activiti 的历史管理类,可以查询历史信息。执行流程时,引擎会保存很多数据,比如流程实例启动时间、任务的参与者、完成任务的时间、每个流程实例的执行路径等等。这个服务主要通过查询功能来获得这些数据
-
ManagementService
Activiti 的引擎管理类,提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护
5.Activiti流程设计
-
下载activiti-explorer
官网下载:https://www.activiti.org/get-started
-
启动activiti-explorer
解压压缩包,将war包放到tomcat的部署目录下
访问:http://localhost:8080/activiti-explorer
默认账号密码:kermit kermit
-
新建模型
-
设计流程
点击左边的下拉框,找到对应的东西,拖到右边
-
设置节点的属性
点击节点,下面的name是节点的名字,随便设置
滚动到下面,点击Assignment,这个是负责审批的系统用户(必须是系统里面已经有的用户)
两个都设置完了,就是这样的
-
设置流程定义的key
-
保存模型
-
将模型导出(会导出一个xml文件)
-
将流程的预览图保存下来
在流程图上右键另存为即可,保存下来是一个png图片
6.流程实例
6.1 什么是流程实例
流程定义ProcessDefinition和流程实例ProcessInstance是Activiti重要的概念,类似于Java类和Java实例的关系
启动一个流程实例表示开始一次业务流程的运行,比如员工请假流程部署完成,如果张三要请假就可以启动一个流程实例,如果李四要请假也启动一个流程实例,两个流程的执行互相不影响,就好比定义一个 java 类,实例化两个对象一样,部署的流程就好比 java 类,启动一个流程实例就好比 new 一个 java 对象
6.2 关联实际业务
BusinessKey:比如我们填写一个请假单,申请人提交的请假信息我们一定需要存储,存储的时候一定会有一个唯一标识,我们通常使用这个标识来关联activiti,这个标识在activiti中称为businesskey,通常为业务的主键
给流程实例指定BusinessKey,那么这个流程实例就会对这个信息进行处理
举例:请假流程启动一个流程实例,就可以将请假单的id作为业务标识存储到activiti中,将来查询activiti的流程实例信息就可以获取请假单的id从而关联查询业务系统数据库得到请假单信息
/**
* 启动流程实例,添加businessKey
*/
@Test
public void startUpProcessAddBusinessKey(){
String businessKey = "1";
// 启动流程实例,指定业务标识businessKey,也就是请假申请单id
ProcessInstance processInstance = runtimeService.
startProcessInstanceByKey("qingjia",businessKey);
// 输出
System.out.println("业务id:"+processInstance.getBusinessKey());
}
6.3 流程实例的挂起、激活
例子:
- 如果公司规定,每个月的最后一天需要封账,不能再做报销处理了,那么就可以把报销的流程实例挂起。
- 公司系统正在维护,不能接受申请,那么可以把所有的实例挂起,不在接受任何申请
6.3.1 全部流程实例挂起
操作流程定义为挂起状态,该流程定义下面的所有流程实例全部暂停:
流程定义为挂起状态,该流程定义将不允许启动新的流程实例,同时该流程定义下所有的流程实例将全部挂起暂停执行
@Test
public void suspendProcessInstance() {
//获取流程定义对象
ProcessDefinition qingjia = repositoryService.createProcessDefinitionQuery().processDefinitionKey("qingjia").singleResult();
// 获取到当前流程定义状态:为true是暂停的,为false是运行的
boolean suspended = qingjia.isSuspended();
//我想做:如果挂起就激活,如果激活就挂起
if (suspended) {// true那就是暂停,可以激活
// 参数1:流程定义的id 参数2:是否激活 参数3:时间点
repositoryService.activateProcessDefinitionById(qingjia.getId(), true, null);
System.out.println("流程定义:" + qingjia.getId() + "激活");
} else {//false那就处于激活状态,因此可以挂起
// 参数1:流程定义的id 参数2:是否挂起 参数3:时间点
repositoryService.suspendProcessDefinitionById(qingjia.getId(), true, null);
System.out.println("流程定义:" + qingjia.getId() + "挂起");
}
}
6.3.2 单个流程实例挂起
操作某个流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不在继续执行,完成该流程实例的当前任务将报异常
@Test
public void SingleSuspendProcessInstance() {
//流程实例id
String processInstanceId = "8bdff984-ab53-11ed-9b17-f8e43b734677";
//根据流程实例id操作指定流程实例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
//获取到当前流程定义是否为暂停状态 suspended方法为true代表为暂停 false就是运行的
boolean suspended = processInstance.isSuspended();
if (suspended) {
runtimeService.activateProcessInstanceById(processInstanceId);
System.out.println("流程实例:" + processInstanceId + "激活");
} else {
runtimeService.suspendProcessInstanceById(processInstanceId);
System.out.println("流程实例:" + processInstanceId + "挂起");
}
}
7.Activiti使用流程
-
第一步:引入依赖并初始化数据库
既然activiti是一个框架,那么我们肯定是需要引入对应的jar包坐标的,具体参考代码中的。
-
第二步:通过工具绘画流程图
使用 activiti 流程建模工具(activity-designer)画出业务流程图,最终生成一个**.bpmn 文件**
.bpmn 其实就是一个xml文件
-
第三步:流程定义部署
向 activiti 部署业务流程定义(.bpmn 文件),使用 activiti 提供的 api 向 activiti 中部署.bpmn 文件,
通俗来讲,就是让activiti认识要使用的流程
-
第四步:启动一个流程实例
启动一个流程实例表示开始一次业务流程的运行,比如员工请假流程部署完成,如果张三要请假就可以启动一个流程实例,如果李四要请假也启动一个流程实例,两个流程的执行互相不影响,就好比定义一个java类,实例化两个对象一样,部署的流程就好比java类,启动一个流程实例就好比new一个java对象
-
第五步:用户查询代办任务(Task)
因为现在系统的业务流程已经交给 activiti 管理,通过 activiti 就可以查询当前流程执行到哪了,当前用户需要办理什么任务,这些 activiti帮我们管理了。实际上我们学习activiti也只是学习它的API怎么使用,因为很多功能activiti都已经封装好了,我们会调用就行了
-
第六步:用户办理任务
用户查询待办任务后,就可以办理某个任务,如果这个任务办理完成还需要其它用户办理,比如请假单创建后由部门经理审核,这个过程也是由 activiti 帮我们完成了,不需要我们在代码中硬编码指定下一个任务办理人了
-
第七步:流程结束
当任务办理完成没有下一个任务节点了,这个流程实例就完成了。
8.Activiti入门案例
-
引入Activiti依赖
<!--引入activiti的springboot启动器 --> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>7.1.0.M6</version> <exclusions> <exclusion> <artifactId>mybatis</artifactId> <groupId>org.mybatis</groupId> </exclusion> </exclusions> </dependency>
说明:
Activiti7与SpringBoot整合后,默认集成了SpringSecurity安全框架,后续案例设置审批人时都必须是系统用户,Activiti框架会检查用户是否存在,否则会出现异常。
-
修改配置文件
spring: activiti: # false:默认,数据库表不变,但是如果版本不对或者缺失表会抛出异常(生产使用) # true:表不存在,自动创建(开发使用) # create_drop: 启动时创建,关闭时删除表(测试使用) # drop_create: 启动时删除表,在创建表 (不需要手动关闭引擎) database-schema-update: true #监测历史表是否存在,activities7默认不开启历史表 db-history-used: true #none:不保存任何历史数据,流程中这是最高效的 #activity:只保存流程实例和流程行为 #audit:除了activity,还保存全部的流程任务以及其属性,audit为history默认值 #full:除了audit、还保存其他全部流程相关的细节数据,包括一些流程参数 history-level: full #校验流程文件,默认校验resources下的process 文件夹的流程文件 check-process-definitions: true
-
启动项目,等待自动创建数据库表
每张表具体有什么作用,上面有
-
下载流程设计工具和IDEA插件
- IDEA可下载Activiti BPMN visualizer插件
-
流程图绘制使用Activiti 官方提供Activiti Modeler
下载地址https://www.activiti.org/get-started
下载压缩包之后,将war包放到tomcat的部署目录用,启动tomcat
然后访问:http://localhost:8080/activiti-explorer。
默认账号和密码都是:kermit
-
设计流程
看上面的设计流程部分
-
将导出的流程图和xml文件放到项目中resources下的process中
resources下新建process文件夹
将qingjia.bpmn20.xml与qingjia.png放入process目录
-
流程定义部署(两种方式)
将上面在设计器中定义的流程部署到activiti数据库中,就是流程定义部署。
-
单个文件部署方式
将流程定义的bpmn和png两个文件一个一个添加部署到activiti中
@RunWith(SpringRunner.class)//Junit4才需要这个注解,Junit5不需要 @SpringBootTest public class ProcessTest { //注入Acitviti提供的资源管理接口(负责流程部署) @Autowired private RepositoryService repositoryService; @Test public void deployProcess() { // 流程部署 Deployment deploy = repositoryService.createDeployment() .addClasspathResource("process/qingjia.bpmn20.xml") .addClasspathResource("process/qingjia.png") //随便给流程七个名字 .name("请假申请流程") .deploy(); System.out.println(deploy.getId()); System.out.println(deploy.getName()); } }
-
压缩包部署方式
将两个文件打成zip包进行部署
@Test public void deployProcessByZip() { // 定义zip输入流 InputStream inputStream = this .getClass() .getClassLoader() .getResourceAsStream( "process/qingjia.zip"); ZipInputStream zipInputStream = new ZipInputStream(inputStream); // 流程部署 Deployment deployment = repositoryService.createDeployment() .addZipInputStream(zipInputStream) .name("请假申请流程") .deploy(); System.out.println("流程部署id:" + deployment.getId()); System.out.println("流程部署名称:" + deployment.getName()); }
说明:
部署成功后,下面这三个表数据会发生变化
-
act_re_deployment:流程定义部署表,每部署一次增加一条记录
-
act_re_procdef:流程定义表,部署每个新的流程定义都会在这张表中增加一条记录
-
act_ge_bytearray:流程资源表
-
-
启动一个流程实例
流程实例:一个流程实例就是一个审批任务,一个流程部署后,可以有很多的流程实例
例如:我们有一个请假的审批模板,那么张三可以请假,李四也可以请假。张三和李四的请假就是一个个流程实例
@Autowired private RuntimeService runtimeService; @Test public void startUpProcess() { //创建流程实例,我们需要知道流程定义的key //流程定义的key就是xml文件中process标签的id属性值 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("qingjia"); //输出实例的相关信息 System.out.println("流程定义id:" + processInstance.getProcessDefinitionId()); System.out.println("流程实例id:" + processInstance.getId()); System.out.println("当前活动Id:" + processInstance.getActivityId()); }
说明:启动一个流程实例后,可以去下面的表中查看各种信息
act_hi_actinst 流程实例执行历史
act_hi_identitylink 流程的参与用户历史信息
act_hi_procinst 流程实例历史信息
act_hi_taskinst 流程任务历史信息
act_ru_execution 流程执行信息
act_ru_identitylink 流程的参与用户信息
act_ru_task 任务信息
-
审批人查询待办任务
@Autowired private TaskService taskService; /** * 查询当前个人待执行的任务 */ @Test public void findPendingTaskList() { //任务负责人 String assignee = "zhangsan"; //查询出来的结果是所有需要zhangsan审批的任务 List<Task> list = taskService.createTaskQuery() .taskAssignee(assignee)//只查询该任务负责人的任务 .list(); for (Task task : list) { System.out.println("流程实例id:" + task.getProcessInstanceId()); System.out.println("任务id:" + task.getId()); System.out.println("任务负责人:" + task.getAssignee()); System.out.println("任务名称:" + task.getName()); } }
说明:
流程实例id:一个流程只有一个,标识这个流程
任务id:流程每进行到某个节点,就会给这个节点分配一个任务id
-
处理当前任务
任务负责人查询待办任务,选择任务进行处理,完成任务。
完成任务后,任务自动到下一个节点
/**
* 完成任务
*/
@Test
public void completTask(){
Task task = taskService.createTaskQuery()
.taskAssignee("zhangsan") //要查询的负责人
.singleResult();//返回一条
//完成任务,参数:任务id
taskService.complete(task.getId());
}
-
查询已处理任务
@Autowired private HistoryService historyService; /** * 查询已处理历史任务 */ @Test public void findProcessedTaskList() { //张三已处理过的历史任务 List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().taskAssignee("zhangsan").finished().list(); for (HistoricTaskInstance historicTaskInstance : list) { System.out.println("流程实例id:" + historicTaskInstance.getProcessInstanceId()); System.out.println("任务id:" + historicTaskInstance.getId()); System.out.println("任务负责人:" + historicTaskInstance.getAssignee()); System.out.println("任务名称:" + historicTaskInstance.getName()); } }
-
任务结束
当任务后面没有节点了,任务会自动结束
-
其他接口【了解即可】
/** * 查询流程定义 *【流程定义就类似于一个java类】 */ @Test public void findProcessDefinitionList(){ List<ProcessDefinition> definitionList = repositoryService.createProcessDefinitionQuery() .orderByProcessDefinitionVersion() .desc() .list(); //输出流程定义信息 for (ProcessDefinition processDefinition : definitionList) { System.out.println("流程定义 id="+processDefinition.getId()); System.out.println("流程定义 name="+processDefinition.getName()); System.out.println("流程定义 key="+processDefinition.getKey()); System.out.println("流程定义 Version="+processDefinition.getVersion()); System.out.println("流程部署ID ="+processDefinition.getDeploymentId()); } } /** * 删除流程定义 */ public void deleteDeployment() { //部署id String deploymentId = "82e3bc6b-81da-11ed-8e03-7c57581a7819"; //删除流程定义,如果该流程定义已有流程实例启动则删除时出错 repositoryService.deleteDeployment(deploymentId); //设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式 //repositoryService.deleteDeployment(deploymentId, true); }
9.任务分配
任务由谁去审批,就是任务分配。上面进行流程设计的时候,我们给每个任务节点指定了审批人,这就是任务分配的一种
任务分配的种类:固定分配、UEL表达式分配、监听器分配
9.1 固定分配
在进行流程设计图设计的时候,直接指定节点的负责人【因此不灵活】
9.2 表达式分配
Activiti支持两种表达式UEL-value和UEL-method
9.2.1 UEL-value
-
新建一个加班流程
-
指定审批人的时候,不写指定的名字,而是使用表达式
${这里随便}
-
导出流程图,导入IDE中,启动实例
启动实力的时候,就需要指定对应的审批人员
@Test // 流程部署定义 public void deployProcess01() { Deployment deploy = repositoryService.createDeployment() .addClasspathResource("process/jiaban01.bpmn20.xml") .name("加班申请流程") .deploy(); System.out.println(deploy.getId()); System.out.println(deploy.getName()); } /** * 启动流程实例 */ @Test public void startUpProcess01() { //这里就是指定对应的操作人 Map<String, Object> variables = new HashMap<>(); //key是上面写的占位符 //value是审批人 variables.put("assignee1","zhangsan"); variables.put("assignee2","lisi"); //创建流程实例,我们需要知道流程定义的key,并且传入map ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("qingjia01", variables); //输出实例的相关信息 System.out.println("流程定义id:" + processInstance.getProcessDefinitionId()); System.out.println("流程实例id:" + processInstance.getId()); }
9.3.2 UEL-method
-
创建一个javaBean,并托管到Spring容器中
@Component public class UserBean { public String getUsername(int id) { if(id == 1) { return "zhangsan"; } if(id == 2) { return "lisi"; } return "admin"; } }
-
新建一个流程,审批人写表达式
${javaBean.方法名(参数id)}
例如:
经理审批:${userBean.getUsername(1)}
人事审批:${userBean.getUsername(2)}
userBean 是我们自定义的spring 容器中的一个 bean,表示调用该 bean 的 getUsername(int id)方法。
-
部署启动流程
启动流程实例,就会调用bean方法,参数为:1,经理审批后,接着调用bean方法,参数为:2
@Test public void deployProcess02() { // 流程部署 Deployment deploy = repositoryService.createDeployment() .addClasspathResource("process/jiaban02.bpmn20.xml") .name("加班申请流程") .deploy(); System.out.println(deploy.getId()); System.out.println(deploy.getName()); } /** * 启动流程实例 */ @Test public void startUpProcess02() { //创建流程实例,我们需要知道流程定义的key ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("qingjia02"); //输出实例的相关信息 System.out.println("流程定义id:" + processInstance.getProcessDefinitionId()); System.out.println("流程实例id:" + processInstance.getId()); }
9.3 监听器分配
使用监听器的方式来指定负责人,那么在流程设计时就不需要指定assignee。
任务监听器是发生对应的任务相关事件时执行自定义 java 逻辑 或表达式
相关事件包括:
- Create:任务创建后触发
- Assignment:任务分配后触发
- Delete:任务完成后触发
- All:所有事件发生都触发
-
创建任务监听类【必须实现org.activiti.engine.delegate.TaskListener 接口 】
public class MyTaskListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { //这里的Name是设计流程图的时候给节点的名字 if(delegateTask.getName().equals("经理审批")){ //这里指定任务负责人 delegateTask.setAssignee("zhangsan"); } else if(delegateTask.getName().equals("人事审批")){ //这里指定任务负责人 delegateTask.setAssignee("lisi"); } } }
-
设计流程图,并配置监听器【不需要指定审批人了】
-
部署和启动流程实例
启动流程实例,就会调用MyTaskListener监听方法
@Test public void deployProcess03() { // 流程部署 Deployment deploy = repositoryService.createDeployment() .addClasspathResource("process/jiaban03.bpmn20.xml") .name("加班申请流程") .deploy(); System.out.println(deploy.getId()); System.out.println(deploy.getName()); } /** * 启动流程实例 */ @Test public void startUpProcess03() { //创建流程实例,我们需要知道流程定义的key ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("jiaban03"); //输出实例的相关信息 System.out.println("流程定义id:" + processInstance.getProcessDefinitionId()); System.out.println("流程实例id:" + processInstance.getId()); }
10.流程变量
10.1 什么是流程变量
例如:
公司规定请假天数小于三天部门经理审批后可以直接由人事备案。请假天数大于三天部门经理审批后还需要总经理审批,总经理审批后才能交由人事备案。
在这个案例中,交给谁审批由审批天数控制,因此请假天数是流程变量
10.2 流程变量的作用域
流程变量的默认作用域是流程实例
10.2.1 global变量
流程变量的默认作用域是流程实例,当一个流程变量的作用域为流程实例时,可以称为 global 变量
注意:
global 变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。
10.2.2 local变量
任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大, 称为 local 变量。
说明:
Local 变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。Local 变量名也可以和 global 变量名相同,没有影响。
10.3 流程变量的使用方法
通过UEL表达式使用流程变量:
前面使用的UEL表达式就是一种使用方式
使用
${assignee1}
和${assignee2}
分别表示不同的审批人也可以使用UEL表达式但是里面是表达式
例如:
${day > 3}
或${day <= 3}
10.3.1 启动实例时设置变量
作用域是整个流程实例
@Test
public void startUpProcess() {
Map<String, Object> variables = new HashMap<>();
variables.put("assignee1", "zhangsan");
variables.put("assignee2", "lisi");
//创建流程实例,我们需要知道流程定义的key
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("qingjia", variables);
//输出实例的相关信息
System.out.println("流程定义id:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例id:" + processInstance.getId());
}
10.3.2 任务办理时设置流程变量
作用域是整个流程实例
@Test
public void completTask() {
Task task = taskService.createTaskQuery()
.taskAssignee("zhangsan") //要查询的负责人
.singleResult();//返回一条
Map<String, Object> variables = new HashMap<>();
variables.put("assignee2", "zhao");//可以指定assignee2是谁
//完成任务,参数:任务id
taskService.complete(task.getId(), variables);
}
10.3.3 通过当前流程实例设置
作用域是整个流程实例
@Test
public void processInstanceIdSetVariables() {
Map<String, Object> variables = new HashMap<>();
variables.put("assignee2", "wang");
runtimeService.setVariables("1c347a90-82c6-11ed-96ca-7c57581a7819", variables);
}
10.3.4 设置Locl变量
@Test
public void completLocalTask() {
Task task = taskService.createTaskQuery()
.taskAssignee("zhangsan") //要查询的负责人
.singleResult();//返回一条
// 设置local变量,作用域为该任务
taskService.setVariableLocal(task.getId(),"assignee2","li");
// 查看local变量
System.out.println(taskService.getVariableLocal(task.getId(), "assignee2"));
//完成任务,参数:任务id
taskService.complete(task.getId());
}
11.任务组
11.1 什么是任务组
之前我们设定审批人的时候,无论是固定分配,还是表达式分配,审批人都只能设置一个。如果我们的经理有好几位,那么可以把经理们编程一个组,组内任何一个人都可以审批当前任务
11.2 使用流程
-
设置候选人【中间使用逗号隔开】
-
部署流程
@Test public void deployProcess04() { // 流程部署 Deployment deploy = repositoryService.createDeployment() .addClasspathResource("process/jiaban04.bpmn20.xml") .name("请假申请流程") .deploy(); System.out.println(deploy.getId()); System.out.println(deploy.getName()); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("jiaban04"); System.out.println(processInstance.getId()); }
-
查询组任务
@Test public void findGroupTaskList() { //查询组任务 List<Task> list = taskService.createTaskQuery() .taskCandidateUser("zhangsan01")//根据候选人查询 .list(); for (Task task : list) { System.out.println("----------------------------"); System.out.println("流程实例id:" + task.getProcessInstanceId()); System.out.println("任务id:" + task.getId()); System.out.println("任务负责人:" + task.getAssignee()); System.out.println("任务名称:" + task.getName()); } }
-
拾取组任务
说明:
任务只能由一个人拾取,不是组内成员也可以拾取,因此需要校验
@Test public void claimTask(){ //拾取任务,即使该用户不是候选人也能拾取(建议拾取时校验是否有资格) //校验该用户有没有拾取任务的资格 Task task = taskService.createTaskQuery() .taskCandidateUser("zhangsan01")//根据候选人查询 .singleResult(); if(task!=null){ //拾取任务 taskService.claim(taskId, "zhangsan01"); System.out.println("任务拾取成功"); } }
-
查询个人代办任务
@Test public void findGroupPendingTaskList() { //任务负责人 String assignee = "zhangsan01"; List<Task> list = taskService.createTaskQuery() .taskAssignee(assignee)//只查询该任务负责人的任务 .list(); for (Task task : list) { System.out.println("流程实例id:" + task.getProcessInstanceId()); System.out.println("任务id:" + task.getId()); System.out.println("任务负责人:" + task.getAssignee()); System.out.println("任务名称:" + task.getName()); } }
-
处理任务
@Test public void completGroupTask() { Task task = taskService.createTaskQuery() .taskAssignee("zhangsan01") //要查询的负责人 .singleResult();//返回一条 taskService.complete(task.getId()); }
-
归还组任务
如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人
@Test public void assigneeToGroupTask() { String taskId = "d96c3f28-825e-11ed-95b4-7c57581a7819"; // 任务负责人 String userId = "zhangsan01"; // 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务 Task task = taskService .createTaskQuery() .taskId(taskId) .taskAssignee(userId) .singleResult(); if (task != null) { // 如果设置为null,归还组任务,该 任务没有负责人 taskService.setAssignee(taskId, null); } }
-
任务交接
任务交接,任务负责人将任务交给其它候选人办理该任务
@Test public void assigneeToCandidateUser() { // 当前待办任务 String taskId = "d96c3f28-825e-11ed-95b4-7c57581a7819"; // 校验zhangsan01是否是taskId的负责人,如果是负责人才可以归还组任务 Task task = taskService .createTaskQuery() .taskId(taskId) .taskAssignee("zhangsan01") .singleResult(); if (task != null) { // 将此任务交给其它候选人zhangsan02办理该 任务 taskService.setAssignee(taskId, "zhangsan02"); } }
12.网关
12.1 什么是网关
网关用来控制流程的流向,通常会和流程变量一起使用
12.2 网关的种类
12.2.1 排他网关
只有一条路径会被选择
当你的流程出现这样的场景:请假申请,两天以内,部门经理审批流程就结束了,两天以上需要总经理直接审批,这个时候就需要排他网关
12.2.2 并行网关
所有路径会被同时选择
当出现这样的场景:请假申请开始,需要部门经理和总经理都审批,两者没有前后需要两个人全部审批才能进入下个节点人事审批。这个时候就需要并行网关
说明:
如果在并行网关中加条件,条件会被忽略,不会生效
12.2.3 包含网关
包容网关:可以同时执行多条线路,也可以在网关上设置条件,可以看做是排他网关和并行网关的结合体。
当出现这样的场景:请假申请大于等于2天需要由部门总经理审批,小于2天由部门经理审批,请假申请必须经过人事经理审批。这个时候就需要包含网关
12.3 网关的使用
-
流程图的设计
-
部署流程
@Test public void deployProcess03() { // 流程部署 Deployment deploy = repositoryService.createDeployment() .addClasspathResource("process/jiaban03.bpmn20.xml") .name("加班申请流程") .deploy(); System.out.println(deploy.getId()); System.out.println(deploy.getName()); }
-
启动实例
/** * 启动流程实例 */ @Test public void startUpProcess03() { //设置参数 Map<String,Object> map=new HashMap<>(); map.put("day","2"); //创建流程实例,我们需要知道流程定义的key,和变量 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("jiaban03",map); //输出实例的相关信息 System.out.println("流程定义id:" + processInstance.getProcessDefinitionId()); System.out.println("流程实例id:" + processInstance.getId()); }
-
查询待办任务
-
处理代办任务
-
结束流程