Activiti7学习(进阶篇)

2023-11-03 17:50
文章标签 学习 进阶篇 activiti7

本文主要是介绍Activiti7学习(进阶篇),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1. 流程实例
    • 1.1什么是流程实例
    • 1.2 业务管理
      • 实现代码
    • 1.3 流程实例的挂起和激活
      • 全部流程挂起
      • 单个实例挂起
  • 2. 个人任务
    • 2.1 分配任务责任人
      • 2.1.1 固定分配
      • 2.1.2 表达式分配
        • UEL-value
        • UEL-method
        • UEL-method 与 UEL-value 结合
        • 其它
      • 2.1.3 监听器分配
        • 自定义的任务监听器
        • 测试代码
    • 2.2 查询任务
      • 查询任务负责人的待办任务
      • 关联 businessKey
    • 2.3办理任务
  • 3.流程变量
    • 3.1、什么是流程变量
    • 3.2 流程变量类型
    • 3.3、流程变量作用域
      • 3.3.1、globa变量
      • 3.3.2、local变量
    • 3.4、流程变量的使用方法
      • 在属性上使用UEL表达式
      • 在连线上使用UEL表达式
    • 3.5流程变量使用
      • 3.5.1需求
      • 3.5.2 流程定义
      • 3.5.3 使用Global变量
        • 3.5.3.1 POJO创建
        • 3.5.3.2 流程的部署
        • 3.5.3.3 设置流程变量
          • a.启动时设置流程变量
          • b.任务办理时设置
          • c.当前流程实例设置
          • d.当前任务设置
      • 3.5.4设置local流程变量
        • 3.5.4.1、任务办理时设置
        • 3.5.4.2、通过当前任务设置
        • 3.5.4.3、 Local变量测试1
        • 3.5.4.4、 Local变量测试2
  • 4. 组任务
    • 4.1、需求
    • 4.2、设置任务候选人
    • 4.3、组任务
      • 4.3.1、组任务办理流程
      • 4.3.2、 查询组任务
      • 4.3.3、 拾取组任务
      • 4.3.4、 查询个人待办任务
      • 4.3.5、 办理个人任务
      • 4.3.6、 归还组任务
      • 4.3.7、任务交接
      • 4.3.8、 数据库表操作
  • 5.网关
    • 5.1 排他网关ExclusiveGateway
      • 5.1.1 什么是排他网关:
      • 5.1.2流程定义
      • 5.1.3测试
    • 5.2 并行网关ParallelGateway
      • 5.2.1 什么是并行网关
      • 5.2.2 流程定义
      • 5.2.3 测试
    • 5.3包含网关InclusiveGateway
      • 5.3.1什么是包含网关
      • 5.3.2流程定义:
      • 5.3.3测试
    • 5.4 事件网关EventGateway
      • 5.4.1流程定义
  • 三 、Activiti整合篇
    • 1. 和Spring整合
      • 1.1 添加相关的依赖
      • 1.2 添加整合的配置文件
      • 1.3 创建测试类测试
    • 2. 和SpringBoot的整合
      • 2.1 添加相关的依赖
      • 2.2 修改配置文件
      • 2.3 整合SpringSecurity
      • 2.4 创建bpmn文件
      • 2.5 单元测试

1. 流程实例

1.1什么是流程实例

流程实例(ProcessInstance)代表流程定义的执行实例

一个流程实例包括了所有的运行节点,我们可以利用这个对象来了解当前流程实例的进度等信息

例如:用户或者程序安装流程定义的内容发起了一个流程,这个就是一个流程实例

在这里插入图片描述

1.2 业务管理

流程定义部署在Activiti后,我们就可以在系统中通过Activiti去管理流程的执行,但是如果我们要将我们的 流程实例业务数据 关联,这时我们需要使用到Activiti中预留的 BusinessKey (业务标识) 来关联(就是activiti通过预设定好的流程帮助我们自动管理流程,但是流程实例有时需要和具体的业务挂钩,所以可以使用activiti给我们预留的businessKey)
在这里插入图片描述

实现代码

先回顾一下部署流程定义的代码(自己手动的)

(注意:1次部署,就会在act_re_deploy表中插入1条数据,假设2次都部署同1个文件,并且参数都不改的情况下—> 即2次调用部署的方法,那么这2次部署产生的2条数据的name_和key_都是相同的,但是它们的id_不同,其中name和key都是在方法中指定的。在act_re_procdef表中也会插入2条数据,它们的name_和key_也都是相同的,但是它们的id_是不同的,其中name和key是在bpmn.xml中的name属性和id属性指定的,id_是key:{0}:{1})的形式,并且这2条数据都是1个deploy_id_字段来标识是哪1次部署生成的流程定义,还有需要注意的就是,这2条流程定义信息的key如果一样的话,还有个版本号version_字段,当使用流程引擎提供的startProcessInstanceByKey方法时,会选择指定key中最大版本的那条流程定义数据来开启1个流程实例

@Test
public void test01() {ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();Deployment deploy = processEngine.getRepositoryService().createDeployment().addClasspathResource("bpmn/evection.png").addClasspathResource("bpmn/evection.bpmn").name("流程申请单阿").key("evectionah").category("haa").deploy();/*流程部署的id: 1流程部署的name: 流程申请单阿流程部署的key: evectionah流程部署的category: haa*/log.info("流程部署的id: {}", deploy.getId());log.info("流程部署的name: {}", deploy.getName());log.info("流程部署的key: {}", deploy.getKey());log.info("流程部署的category: {}", deploy.getCategory());// 根据流程部署id,去查询刚刚部署的流程定义ProcessDefinition processDefinition = processEngine.getRepositoryService().createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult();/*流程定义的id: evection2:1:4  - 它的前面部分是 (创建bpmn文件时,点击空白位置时) 填入的id流程定义的key: evection2     - 创建bpmn文件时,点击空白位置时,填入的id流程定义的name: 出差申请单1   - 创建bpmn文件时,点击空白位置时,填入的name*/log.info("流程定义的id: {}", processDefinition.getId());log.info("流程定义的key: {}", processDefinition.getKey());log.info("流程定义的name: {}", processDefinition.getName());}

开启1个流程实例, 并且指定businessKey(案例中的)

/*** 启动流程实例,添加businessKey*/
@Test
public void test01() {// 1.获取ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 2.获取RuntimeService对象RuntimeService runtimeService = processEngine.getRuntimeService();// 3.启动流程实例// (第 1 个参数是:创建bpmn流程图文件时,点击空白位置时,填入的流程定义id)// (第 2 个参数是:一般是先把业务数据保存好后, 把业务数据的主键放到这, 将activiti的流程实例与业务关联起来)// (这个businessKey保存在act_ru_execution表的BUSINESS_KEY_字段中)// (这个businessKey也会保存在act_hi_procinst表的BUSINESS_KEY_字段中)ProcessInstance instance = runtimeService.startProcessInstanceByKey("evection", "1001");// 4.输出processInstance相关属性System.out.println("businessKey = " + instance.getBusinessKey());
}

(下图:act_ru_execution表中的BUSINESS_KEY_字段)
在这里插入图片描述

1.3 流程实例的挂起和激活

在实际场景中可能由于流程变更需要将当前运行的流程暂停,而不是删除,流程暂停后将不能继续执行。

全部流程挂起

操作流程的定义为挂起状态,该流程定义下边所有的流程实例全部暂停。

流程定义为挂起状态,该流程定义将不允许启动新的流程实例,同时该流程定义下的所有的流程实例都 将全部挂起暂停执行

/*** 全部流程挂起实例与激活*/
@Test
public void test02() {// 1.获取ProcessEngine对象ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 2.获取RepositoryService对象RepositoryService repositoryService = engine.getRepositoryService();// 3.查询流程定义的对象(根据流程定义key,去查询流程定义对象)ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey("evection").singleResult();// 4.获取当前流程定义的状态boolean suspended = processDefinition.isSuspended();String id = processDefinition.getId();// 5.如果挂起就激活,如果激活就挂起if (suspended) {// 表示当前定义的流程状态是 挂起的repositoryService.activateProcessDefinitionById(id   // 流程定义的id, true // 是否激活, null // 激活时间);System.out.println("流程定义:" + id + ",已激活");} else {// 非挂起状态,激活状态 那么需要挂起流程定义repositoryService.suspendProcessDefinitionById(id   // 流程id, true // 是否挂起, null // 挂起时间);System.out.println("流程定义:" + id + ",已挂起");}
}

挂起流程定义后,对于的实例对象中的状态会修改为2
(补充:对某个流程定义挂起后,表act_re_procdef表的SUSPENSION_STATE_字段会修改为2,同时,表act_ru_task表的SUSPENSION_STATE_字段也会修改为2)
在这里插入图片描述
然后再去操作对于的流程实例的话(完成该实例的当前任务),会抛如下异常信息

在这里插入图片描述

我们再将挂起的流程转变为激活状态,对于的状态值会从2更新为1

在这里插入图片描述

然后就是业务流程可以正常处理了

单个实例挂起

操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不再继续执行,当前流程 定义的其他流程实例是不受干扰的。完成该流程实例的当前任务会抛异常

/*** 单个流程实例挂起与激活*/@Testpublic void test03() {// 1.获取ProcessEngine对象ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 2.获取RuntimeServiceRuntimeService runtimeService = engine.getRuntimeService();// 3.获取流程实例对象(根据流程实例id,去查询指定的流程实例对象)ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId("25001").singleResult();// 4.获取相关的状态操作boolean suspended = processInstance.isSuspended();String id = processInstance.getId();if (suspended) {// 挂起--》激活runtimeService.activateProcessInstanceById(id);System.out.println("流程定义:" + id + ",已激活");} else {// 激活--》挂起// (taskService.createTaskQuery().processInstanceId("10001").active().list(); 查询时, 是不包括挂起的流程实例的)// (可以观察到act_ru_task表中SUSPENSION_STATE_字段由1改为了2, 该流程实例对应的任务状态为从激活状态变为了挂起状态)runtimeService.suspendProcessInstanceById(id);System.out.println("流程定义:" + id + ",已挂起");}}

然后我们可以在数据库中查看到状态的更新
在这里插入图片描述

2. 个人任务

2.1 分配任务责任人

2.1.1 固定分配

在进行业务流程建模的时候指定固定的任务负责人:

在这里插入图片描述
在Properties视图中,填写Assiginee项为任务负责人

2.1.2 表达式分配

在Activiti中支持使用 UEL表达式,UEL表达式是Java EE6 规范的一部分, UEL(Unified Expression Language) 即 统一表达式语言, Activiti支持两种UEL表达式: UEL-valueUEL-method

UEL-value

在这里插入图片描述

在assignee中使用流程变量处理
(创建请假单的Assignee设置为${assignee0}
在这里插入图片描述
(经理审批的Assignee设置为${assignee1}
在这里插入图片描述
(经理审批的Assignee设置为${assignee2}
在这里插入图片描述
(经理审批的Assignee设置为${assignee3}
在这里插入图片描述
然后我们可以来操作

首先我们需要将定义的流程部署到Activiti数据库中

/*** 先将新定义的流程部署到Activiti中数据库中*/
@Test
public void test01() {// 1.获取ProcessEngine对象ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 2.获取RepositoryService进行部署操作RepositoryService service = engine.getRepositoryService();// 3.使用RepositoryService进行部署操作Deployment deploy = service.createDeployment().addClasspathResource("bpmn/evection-uel.bpmn") // 添加bpmn资源.addClasspathResource("bpmn/evection-uel.png") // 添加png资源.name("出差申请流程-UEL").deploy();// 部署流程// 4.输出流程部署的信息System.out.println("流程部署的id:" + deploy.getId());System.out.println("流程部署的名称:" + deploy.getName());
}

部署成功后我们需要启动一个新的流程实例,然后在流程实例创建的时候关联UEL表达式

/*** 创建一个流程实例* 给流程定义中的 UEL表达式赋值*/
@Test
public void test02() {// 获取流程引擎ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取RuntimeService对象RuntimeService runtimeService = processEngine.getRuntimeService();// 设 置 assignee 的 取 值 , Map<String, Object> map = new HashMap<>();map.put("assignee0", "张三");map.put("assignee1", "李四");map.put("assignee2", "王五");map.put("assignee3", "赵财务");// 创建流程实例runtimeService.startProcessInstanceByKey("evection-uel", map);
}

启动成功后我们在 act_ru_variable中可以看到UEL表达式对应的赋值信息
(在act_ru_task表中,我们可以看到多了一条当前流程实例的当前任务信息,并且这个任务的ASSIGNEE_字段为张三,并且在act_ru_variable表中,我们可以看到我们在启动一个流程实例时在map中所有指定的变量)
在这里插入图片描述

UEL-method

在这里插入图片描述
图中:userBean 是 spring 容器中的一个 bean,表示调用该 bean 的 getUserId()方法。

UEL-method 与 UEL-value 结合

再比如:${ldapService.findManagerForEmployee(emp)}

ldapService 是 spring 容器的一个 bean,findManagerForEmployee 是该 bean 的一个方法,emp 是activiti流程变量, emp 作为参数传到 ldapService.findManagerForEmployee 方法中。

其它

表达式支持解析基础类型、 bean、 list、 array 和 map,也可作为条件判断。如下:${order.price > 100 && order.price < 250}

2.1.3 监听器分配

可以使用监听器来完成很多Activiti的流程业务。

我们在此处使用监听器来完成负责人的指定,那么我们在流程设计的时候就不需要指定assignee

Event选项

create:任务创建后触发
assignment:任务分配后触发
delete:任务完成后触发(idea里面的actiBpm插件中是delete,但是eclipse里面的插件是complete)
All:所有事件都触发

(idea的actiBpm插件ta喵的有问题)
在这里插入图片描述

自定义的任务监听器
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;public class MyTaskListener implements TaskListener {@Overridepublic void notify(DelegateTask delegateTask) {//(注意,这里需要添加判断条件)if("创建请假单".equals(delegateTask.getName())&& "create".equals(delegateTask.getEventName())){// 指定任务的负责人delegateTask.setAssignee("张三-Listener");}}
}
测试代码
/*** 先将新定义的流程部署到Activiti中数据库中*/
@Test
public void test01() {// 1.获取ProcessEngine对象ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 2.获取RepositoryService进行部署操作RepositoryService service = engine.getRepositoryService();// 3.使用RepositoryService进行部署操作Deployment deploy = service.createDeployment().addClasspathResource("bpmn/evection-listener.bpmn") // 添加bpmn资源.addClasspathResource("bpmn/evection-listener.png") // 添加png资源.name("出差申请流程-UEL").deploy();// 部署流程// 4.输出流程部署的信息System.out.println("流程部署的id:" + deploy.getId());System.out.println("流程部署的名称:" + deploy.getName());
}/*** 创建一个流程实例* 给流程定义中的 UEL表达式赋值*/
@Test
public void test02() {// 获取流程引擎ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取RuntimeService对象RuntimeService runtimeService = processEngine.getRuntimeService();// 创建流程实例runtimeService.startProcessInstanceByKey("evection-listener");
}

在这里插入图片描述
如下图(act_ru_task表中刚刚创建的流程实例的当前任务的指定负责人就是设定的张三-Listener
在这里插入图片描述

2.2 查询任务

查询任务负责人的待办任务

代码如下:

// 查询当前个人待执行的任务
@Test
public void findPersonalTaskList() {// 流程定义keyString processDefinitionKey = "myEvection1";// 任务负责人String assignee = "张三";// 获取TaskServiceTaskService taskService = processEngine.getTaskService();List<Task> taskList = taskService.createTaskQuery().processDefinitionKey(processDefinitionKey).includeProcessVariables().taskAssignee(assignee).list();for (Task task : taskList) {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());}
}

关联 businessKey

需求:
在 activiti 实际应用时,查询待办任务可能要显示出业务系统的一些相关信息。

比如:
查询待审批出差任务列表需要将出差单的日期、 出差天数等信息显示出来。出差天数等信息在业务系统中存在,而并没有在 activiti 数据库中存在,所以是无法通过 activiti 的 api查询到出差天数等信息。

实现:
在查询待办任务时,通过 businessKey(业务标识 )关联查询业务系统的出差单表,查询出出差天数等信息
(下面先查询张三的待办任务,然后根据待办任务查询流程实例,从流程实例中获取businessKey)

@Test
public void findProcessInstance() {// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取TaskServiceTaskService taskService = processEngine.getTaskService();// 获取RuntimeServiceRuntimeService runtimeService = processEngine.getRuntimeService();// 查询流程定义的对象Task task = taskService.createTaskQuery().processDefinitionKey("myEvection1").taskAssignee("张三").singleResult();// 使用task对象获取实例id !!!String processInstanceId = task.getProcessInstanceId();// 使用实例id,获取流程实例对象ProcessInstance processInstance =runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();// 使用processInstance,得到 businessKeyString businessKey = processInstance.getBusinessKey();System.out.println("businessKey==" + businessKey);
}

2.3办理任务

注意:在实际应用中,完成任务前需要校验任务的负责人是否具有该任务的办理权限 。
(在act_ru_task表中就记录了当前的所有任务,并且记录了每个任务对应的负责人,如果根据当前这个人 + 任务id 找不到这个任务,那么说明这个任务不属于当前这个人)

/*** 完成任务,判断当前用户是否有权限*/ 
@Test
public void completTask() {//任务idString taskId = "15005";//	任务负责人String assingee = "张三";//获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 创建TaskServiceTaskService taskService = processEngine.getTaskService();//	完成任务前,需要校验该负责人可以完成当前任务//	校验方法://	根据任务id和任务负责人查询当前任务,如果查到该用户有权限,就完成Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(assingee).singleResult(); if(task != null){taskService.complete(taskId); System.out.println("完成任务");}
}

3.流程变量

3.1、什么是流程变量

流程变量在 activiti 中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和 activiti 结合时少不了流程变量,流程变量就是 activiti 在管理工作流时根据管理需要而设置的变量。

比如:在出差申请流程流转时如果出差天数大于 3 天则由总经理审核,否则由人事直接审核, 出差天数就可以设置为流程变量,在流程流转时使用。

注意:虽然流程变量中可以存储业务数据可以通过activiti的api查询流程变量从而实现 查询业务数据, 但是不建议这样使用,因为业务数据查询由业务系统负责,activiti设置流程变量是为了流程执行需要而 创建。

3.2 流程变量类型

如果将 pojo 存储到流程变量中,必须实现序列化接口 serializable,为了防止由于新增字段无法反序列化,需要生成 serialVersionUID
在这里插入图片描述

3.3、流程变量作用域

流程变量的作用域可以是一个流程实例(processInstance),或一个任务(task),或一个执行实例 (execution)

3.3.1、globa变量

流程变量的默认作用域是流程实例。当一个流程变量的作用域为流程实例时,可以称为 global 变量

注意:

如: Global变量:userId(变量名):zhangsan(变量值)

global 变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。

3.3.2、local变量

任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大, 称为 local 变量。

Local 变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。Local 变量名也可以和 global 变量名相同,没有影响。

3.4、流程变量的使用方法

在属性上使用UEL表达式

可以在 assignee 处设置 UEL 表达式,表达式的值为任务的负责人,比如: ${assignee}, assignee 就是一个流程变量名称。

Activiti获取UEL表达式的值,即流程变量assignee的值 ,将assignee的值作为任务的负责人进行任务分配

在连线上使用UEL表达式

可以在连线上设置UEL表达式,决定流程走向。

比如:${price<10000} 。price就是一个流程变量名称,uel表达式结果类型为布尔类型。

如果UEL表达式是true,要决定 流程执行走向。

3.5流程变量使用

3.5.1需求

员工创建出差申请单,由部门经理审核,部门经理申请通过后3天以下由财务直接申批,3天以上先由 总经理审批,总经理审批通过后再由财务审批。
在这里插入图片描述

3.5.2 流程定义

先通过UEL-value来设置负责人

在这里插入图片描述
然后在分支线上来设置条件(点击这个连线,在condition中写上条件${num>=3}

在这里插入图片描述
那么还可以通过对象参数命名,比如 ${evection.num>=3}:

在这里插入图片描述
另一根线对应的设置 ${num<3}
在这里插入图片描述

或如下图设置为: ${evection.num<3}

在这里插入图片描述
然后可以将相关的资源文件拷贝到项目中,

3.5.3 使用Global变量

接下来使用Global变量控制流程

3.5.3.1 POJO创建

首先创建POJO对象(需要实现Serializable接口,最好在加上序列号版本)

/*** 出差申请的POJO对象*/
@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;
}
3.5.3.2 流程的部署
/*** 部署流程*/
@Test
public void test01() {// 1.获取ProcessEngine对象ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 2.获取RepositoryService进行部署操作RepositoryService service = engine.getRepositoryService();// 3.使用RepositoryService进行部署操作Deployment deploy = service.createDeployment().addClasspathResource("bpmn/evection-variable.bpmn") // 添加bpmn资源.addClasspathResource("bpmn/evection-variable.png") // 添加png资源.name("出差申请流程-流程变量").deploy();// 部署流程// 4.输出流程部署的信息System.out.println("流程部署的id:" + deploy.getId());System.out.println("流程部署的名称:" + deploy.getName());
}
3.5.3.3 设置流程变量
a.启动时设置流程变量

在启动流程时设置流程变量,变量的作用域是整个流程实例

/*** 启动流程实例,设置流程变量*/
@Test
public void test02() {ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();RuntimeService runtimeService = engine.getRuntimeService();// 流程定义keyString key = "evection-variable";// 创建变量集合Map<String, Object> variables = new HashMap<>();// 创建出差对象 POJOEvection evection = new Evection();// 设置出差天数evection.setNum(4d);// 定义流程变量到集合中(evection这个key需要和流程变量里的uel表达式一致)variables.put("evection", evection);// 设置assignee的取值(设置userTask中的流程变量对应的值)variables.put("assignee0", "张三1");variables.put("assignee1", "李四1");variables.put("assignee2", "王五1");variables.put("assignee3", "赵财务1");ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key, variables);// 输出信息System.out.println("获取流程实例名称:" + processInstance.getName());System.out.println("流程定义ID:" + processInstance.getProcessDefinitionId());
}

完成任务

/*** 完成任务*/
@Test
public void test03() {String key = "evection-variable";String assignee = "李四1";ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = engine.getTaskService();Task task = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assignee).singleResult();if (task != null) {// 因为上面设置的是4天, 所以流程就会从经理审批到总经理审批节点, 再到财务审批// (如果上面设置的是2天, 流程就会从经理审批直接到财务审批)taskService.complete(task.getId());System.out.println("任务执行完成...");}
}

通过startProcessInstanceByKey方法设置流程变量的作用域是一个流程实例,流程变量使用Map存储, 同一个流程实例map中的key相同,后者会覆盖前者

b.任务办理时设置

在完成任务时设置流程变量,该流程变量只有在该任务完成后其它结点才可使用该变量,它的作用域 是整个流程实例,如果设置的流程变量的key在流程实例中已存在相同的名字,则后设置的变量 替换 前边设置 的变量

这里需要在创建出差单任务完成时设置流程变量

/*** 启动流程实例,设置流程变量*/
@Test
public void test02() {ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();RuntimeService runtimeService = engine.getRuntimeService();// 流程定义keyString key = "evection-variable";// 创建变量集合Map<String, Object> variables = new HashMap<>();// 设置assignee的取值variables.put("assignee0", "张三1");variables.put("assignee1", "李四1");variables.put("assignee2", "王五1");variables.put("assignee3", "赵财务1");ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key, variables);// 输出信息System.out.println("获取流程实例名称:" + processInstance.getName());System.out.println("流程定义ID:" + processInstance.getProcessDefinitionId());
}/*** 完成任务*/
@Test
public void test03() {String key = "evection-variable";String assignee = "李四1";ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = engine.getTaskService();Task task = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assignee).singleResult();Map<String, Object> variables = new HashMap<>();// 创建出差对象 POJOEvection evection = new Evection();// 设置出差天数evection.setNum(4d);// 定义流程变量到集合中variables.put("evection", evection);if (task != null) {taskService.complete(task.getId(), variables);System.out.println("任务执行完成...");}
}

说明:
通过当前任务设置流程变量,需要指定当前任务id,如果当前执行的任务id不存在则抛出异常。 任务办理时也是通过map<key,value>设置流程变量,一次可以设置多个变量

c.当前流程实例设置

通过 流程实例id设置全局变量该流程实例必须未执行完成

@Test
public void setGlobalVariableByExecutionId() {// 当前流程实例执行 id,通常设置为当前执行的流程实例(act_ru_task表中的EXECUTION_ID_字段)String executionId = "2601";// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取RuntimeServiceRuntimeService runtimeService = processEngine.getRuntimeService();// 创建出差pojo对象Evection evection = new Evection();// 设置天数evection.setNum(3d);// 通过流程实例 id设置流程变量// (必须是当前流程实例还未走完,并且在此流程实例在用这个流程变量之前,设置进去才有作用哦)runtimeService.setVariable(executionId, "evection", evection);// 一次设置多个值// runtimeService.setVariables(executionId, variables)
}

注意:
executionId必须是当前 未结束的 流程实例的 执行id,通常此id设置流程实例 的id。也可以通runtimeService.getVariable()获取流程变量。

d.当前任务设置
@Test
public void setGlobalVariableByTaskId() {//当前待办任务idString taskId = "1404";// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = processEngine.getTaskService();Evection evection = new Evection();evection.setNum(3);//通过任务设置流程变量taskService.setVariable(taskId, "evection", evection);//一次设置多个值//taskService.setVariables(taskId, variables)
}

注意:

任务id必须是当前待办任务id,act_ru_task中存在。如果该任务已结束,会报错 也可以通过taskService.getVariable()获取流程变量。

3.5.4设置local流程变量

3.5.4.1、任务办理时设置

任务办理时设置local流程变量,当前运行的流程实例只能在该任务结束前使用,任务结束该变量无法在 当前流程实例使用,可以通过查询历史任务查询。

/**处理任务时设置local流程变量*/
@Test
public void completTask() {// 任务idString taskId = "1404";// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = processEngine.getTaskService();// 定义流程变量Map<String, Object> variables = new HashMap<String, Object>();Evection evection = new Evection();evection.setNum(3d);// 定义流程变量Map<String, Object> variables = new HashMap<String, Object>();// 变量名是holiday,变量值是holiday对象variables.put("evection", evection);// 设置local变量,作用域为该任务taskService.setVariablesLocal(taskId, variables);// 完成任务taskService.complete(taskId);
}

说明:
设置作用域为任务的local变量,每个任务可以设置同名的变量,互不影响。

3.5.4.2、通过当前任务设置
@Test
public void setLocalVariableByTaskId() {//	当前待办任务idString taskId = "1404";//	获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = processEngine.getTaskService();Evection evection = new Evection();evection.setNum(3d);//	通过任务设置流程变量taskService.setVariableLocal(taskId, "evection", evection);//	一次设置多个值//taskService.setVariablesLocal(taskId, variables)
}

注意:

任务id必须是当前待办任务id,act_ru_task中存在。

3.5.4.3、 Local变量测试1

如果上边例子中设置global变量改为设置local变量是否可行?为什么?

Local变量在任务结束后无法在当前流程实例执行中使用,如果后续的流程执行需要用到此变量则会报 错。

3.5.4.4、 Local变量测试2

在部门经理审核、总经理审核、财务审核时设置local变量,可通过historyService查询每个历史任务时 将流程变量的值也查询出来。

代码如下:

// 创建历史任务查询对象
HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery();// 查询结果包括 local变量
historicTaskInstanceQuery.includeTaskLocalVariables();for (HistoricTaskInstance historicTaskInstance : list) {System.out.println("==============================");System.out.println(" 任 务 id:" + historicTaskInstance.getId());System.out.println(" 任 务 名 称 :" + historicTaskInstance.getName());System.out.println("任务负责人:" + historicTaskInstance.getAssignee());System.out.println("任务local变量:" + historicTaskInstance.getTaskLocalVariables());}

注意:查询历史流程变量,特别是查询pojo变量需要经过反序列化,不推荐使用。

4. 组任务

4.1、需求

在流程定义中在任务结点的 assignee 固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn 文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。

针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务。

4.2、设置任务候选人

在流程图中任务节点的配置中设置 candidate-users(候选人),多个候选人之间用逗号分开。

在这里插入图片描述
查看bpmn文件

<userTask activiti:candidateUsers="lisi,wangwu"activiti:exclusive="true" id="_3" name="经理审批"/>

我们可以看到部门经理的审核人已经设置为 lisi,wangwu 这样的一组候选人,可以使用 activiti:candiateUsers=”用户 1,用户 2,用户 3”的这种方式来实现设置一组候选人

4.3、组任务

4.3.1、组任务办理流程

a、查询组任务

指定候选人,查询该候选人当前的待办任务。

候选人不能立即办理任务。

b、拾取(claim)任务

该组任务的所有候选人都能拾取。

将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人。

如果拾取后不想办理该任务? 需要将已经 拾取 的个人任务 归还 到组里边,将个人任务变成了组任务。

c、查询个人任务

查询方式同个人任务部分,根据assignee查询用户负责的个人任务。

d、办理个人任务

4.3.2、 查询组任务

根据候选人查询组任务
(之前的例子是已经指定了任务对应的负责人,但是现在为任务指定多个候选人。当部署该流程定义后,使用该流程定义开启1个流程实例,该流程实例的当前任务在act_ru_task表中ASSIGNEE_字段为null)

/*** 查询组任务*/
@Test
public void test03() {String key = "evection1";String candidateUser = "lisi";ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = engine.getTaskService();List<Task> list = taskService.createTaskQuery().processDefinitionKey(key).taskCandidateUser(candidateUser) // 候选人作为查询条件.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());}
}

4.3.3、 拾取组任务

候选人员拾取组任务后该任务变为自己的个人任务。
(当某个候选人拾取到任务后,act_ru_task表中该任务的ASSIGNEE_字段由null改为了该候选人userId。在任务被其中1个候选人拾取后,其它候选人将查不到该候选任务)

/*** 候选人 拾取任务*/
@Test
public void test04() {ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = engine.getTaskService();String taskId = "72505";// 候选人String userId = "lisi";// 拾取任务Task task = taskService.createTaskQuery().taskId(taskId).taskCandidateUser(userId) // 根据候选人查询.singleResult();if (task != null) {// 可以拾取任务(但是注意,这里的userId不一定必须是候选人里面去认领这个任务,可以任意userId去认领,//                     但是认领完成之后,不能调用taskService#claim(..)再次认领, 因为这个任务此时已经有任务负责人了//                     所以,候选人这个东西只是作为查询条件有用罢了//                     如果一定需要改任务负责人, 可以使用taskService#setAssignee(..)为任务指定对应的负责人,这个只会修改当前指定任务的assignee字段//                                           而taskService#delegateTask(..)任务委派除了会修改assignee字段还会把原来的assignee保存到owner字段上(注意,只会在owner初始为null并且调用taskService#delegateTask时仅会修改1次)                taskService.claim(taskId, userId);System.out.println("拾取成功");}
}

4.3.4、 查询个人待办任务

查询方式同个人任务查询

@Test
public void test03() {String key = "evection1";String candidateUser = "lisi";ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = engine.getTaskService();List<Task> list = taskService.createTaskQuery().processDefinitionKey(key)//.taskCandidateUser(candidateUser) // 候选人作为条件查询//.taskCandidateOrAssigned(candidateUser) // 候选人或指定负责人 作为 条件查询.taskAssignee(candidateUser) // 指定负责人作为条件查询.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());}
}

4.3.5、 办理个人任务

同个人任务办理

/*** 完成个人任务*/
@Test
public void test05() {String taskId = "72505";ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = engine.getTaskService();// 完成指定taskId的任务taskService.complete(taskId);System.out.println("完成任务:" + taskId);
}

4.3.6、 归还组任务

如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人
(当其中1个候选人拾取完任务后,这个人就成了当前该任务的负责人,此时,这个人也可以把任务归还,归还后,act_ru_task表中该任务数据的ASSIGNEE_字段为null)

/*** 归还任务*/
@Test
public void test06() {ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = engine.getTaskService();String taskId = "75002";String userId = "zhangsan";Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(userId).singleResult();if (task != null) {// 如果设置为null,归还组任务,任务没有负责人taskService.setAssignee(taskId, null);}
}

4.3.7、任务交接

任务负责人将任务交给其他负责人来处理
(可以调用taskService.setAssignee(taskId, userId)指定给任何人,不一定要给候选人,这里的候选人只是说可以通过候选人作为条件来查可以落到当前候选人的任务)

/*** 任务交接*/
@Test
public void test07() {ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = engine.getTaskService();String taskId = "75002";String userId = "zhangsan";Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(userId).singleResult();if (task != null) {// 设置该任务的新的负责人taskService.setAssignee(taskId, "赵六");}
}

4.3.8、 数据库表操作

查询当前任务执行表

SELECT * FROM act_ru_task

任务执行表,记录当前执行的任务,由于该任务当前是组任务,所以assignee为空,当拾取任务后该字段就是拾取用户的id

查询任务参与者

SELECT * FROM act_ru_identitylink

任务参与者,记录当前参考任务用户或组,当前任务如果设置了候选人,会向该表插入候选人记录,有 几个候选就插入几个

与act_ru_identitylink对应的还有一张历史表act_hi_identitylink,向act_ru_identitylink插入记录的同时 也会向历史表插入记录。任务完成

5.网关

网关用来控制流程的流向

5.1 排他网关ExclusiveGateway

5.1.1 什么是排他网关:

排他网关,用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支,

注意:排他网关只会选择一个为true的分支执行。如果有两个分支条件都为true,排他网关会选择id值 较小的一条分支去执行。

为什么要用排他网关?不用排他网关也可以实现分支,如:在连线的condition条件上设置分支条件。

在连线设置condition条件的缺点:如果条件都不满足,流程就结束了(是异常结束)。 如果 使用排他网关决定分支的走向,如下:
在这里插入图片描述
如果从网关出去的线所有条件都不满足则系统抛出异常。

org.activiti.engine.ActivitiException: No outgoing sequence flow of the 
exclusive gateway 'exclusivegateway1' could be selected for continuing the process
at 
org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.leave(
ExclusiveGatewayActivityBehavior.java:85)

5.1.2流程定义

排他网关图标,红框内:
在这里插入图片描述

5.1.3测试

在部门经理审核后,走排他网关,从排他网关出来的分支有两条,一条是判断出差天数是否大于3天,另 一条是判断出差天数是否小于等于3天。

设置分支条件时,如果所有分支条件都不是true,报错:

org.activiti.engine.ActivitiException: No outgoing sequence flow of the 
exclusive gateway 'exclusivegateway1' could be selected for continuing the 
processat org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.leave(
ExclusiveGatewayActivityBehavior.java:85)

5.2 并行网关ParallelGateway

5.2.1 什么是并行网关

并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出 顺序流的:

l fork分支:
并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。

l join汇聚:
所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。

注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。

与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。

例子:
在这里插入图片描述
说明:

技术经理和项目经理是两个execution分支,在act_ru_execution表有两条记录分别是技术经理和项目经理,act_ru_execution还有一条记录表示该流程实例。

待技术经理和项目经理任务全部完成,在汇聚点汇聚,通过parallelGateway并行网关。 并行网关在业务应用中常用于会签任务,会签任务即多个参与者共同办理的任务。

5.2.2 流程定义

并行网关图标,红框内:
在这里插入图片描述

5.2.3 测试

当执行到并行网关数据库跟踪如下:
当前任务表:SELECT * FROM act_ru_task
在这里插入图片描述上图中:有两个任务当前执行。
查询流程实例执行表:SELECT * FROM act_ru_execution

在这里插入图片描述
上图中,说明当前流程实例有多个分支(两个)在运行。
对并行任务的执行:
并行任务执行不分前后,由任务的负责人去执行即可。

执行技术经理任务后,查询当前任务表 SELECT * FROM act_ru_task
在这里插入图片描述

并行任务执行不分前后,由任务的负责人去执行即可。
执行技术经理任务后,查询当前任务表 SELECT * FROM act_ru_task
在这里插入图片描述

已完成的技术经理任务在当前任务表act_ru_task_已被删除。
在流程实例执行表:SELECT * FROM act_ru_execution有中多个分支存在且有并行网关的汇聚结点。
在这里插入图片描述
有并行网关的汇聚结点:说明有一个分支已经到汇聚,等待其它的分支到达。

当所有分支任务都完成,都到达汇聚结点后:
流程实例执行表:SELECT * FROM act_ru_execution,执行流程实例已经变为总经理审批,说明流程执行已经通过并行网关
在这里插入图片描述
总结:所有分支到达汇聚结点,并行网关执行完成。

5.3包含网关InclusiveGateway

5.3.1什么是包含网关

包含网关可以看做是排他网关和并行网关的结合体。

和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。

包含网关的功能是基于进入和外出顺序流的:

l 分支:
所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。

l 汇聚:
所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token的进入顺序流的分支都到达。这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后, 流程会穿过包含网关继续执行。

5.3.2流程定义:

出差申请大于等于3天需要由项目经理审批,小于3天由技术经理审批,出差申请必须经过人事经理审批。

包含网关图标,红框内:
在这里插入图片描述
定义流程:(这个图应该画错了,最后那个网关应该是排它网关才对)
在这里插入图片描述
注意:通过包含网关的每个分支的连线上设置condition条件(注意:这是包含网关与并行网关的区别,并行网关分支连线上设置的条件是无效的,但包含网关分支连线上设置的条件是有效的)。

5.3.3测试

如果包含网关设置的条件中,流程变量不存在,报错;

org.activiti.engine.ActivitiException: Unknown property used in expression:
${evection.num>=3}

需要在流程启动时设置流程变量evection.num。

1)、当流程执行到第一个包含网关后,会根据条件判断,当前要走哪几个分支:
流程实例执行表:SELECT * FROM act_ru_execution
在这里插入图片描述
第一条记录:包含网关分支。

后两条记录代表两个要执行的分支:
ACT_ID = “_13” 代表 项目经理审批
ACT_ID = “_5” 代表 人事经理审批

当前任务表:ACT_RU_TASK
在这里插入图片描述
上图中,项目经理审批、人事经理审批 都是当前的任务,在并行执行。
如果有一个分支执行先走到汇聚结点的分支,要等待其它执行分支走到汇聚。

2)、先执行项目经理审批,然后查询当前任务表:ACT_RU_TASK
在这里插入图片描述
当前任务还有人事经理审批需要处理。
流程实例执行表:SELECT * FROM act_ru_execution
在这里插入图片描述
发现人事经理的分支还存在,而项目经理分支已经走到ACT_ID = _18的节点。而ACT_ID= 18就是第二个包含网关
这时,因为有2个分支要执行,包含网关会等所有分支走到汇聚才能执行完成。

3)、执行人事经理审批
然后查询当前任务表:ACT_RU_TASK
在这里插入图片描述
当前任务表已经不是人事经理审批了,说明人事经理审批已经完成。流程实例执行表:SELECT * FROM act_ru_execution
在这里插入图片描述
包含网关执行完成,分支和汇聚就从act_ru_execution删除。
小结:在分支时,需要判断条件,符合条件的分支,将会执行,符合条件的分支最终才进行汇聚。

5.4 事件网关EventGateway

事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获事件。 当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。与此同时,会为每个外出顺序流创建相对的事件 订阅。

事件网关的外出顺序流和普通顺序流不同,这些顺序流不会真的"执行", 相反它们让流程引擎去决定执行到事件网关的流程需要订阅哪些事件。 要考虑以下条件:

  1. 事件网关必须有两条或以上外出顺序流;
  2. 事件网关后,只能使用intermediateCatchEvent类型(activiti不支持基于事件网关后连接ReceiveTask)
  3. 连接到事件网关的中间捕获事件必须只有一个入口顺序流。

5.4.1流程定义

事件网关图标,红框内
在这里插入图片描述
intermediateCatchEvent:
在这里插入图片描述
intermediateCatchEvent支持的事件类型:
Message Event: 消息事件
Singal Event: 信号事件
Timer Event: 定时事件

在这里插入图片描述
使用事件网关定义流程:
在这里插入图片描述

三 、Activiti整合篇

1. 和Spring整合

1.1 添加相关的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.bobo</groupId><artifactId>ActivitiDemo02Spring</artifactId><version>1.0-SNAPSHOT</version><properties><slf4j.version>1.6.6</slf4j.version><log4j.version>1.2.12</log4j.version></properties><dependencies><dependency><groupId>org.activiti</groupId><artifactId>activiti-engine</artifactId><version>7.0.0.Beta1</version></dependency><dependency><groupId>org.activiti</groupId><artifactId>activiti-spring</artifactId><version>7.0.0.Beta1</version></dependency><dependency><groupId>org.activiti</groupId><artifactId>activiti-bpmn-model</artifactId><version>7.0.0.Beta1</version></dependency><dependency><groupId>org.activiti</groupId><artifactId>activiti-bpmn-converter</artifactId><version>7.0.0.Beta1</version></dependency><dependency><groupId>org.activiti</groupId><artifactId>activiti-json-converter</artifactId><version>7.0.0.Beta1</version></dependency><dependency><groupId>org.activiti</groupId><artifactId>activiti-bpmn-layout</artifactId><version>7.0.0.Beta1</version><exclusions><exclusion><groupId>com.github.jgraph</groupId><artifactId>jgraphx</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.activiti.cloud</groupId><artifactId>activiti-cloud-services-api</artifactId><version>7.0.0.Beta1</version></dependency><dependency><groupId>aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.5.4</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.11</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.7.RELEASE</version></dependency><!-- log start --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>${log4j.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>${slf4j.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-nop</artifactId><version>${slf4j.version}</version></dependency><!-- log end --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.5</version></dependency><dependency><groupId>commons-dbcp</groupId><artifactId>commons-dbcp</artifactId><version>1.4</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version></dependency></dependencies><repositories><repository><id>alfresco</id><name>Activiti Releases</name><url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases/</url><releases><enabled>true</enabled></releases></repository></repositories><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.18.1</version><configuration><skipTests>true</skipTests></configuration></plugin></plugins></build></project>

1.2 添加整合的配置文件

添加一个Spring的配置文件,并在其中完成Activiti的整合操作

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 数据源 --><bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/activiti? characterEncoding=utf-8&amp;nullCatalogMeansCurrent=true&amp;serverTimezone=UTC"/><property name="username" value="root"/><property name="password" value="123456"/><property name="maxActive" value="3"/><property name="maxIdle" value="1"/></bean><!-- 工作流引擎配置bean --><bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"><!-- 数据源 --><property name="dataSource" ref="dataSource"/><!-- 使用spring事务管理器 --><property name="transactionManager" ref="transactionManager"/><!--数据库策略false:	默认值。activiti在启动时,会对比数据库表中保存的版本,如果没有表 或者版本不匹配,将抛出异常。(生产环境常用)true:	activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创 建。(开发时常用)create_drop:  在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)。(单元测试常用)drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)。--><property name="databaseSchemaUpdate" value="drop-create"/></bean><!-- 流程引擎 --><bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean"><property name="processEngineConfiguration" ref="processEngineConfiguration"/></bean><!-- 资源服务service --><bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService"/><!-- 流程运行service --><bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService"/><!-- 任务管理service --><bean id="taskService" factory-bean="processEngine" factory-method="getTaskService"/><!-- 历史管理service --><bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService"/><!-- 事务管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!-- 通知 --><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><!-- 传播行为 --><tx:method name="save*" propagation="REQUIRED"/><tx:method name="insert*" propagation="REQUIRED"/><tx:method name="delete*" propagation="REQUIRED"/><tx:method name="update*" propagation="REQUIRED"/><tx:method name="find*" propagation="SUPPORTS" read-only="true"/><tx:method name="get*" propagation="SUPPORTS" read-only="true"/></tx:attributes></tx:advice><!-- 切面,根据具体项目修改切点配置<aop:config proxy-target-class="true"><aop:advisor advice-ref="txAdvice"pointcut="execution(*com.bobo.service.impl..(..))"/></aop:config>-->
</beans>

databaseSchemaUpdate的取值注意:

  • false: 默认值。activiti在启动时,会对比数据库表中保存的版本,如果没有表或者版本不匹 配,将抛出异常。(生产环境常用)
  • true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建。(开发时常用)
  • create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)。(单元测试常用)
  • drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)。

1.3 创建测试类测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:activiti-spring.xml"})
public class ActivitiTest {@Autowiredprivate RepositoryService repositoryService;@Testpublic void test01() {System.out.println(repositoryService);}}

通过方法的执行我们能够发现相关的表结构在数据库中完成了创建,说明Activiti和Spring的整合成功。

2. 和SpringBoot的整合

Activiti7发布正式版本之后,它和SpringBoot2.x已经完全整合开发了

2.1 添加相关的依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions>
</dependency>
<dependency><groupId>org.activiti</groupId><artifactId>activiti-spring-boot-starter</artifactId><version>7.0.0.Beta2</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>

2.2 修改配置文件

# 配置Spring的数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql:///activiti?characterEncoding=utf- 8&amp;nullCatalogMeansCurrent=true&amp;serverTimezone=UTC spring.datasource.name=root
spring.datasource.password=123456# activiti的配置
#1.flase:默认值。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出  异常
#2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
#3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表) 
#4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
spring.activiti.database-schema-update=true
# 检测历史表是否存在, Activiti7中默认是没有开启数据库历史记录的,启动数据库历史记录
spring.activiti.db-history-used=true
#记录历史等级 可配置的历史级别有none, activity, audit, full 
#none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。#activity:级别高于none,保存流程实例与流程行为,其他数据不保存。#audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。audit为history的默认  值。
#full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数  据,包括一些流程参数等。
spring.activiti.history-level=full
# 校验流程文件,默认校验resouces下的 process 文件夹里的流程文件
spring.activiti.check-process-definitions=false

2.3 整合SpringSecurity

因为Activiti7和SpringBoot整合后,默认情况下,集成了SpringSecurity安全框架,这样我们就要准备SpringSecurity的相关配置信息

添加一个SpringSecurity的工具类

@Component
public class SecurityUtil {private Logger logger = LoggerFactory.getLogger(SecurityUtil.class);@Autowired@Qualifier("myUserDetailsService")private UserDetailsService userDetailsService;public void logInAs(String username) {UserDetails user = userDetailsService.loadUserByUsername(username);if (user == null) {throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user");}logger.info("> Logged in as: " + username);SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() {@Overridepublic Collection<? extends GrantedAuthority>getAuthorities() {return user.getAuthorities();}@Overridepublic Object getCredentials() {return user.getPassword();}@Overridepublic Object getDetails() {return user;}@Overridepublic Object getPrincipal() {return user;}@Overridepublic boolean isAuthenticated() {return true;}@Overridepublic void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {}@Overridepublic String getName() {return user.getUsername();}}));org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);}
}

这个类可以从Activiti7官方提供的Example中找到。

添加一个SpringSecurity的配置文件

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;@Configuration
public class SpringSecurityConfiguration {private Logger logger = LoggerFactory.getLogger(SpringSecurityConfiguration.class);@Beanpublic UserDetailsService myUserDetailsService() {InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();//这里添加用户,后面处理流程时用到的任务负责人,需要添加在这里String[][] usersGroupsAndRoles = {{"jack", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},{"rose", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},{"tom", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},{"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},{"system", "password", "ROLE_ACTIVITI_USER"},{"admin", "password", "ROLE_ACTIVITI_ADMIN"},};for (String[] user : usersGroupsAndRoles) {List<String> authoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length));logger.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]");inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]),authoritiesStrings.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList())));}return inMemoryUserDetailsManager;}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

2.4 创建bpmn文件

创建一个简单的bpmn文件,并设置任务的用户组,CandidateGroups,CandidateGroups中的内容 要与在SpringSecurity的配置文件中配置的用户组的名称要保持一致,可以填写activitTeam或者otherTeam。这样填写的好处是,当不确定到底由谁来负责当前的任务的时候,只要是Groups内的用户 都可以拾取这个任务

在这里插入图片描述

Activiti7中可以自动部署流程,前提是在resources目录下,创建一个新的目录processes,用来放置bpmn文件
在这里插入图片描述

2.5 单元测试

import com.bobo.utils.SecurityUtil;
import org.activiti.api.process.model.ProcessDefinition;
import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.model.builders.ProcessPayloadBuilder;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.activiti.api.task.model.Task;
import org.activiti.api.task.model.builders.ClaimTaskPayloadBuilder;import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.model.payloads.ClaimTaskPayload;
import org.activiti.api.task.runtime.TaskRuntime;
import org.activiti.engine.RepositoryService;
import org.activiti.runtime.api.ProcessRuntime;
import org.activiti.runtime.api.model.ProcessInstance;
import org.activiti.runtime.api.model.builders.ProcessPayloadBuilder;
import org.activiti.runtime.api.query.Pageable;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class ActSpringbootApplicationTests {@Autowiredprivate ProcessRuntime processRuntime;@Autowiredprivate TaskRuntime taskRuntime;@Autowiredprivate SecurityUtil securityUtil;@Autowiredprivate RepositoryService repositoryService;@Testvoid contextLoads() {System.out.println(taskRuntime);}/*** 查询流程的定义*/@Testpublic void test02() {securityUtil.logInAs("system");Page<ProcessDefinition> processDefinitionPage =processRuntime.processDefinitions(Pageable.of(0, 10));System.out.println("可用的流程定义数量:" +processDefinitionPage.getTotalItems());for (ProcessDefinition processDefinition : processDefinitionPage.getContent()) {System.out.println("流程定义:" + processDefinition);}}/*** 部署流程*/@Testpublic void test03() {repositoryService.createDeployment().addClasspathResource("processes/my-evection.bpmn").addClasspathResource("processes/my-evection.png").name("出差申请单").deploy();}/*** 启动流程实例*/@Testpublic void test04() {securityUtil.logInAs("system");ProcessInstance processInstance =processRuntime.start(ProcessPayloadBuilder.start().withProcessDefinitionKey("my-evection").build());System.out.println("流程实例id:" + processInstance.getId());}/*** 任务查询、拾取及完成操作*/@Testpublic void test05() {securityUtil.logInAs("jack");Page<Task> tasks = taskRuntime.tasks(Pageable.of(0, 10));if (tasks != null && tasks.getTotalItems() > 0) {for (Task task : tasks.getContent()) {// 拾取任务taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());System.out.println(" 任 务 :" + task);taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(task.getId()).build());}}Page<Task> taskPage2 = taskRuntime.tasks(Pageable.of(0, 10));if (taskPage2.getTotalItems() > 0) {System.out.println("任务:" + taskPage2.getContent());}}}

这篇关于Activiti7学习(进阶篇)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/339855

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件