Activity7 进阶篇


Activity7 入门篇(官方设计器)
案例源码

任务分配

固定任务

在指定用户任务的审批人时,我们是直接指派的固定账号。但是为了保证流程设计审批的灵活性,我们需要各种不同的分配方式,所以我们先来介绍在Activiti7中我们可以使用的相关分配方式。
固定分配就是我们在前面介绍的,在绘制流程图或者直接在流程文件中通过Assignee来指定的方式。

表达式

Activiti 使用 UEL进行表达式解析。UEL代表Unified Expression Language,是 EE6规范的一部分。为了在所有环境上支持UEL标准的所有最新特性,我们使用JUEL的修改版本。
表达式可以用于例如 Java服务任务Java Service tasks,执行监听器Execution Listeners,任务监听器Task Listeners 与条件流 Conditional sequence flows。尽管表达式与方法表达式两种表达式,通过 Activiti 的抽象使他们都可以在需要 expression(表达式)的地方使用。

${myVar}
${myBean.myProperty}

值表达式

我们通过UEL表达式的方式进行站位。
image.png
然后我们做流程的部署和启动。

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取 processEngine 对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取 RepositoryService 对象
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.使用 RepositoryService 对象完成流程部署
    Deployment deploy = repositoryService.createDeployment()
    .addClasspathResource("flow/test2.bpmn20.xml")
    .name("值表达式").deploy();// 是流程部署的行为,可以部署多个流程定义
    System.out.println(deploy.getId());
    System.out.println(deploy.getName());
}

我们发起流程后,根据流程设计应该需要进到人事审批,但是审批的用户是 ${assign1}是一个流程变量。需要我们对变量进行赋值。

/**
 * 发起一个流程
 */
@Test
public void test3() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程,需要通过 runtimeService 来实现
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 对流程变量进行赋值操作
    Map<String, Object> variables = new HashMap<>();
    variables.put("assign1", "jack");
    variables.put("assign2", "mark");
    // 通过流程定义 ID 来启动流程,返回的是流程实例对象
    ProcessInstance processInstance = runtimeService
            .startProcessInstanceById("test1:2:10003", variables);
    System.out.println(processInstance.getId());
    System.out.println(processInstance.getDeploymentId());
    System.out.println(processInstance.getDescription());
}

执行成功后,可以在act_ru_variable表中看到刚才map中的数据。
image.png
涉及到的表:

  • act_ru_variable:流程变量表

方法表达式

方法表达式Method expression:调用一个方法,可以带或不带参数。当调用不带参数的方法时,要确保在方法名后添加空括号(已避免与值表达式混淆)。传递的参数可以是字面值,也可以是表达式,他们会被自动解析。例如:

${printer.print()}
${myBean.getAssignee()}
${myBean.addNewOrder('orderName')}
${myBean.dosimething(myVar,execution)}

myBean 是Spring容器中的Bean对象,表示调用的是Bean的addNewOrder方法,我们通过案例来演示下,先定义对应的Service

@Component
public class MyBean {
    @Bean("myBean")
    public String getAssignee() {
        System.out.println("方法执行了...");
        return "sea";
    }
}

然后在绘制流程图就可以指派了
image.png
然后我们就可以部署,并发起一个流程了。

/**
     * 流程部署操作
     */
    @Test
    public void test1() {
        // 1.获取 processEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 2.获取 RepositoryService 对象
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 3.使用 RepositoryService 对象完成流程部署
        Deployment deploy = repositoryService.createDeployment()
                .addClasspathResource("flow/test3.bpmn20.xml")
                .name("请假流程-方法表达式").deploy();// 是流程部署的行为,可以部署多个流程定义
        System.out.println(deploy.getId());
        System.out.println(deploy.getName());
    }

/**
     * 发起一个流程
     */
    @Test
    public void test3() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 发起流程,需要通过 runtimeService 来实现
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 通过流程定义 ID 来启动流程,返回的是流程实例对象
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceById("test3:1:22503");
        System.out.println(processInstance.getId());
        System.out.println(processInstance.getDeploymentId());
        System.out.println(processInstance.getDescription());
    }

同时代办中的审批人就是方法表达式的返回结果。
image.png

监听器配置

可以使用监听器来完成很多Activiti的流程业务。我们在此处使用监听器来完成责任人的指定,那么我们在流程设计的时候就不需要指定assignee。使用自定义监听器:

@Component
public class MyListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        System.out.println("自定义监听器执行了...");
        if (EVENTNAME_CREATE.equals(delegateTask.getEventName())) {
            // 表示Task的创建事件被触发了
            // 指定当前task节点的处理人
            delegateTask.setAssignee("sea");
        }
    }
}

在配置流程的时候关联监听器。
image.png
任务相关事件包括:

  • Event:
    • Create:任务创建后触发。
    • Assignment:任务分配后触发。
    • Delete:任务完成后触发。
    • All:所有事件发生都触发。

然后我们就可以部署,并发起一个流程了。

 /**
     * 流程部署操作
     */
    @Test
    public void test1() {
        // 1.获取 processEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 2.获取 RepositoryService 对象
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 3.使用 RepositoryService 对象完成流程部署
        Deployment deploy = repositoryService.createDeployment()
                .addClasspathResource("flow/test4.bpmn20.xml")
                .name("监听器配置").deploy();// 是流程部署的行为,可以部署多个流程定义
        System.out.println(deploy.getId());
        System.out.println(deploy.getName());
    }

    /**
     * 发起一个流程
     */
    @Test
    public void test2() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 发起流程,需要通过 runtimeService 来实现
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 通过流程定义 ID 来启动流程,返回的是流程实例对象
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceById("test3:2:27503");
        System.out.println(processInstance.getId());
        System.out.println(processInstance.getDeploymentId());
        System.out.println(processInstance.getDescription());
    }

在代办中的任务处理人就是监听器中设置的信息。
image.png

流程变量

流程变量可以用将数据添加到流程的运行时状态中,或者更具体的说,变量作用域中。改变实体的各种API可以用来更新这些附件的变量。一般来说,一个变量由一个名称和一个值组成。名称用于在整个流程中识别变量。例如,如果一个活动 设置了一个名为var的变量,那么后续活动中可以通过使用这个名称来访问它,变量的值就是一个Java对象。

运行时变量

流程实例运行时的变量,存在 act_ru_variable 表中。在流程实例运行结束时,此实例的变量在表中删除。在流程实例创建和启动时,可以设置流程变量,所有的 startProcessInstanceXXX方法搜友一个可变参数用于设置变量。例如RuntimeService

ProcessInstance startProcessInstanceById(String processDefinitionId, Map<String, Object> variables);

也可以在流程执行中加入变量。例如:RuntimeService

void setVariable(String executionId, String variableName,Object value);
void setVariableLocal(String executionId, String variableName,Object value);
void setVariables(String executionId, Map<String,? extends Object> variables);
void setVariablesLocal(String executionId, Map<String,? extends Object> variables);

读取变量方法:

Map<String,Object> getVariables(String executionId);
Map<String,Object> getVariablesLocal(String executionId);
Map<String,Object> getVariables(String executionId, Collection<String> variableNames);
Map<String,Object> getVariablesLocal(String executionId, Collection<String> variableNames);
Object getVariable(String executionId, String variableName);
<T> T getVariable(String execution,String variableName,Class<T> variableClass);

注意:由于流程实例结束时,对应在运行时表的数据跟着被删除。所以查询一个已经结束的流程实例变量,只能在历史变量表中查找。
当前运行时变量我们也可以根据对应的作用域把他分为全局变量局部变量

全局变量

流程变量的默认作用域是流程实例。当一个流程变量的作用域为流程实例时,可以称为global 变量。
注意:global变量中变量名不允许重复,设置相同名称的变量后,设置的值会覆盖前面设置的变量值。

  • 定义监听器
@Component
public class MyFirstListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        Map<String, Object> variables = delegateTask.getVariables();
        for (String key : variables.keySet()) {
            Object o = variables.get(key);
            System.out.println(key + " ======: " + o);
            delegateTask.setVariable(key, o+"111");
        }
    }
}
  • 流程设计

image.png
然后我们就可以部署,并发起一个流程了。

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取 processEngine 对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取 RepositoryService 对象
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.使用 RepositoryService 对象完成流程部署
    Deployment deploy = repositoryService.createDeployment()
            .addClasspathResource("flow/test5.bpmn20.xml")
            .name("流程变量-全局变量").deploy();// 是流程部署的行为,可以部署多个流程定义
    System.out.println(deploy.getId());
    System.out.println(deploy.getName());
}

/**
 * 发起一个流程
 */
@Test
public void test2() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程,需要通过 runtimeService 来实现
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 对流程变量进行赋值操作
    Map<String, Object> variables = new HashMap<>();
    variables.put("assign1", "jack");
    variables.put("assign2", "mark");
    variables.put("ass1", "变量1");
    variables.put("ass2", 399);
    variables.put("ass3", "北京");
    variables.put("ass4", "大家好");
    // 通过流程定义 ID 来启动流程,返回的是流程实例对象
    ProcessInstance processInstance = runtimeService
            .startProcessInstanceById("test5:1:32503", variables);
    System.out.println(processInstance.getId());
    System.out.println(processInstance.getDeploymentId());
    System.out.println(processInstance.getDescription());
}
  • 查询当前流程的变量信息
/**
 * 查询当前流程的变量信息
 */
@Test
public void test4() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 获取某个流程实例的变量信息
    Map<String, VariableInstance> variableInstances = runtimeService.getVariableInstances("35008");
    for (String s : variableInstances.keySet()) {
        System.out.println(s + "==" + variableInstances.get(s));
    }
}

局部变量

任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大,称为local变量。
Local变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。Local变量名也可以和global变量名相同,没有影响。
我们通过RuntimeService 设置的Local变量,绑定的是executionId。

/**
 * 设置局部变量
 */
@Test
public void test5() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    runtimeService.setVariableLocal("35008", "orderId", "10001");
    runtimeService.setVariableLocal("35008", "price", 666);
}

我们还可以通过TaskService来绑定本地流程变量,绑定的是taskId。

/**
 * 设置局部变量
 */
@Test
public void test6() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    taskService.setVariableLocal("35008", "payId", "101001");
}
  • 查询当前流程的变量信息
/**
 * 查询当前流程的变量信息
 */
@Test
public void test4() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    Map<String, Object> variablesLocal = runtimeService.getVariablesLocal("35008");
    System.out.println(variablesLocal);
    TaskService taskService = processEngine.getTaskService();
    Map<String, Object> variables = taskService.getVariables("35008");
    System.out.println(variables);
}

历史变量

历史变量,存如act_hi_varinst表中。在流程启动时,流程变量会同时存入历史变量表中;在流程结束时,历史表中的变量任然存在。可以理解为"永久代"的流程变量。
获取完成的 ID为 xxx的流程实例中所有的HistoricVariableInstances(历史变量实例),并以变量名排序。

/**
 * 流程变量历史信息查询
 */
@Test
public void test9() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 所有工作流的历史数据的查询都需要通过HistoryService 来实现
    HistoryService historyService = processEngine.getHistoryService();
    List<HistoricVariableInstance> list = historyService.createHistoricVariableInstanceQuery()
            .processInstanceId("35001")//可以指定条件
            .list();
    for (HistoricVariableInstance instance : list) {
        System.out.println(instance);
    }
}

身份服务

在流程定义中任务节点的 assignee 固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。针对这种情况可以给任务设置多个候选人或者候选人组,可以从候选人中选择参与者来完成任务。

审批人

前面案例中直接指派审批用户处理。

候选人

一个节点可以有多个人同时具有审批的权限。这时我们就可以通过候选人来处理。

绘制流程图

我们定义一个简单的审批的流程图。

  • 人事审批中我们设置多个候选人来处理,分别是张三、李四、王五
  • 经理审批中分别是zhangsan、lisi、wangwu

image.png

部署和发布流程

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取 processEngine 对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取 RepositoryService 对象
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.使用 RepositoryService 对象完成流程部署
    Deployment deploy = repositoryService.createDeployment().addClasspathResource("flow/test6.bpmn20.xml")
            .name("候选人").deploy();// 是流程部署的行为,可以部署多个流程定义
    System.out.println("流程部署ID:" + deploy.getId());
    System.out.println("流程部署名称:" + deploy.getName());
}

/**
 * 发起一个流程
 */
@Test
public void test2() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程,需要通过 runtimeService 来实现
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 通过流程定义 ID 来启动流程,返回的是流程实例对象
    ProcessInstance processInstance = runtimeService.startProcessInstanceById("test6:1:47503");
    System.out.println("流程定义的ID = " + processInstance.getProcessDefinitionId());
    System.out.println("流程实例的ID = " + processInstance.getId());
}

启动流程实例后,在act_ru_task表中审批人是空的。
image.png
但是在对应的act_ru_identitylink表中我们可以看到对应的候选人信息。
image.png

任务的拾取

任务被候选人拾取。

/**
 * 拾取操作:代办任务
 * 从候选人 -> 处理人
 */
@Test
public void test4() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    List<Task> list = taskService.createTaskQuery().taskCandidateUser("张三")//根据登录的候选人查询审批任务
            .list();
    if (list != null && list.size() > 0) {
        for (Task task : list) {
            System.out.println(task.getId());
            taskService.claim(task.getId(), "张三");
        }
    }
}

如果一个任务被候选人拾取后,其他候选人就查询不到该任务了。

任务的归还

拾取任务后如果不想操作那么可以归还任务

/**
 * 归还操作:代办任务
 * 该用户放弃审批,交由其他候选人进行审批。
 * 其他候选人需要先拾取,才能进行审批。
 */
@Test
public void test5() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    List<Task> list = taskService.createTaskQuery().taskCandidateOrAssigned("张三")//根据审批人和候选人查询代办任务
            .list();
    if (list != null && list.size() > 0) {
        for (Task task : list) {
            System.out.println(task.getId());
            // 归还操作本质就是设置审批人为空
            taskService.unclaim(task.getId());
        }
    }
}

候选人组

当候选人很多的情况下,我们可以分组来处理。先创建组,然后把用户分配到这个组中。

绘制流程图

image.png

部署发布拾取归还

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取 processEngine 对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取 RepositoryService 对象
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.使用 RepositoryService 对象完成流程部署
    Deployment deploy = repositoryService.createDeployment().addClasspathResource("flow/test7.bpmn20.xml").name("候选人组").deploy();// 是流程部署的行为,可以部署多个流程定义
    System.out.println("流程部署ID:" + deploy.getId());
    System.out.println("流程部署名称:" + deploy.getName());
}

/**
 * 发起一个流程
 */
@Test
public void test2() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程,需要通过 runtimeService 来实现
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 通过流程定义 ID 来启动流程,返回的是流程实例对象
    ProcessInstance processInstance = runtimeService.startProcessInstanceById("test7:1:57503");
    System.out.println("流程定义的ID = " + processInstance.getProcessDefinitionId());
    System.out.println("流程实例的ID = " + processInstance.getId());
}

/**
 * 候选人任务审批查询
 */
@Test
public void test3() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    String group = "销售组";// 根据登录的用户查询出对应的组
    List<Task> list = taskService.createTaskQuery()
            .taskCandidateGroup(group)
            .list();
    if (list != null && list.size() > 0) {
        for (Task task : list) {
            System.out.println(task.getId());
        }
    }
}

/**
 * 拾取操作:代办任务
 * 从候选人 -> 处理人
 */
@Test
public void test4() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    String group = "销售组";// 根据登录的用户查询出对应的组
    List<Task> list = taskService.createTaskQuery()
            .taskCandidateGroup(group)//根据登录的候选人查询审批任务
            .list();
    if (list != null && list.size() > 0) {
        for (Task task : list) {
            System.out.println(task.getId());
            taskService.claim(task.getId(), "张三1");
        }
    }
}

/**
 * 归还操作:代办任务
 * 该用户放弃审批,交由其他候选人进行审批。
 * 其他候选人需要先拾取,才能进行审批。
 */
@Test
public void test5() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    List<Task> list = taskService.createTaskQuery()
            .taskAssignee("张三1")
            .list();
    if (list != null && list.size() > 0) {
        for (Task task : list) {
            System.out.println(task.getId());
            // 归还操作本质就是设置审批人为空
            taskService.unclaim(task.getId());
        }
    }
}

任务交接

/**
 * 交接操作:获取用户审批权限的用户没有时间审批。
 * 它可以不归还而是做任务的交接,把这个任务让另外一个人处理。
 */
@Test
public void test6() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    List<Task> list = taskService.createTaskQuery()
            .taskAssignee("张三1")
            .list();
    if (list != null && list.size() > 0) {
        for (Task task : list) {
            System.out.println(task.getId());
            taskService.setAssignee(task.getId(), "张三2");
        }
    }
}

网关篇

网关可控制流程的执行流向。常用于拆分或合并复杂的流程场景。在Activti7中,有以下几种类型的网关:

  • 排他网关(Exelusive Gateway):用于在流程中进行条件判断,根据不同的条件选择不同的分支路径,只有满足条件的分支会被执行,其他分支会披忽略。
  • 并行网关(Parallel Gateway):用于将流程分成多个并行的分支,这些分支可以同时执行。当所有分支都执行完毕后,流程会继续向下执行。
  • 包容网关(inclusive Gateway):用于根据多个条件的组合情况选择分支路径。可以选择满足任意一个条件的分支执行,或者选择满足所有条件的分支执行。
  • 事件网关(Event Gateway):用于根据事件的触发选择分支路径。当指定的事件触发时,流程会选择对应的分支执行。

这些网关可以根据实际需求灵活地组合使用,以实现不同的流程控制逻辑。Actiiti7提供了直观的图形化界面,用户可以通过拖拽和连接网关来定义流程的分支和合并。同时,Activiti7还提供了丰富的API和扩展点,方便开发人员进行二次开发和定制。接下来我们分别介绍下各种网关的应用。

排他网关

排他网关(exclusive gateway)(也叫异或网关 XOR gateway,或者更专业的,基于数据的排他网关 exclusivedata·based gateway),用于对流程中的决策建模。当执行到达这个网关时,会按照所有出口顺序流定义的顺序对它们进行计算,选择第一个条件计算为true的顺序流(当没有设置条件时,认为顺序流为tue)继续流程。
排他网关用内部带有X图标的标准网关(墓形)表示,"X图标代表异或 的含义。请注意内部没有图标的网关默认为排他网关。BPMN2.0规范不允许在同一个流程中混合使用有及没有X的形标志。
image.png

并行网关

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

  • fork分支:并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
  • join汇聚:所有到达并行网关,在此等待的进入分支, 真到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。

注意,如果同一个并行网关有多个进入和多个外出顺序流,它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支,
与其他网关的主要区别是,并行网关不会解析条件。即便顺序流中定义了条件,也会被忽略。
image.png

  • act_ru_task

image.png

  • act_ru_execution

image.png
执行实例的概念

  • 主流程实例:流程启动就会维护一条记录,act_ru_execution表中PARENT_ID为null的记录。
  • 子流程实例:流程的每一步操作,都会更新子流程实例,表示当前流程的执行进度。如果进入的是并行网关,则会产生3个子流程实例和一个主流程实例。注意rev_版本的变化。

包容网关

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

  • 分支:所有外出顺序流的承件都会被解折,结果为true的)顺序流会以并行方式组续执行, 会为每个顺序流创建一个分变。
  • 汇频:所有并行分支到达包含网关,会进入等待状本,直到每个包含流程token的进入顺序渣的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇紧之后,流程会穿过包含网关继续执行。

image.png

事件网关

事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获事件。当流到达一个基于事件网关,网关会进入等待状态:会暂停执行,与此同时,会为每个外出顺序流创建相对的事件订阅。
事件网关的外出顺序流和普通顺序流不同,这些顺序流不会真的“执行”,相反它们让流程引擎去决定执行到事件网关的流程需要订阅哪些事件,要考虑以下条件:

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

事件篇

事件(event)通常用于为流程生命周期中发生的事情建模。事件总是图形化为圆圈。在BPMN 2.0中,有两种主要的事件分类:捕获(catching)与抛出(throwing)事件

  • 捕获:当流程执行到达这个事件时,会待直到触发器动作。触发器的类型由其中的图标,或者说XML中的类型西明而定义。捕获事件与抛出事件显示上的区别,是其内部的图标没有填充(即是白色的)
  • 抛出:当流程执行到达这个事件时,会触发一个触发器。触发器的类型,由其中的图标,或者说XML中的类型声明而定义。抛出事件与捕获事件显示上的区别,是其内部的图标填充为色。

定时器事件

定时器事件是一种在特定时间触发的事件。在Activiti中,可以通过定时器事件来实现定时执行某个任务或者触发某个流程实例,具体包括定时启动事件,定时捕获中间件事件,定时边界事件,在很多的业务场景中。

定时器开始事件

定时器启动事件(timer start event)在指定时间创建流程实例。在流程只需要启动一次,或者流程需要在特定的时间间隔重复启动时,都可以使用。在使用时我们需要注意如下几个点:

  1. 子流程不能有定时器启动事件;
  2. 定时器启动事件,在流程部署的同时就开始计时。不需要调用startProcessInstanceById就会在时间启动。调用startProcessInstanceById时会在定时启动之外额外启动一个流程。
  3. 当部署带有定时器启动事件的流程的更新版本时,上一版本的定时器作业会被移除,这是因为通常并不希望旧版本的流程仍然自动启动新的流程实例。
  4. asyncExecutorActivate:需要设置为 true,否则定时器不会生效,因为这块需要开启异步任务。

定时器启动事件,用其中有一个钟表图标的 圆圈来表示,我们通过具体案例来介绍
image.png
我们的定时任务信息表:act_ru_timer_job
image.png
时间到达后会触发定时开始事件。
image.png
定时器开始事件除了上面的指定固定时间启动外,我们还可以通过循环和持续时间来处理;

  • timeDate:指定一个具体的日期和时间,例如2022-01-01T80:80:00
  • timeCycle:指定一个重复周期,例如R1/PT1H表示每隔1小时触发一次。
  • timeDuration:指定一个持续时间,例如PT2H30M表示持续2小时30分钟。

然后我们新增一个重复周期的案例,这里我们通过自动任务来演示案例。
image.png
服务任务:不需要我们手动去审批。

public class MyFirstDelegate implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {
        System.out.println("服务任务执行了...." + LocalDateTime.now().toString());
    }
}

定时器中间事件

在开始事件和结束事件之间发生的事件称为 中间事件,定时器中间捕获事件指在流程中将一个定时器作为独立的节点来运行,是一个捕获事件。当流程流转到定时器中间捕获事件时,会启动一个定时路,并一直等待触发,只有到达指定时间定时器才被触发。
image.png
申请出库审批通过后,等待一分钟后定时器触发,然后进入出库申请审批。
image.png

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取 processEngine 对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取 RepositoryService 对象
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.使用 RepositoryService 对象完成流程部署
    Deployment deploy = repositoryService.createDeployment().addClasspathResource("flow/event-timer-mld.bpmn20.xml")
            .name("定时器中间事件").deploy();
    System.out.println("流程部署ID:" + deploy.getId());
    System.out.println("流程部署名称:" + deploy.getName());
}

/**
 * 发起一个流程
 */
@Test
public void test5() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程,需要通过 runtimeService 来实现
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 通过流程定义 ID 来启动流程,返回的是流程实例对象
    ProcessInstance processInstance = runtimeService.startProcessInstanceById("event-timer-mld:1:3");
    System.out.println("流程定义的ID = " + processInstance.getProcessDefinitionId());
    System.out.println("流程实例的ID = " + processInstance.getId());
    try {
        Thread.sleep(Integer.MAX_VALUE);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

/**
 * 任务审批
 */
@Test
public void test2() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    taskService.complete("6");
    try {
        Thread.sleep(Integer.MAX_VALUE);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

定时器边界事件

当某个用户任务或者子流程在规定的时间后还没有执行。那么我们就可以通过定时器边界事件来触发执行特定的处理流程。
注意在定时器边界事件配置了CancelActivity属性,用于说明该事件是否为中断事件。CancelActivity属性值默认为true,表示它是边界中断事件,当该边界事件触发时,它所依附的活动实例被终止,原有的执行流会被中断,流程将沿边界事件的外出顺序流继续流转。如果将其设置为false,表示它是边界非中断事件,当边界事件触发时,则原来的执行流仍然存在,所依附的活动实例继续执行,同时也执行边界事件的外出顺序流。
image.png
流程步骤说明
部署后启动流程。进入到库存审批这个节点,同时在act_ru_timer_job表中可以看到这个边界事件的定义。
等待一分钟定时器边界事件触发,我们可以在控制台中看到JavaDelegate任务的执行。
因为我们的边界事件定义的是非中断,所以用户的任务还在,只是在边界任务中触发了服务任务,来通知用户审批处理操作。
然后库存审批通过后,进入到财务审批节点。同时我们开启了中间边界事件。act_ru_timer_job表中会生成记录。
同时act_ru_task中审批的是财务审批
等待一分钟后,因为边界事件设置的是中断类型,所以触发后财务审批终止。由财务实习审批来处理。

消息事件

消息事件(message event),是指引用具名消息的事件。消息具有名字与载荷。与信号不同,消息事件只有
个接收者。

消息开始事件

消息开始事件 ,也就是我们通过接收到某些消息后来启动流程实例,比如接收到了一封邮件,一条短信等。
image.png
做消息的定义,如图
image.png
注意:这里的id和Name名称需要一致
在开始事件中我们需要绑定上面定义的消息。
image.png
部署流程后,消息启动事件会在act_ru_event_subscr表中记录。

@Test
public void test1() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RepositoryService repositoryService = processEngine.getRepositoryService();
    Deployment deploy = repositoryService.createDeployment()
            .addClasspathResource("flow/event-message-start.bpmn20.xml")
            .name("消息开始事件").deploy();
    System.out.println("流程部署ID:" + deploy.getId());
    System.out.println("流程部署名称:" + deploy.getName());
}

image.png
发送消息后可以看到开始事件被触发。

/**
 * 发送消息
 */
@Test
public void test5() throws InterruptedException {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 发送消息应该是具体的消息名称而不是的id
    runtimeService.startProcessInstanceByMessage("msg01");
    Thread.sleep(Integer.MAX_VALUE);
}

image.png

消息中间事件

消息中间事件就是在流程运作中需要消息来触发的场景。演示案例:用户任务1处理完成后,需要接收特定的消息之后才能进入到用户任务2
image.png
这里同样也要做消息的绑定。
发送消息触发消息中间事件,然后进入到用户消息2 进行审批,说明消息触发了。

/**
 * 触发中间消息
 */
@Test
public void test5() throws InterruptedException {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 根据流程实例id查询 执行实例的编号
    Execution execution = runtimeService.createExecutionQuery()
            .processInstanceId("2501").onlyChildExecutions().singleResult();
    String executionId = execution.getId();
    runtimeService.messageEventReceived("msg02", executionId);
    Thread.sleep(Integer.MAX_VALUE);
}

消息边界事件

消息边界事件同样的针对是用户节点在消息触发前如果还没有审批,就会触发消息事件的处现逻辑。
image.png
流程步骤:
部署流程、启动流程后,进入到用户任务1,在act_ru_event_subscr表中就可以看到对应的消息事件,这时我们就可以发送消息。

@Test
public void test5() throws InterruptedException {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 根据流程实例id查询 执行实例的编号
    Execution execution = runtimeService.createExecutionQuery()
            .processInstanceId("2501").onlyChildExecutions().singleResult();
    String executionId = execution.getId();
    runtimeService.messageEventReceived("msg03", executionId);
    Thread.sleep(Integer.MAX_VALUE);
}

然后可以在控制台中看到JavaDelegate任务的执行。
这里的边界事件为非中断的,所以还是需要用户任务1来进行审批推进的。审批通过后会绑定msg04消息。
当我们触发第二个消息边界事件,那么任务会进入到用户任务3中,同时用户任务2会被中断。

错误事件

错误事件可以用做一个流程的开始事件或者作为一个任务或者子流程的边界事件,错误事件没有提供作用中间事件的功能,这一点和前面介绍的定时器事件和消息事件还有区别的。在错误事件中提供了错误结束事件

错误开始事件

错误开始事件(error start event)可以触发一个事件子流程,且总是在另外一个流程异常结束时触发。BPMN2.0规定了错误开始事件只能在事件子流程中被触发,不能在其他流程中被触发,包括顶级流程、嵌套子流程和调用活动。错误启动事件不能用于启动流程实例。错误启动流程总是中断
image.png
对应的服务任务1中我们需要显示抛出异常信息。

public class MyErrorDelegate1 implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {
        System.out.println("错误开始事件1...." + LocalDateTime.now().toString());
        // 显示的抛出error1的异常信息,触发错误开始事件。
        throw new BpmnError("error1");
    }
}

服务任务2中我们打印日志。

public class MyErrorDelegate2 implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {
        System.out.println("错误开始事件2...." + LocalDateTime.now().toString());
    }
}

那么部署完流程后。然后发起一个新的流程就会走事件子流程中的逻辑了。错误开始事件可以在如下的场景中使用:

  1. 输入验证失败:当用户提交工作流启动请求时,需要对输入的数据进行验证。如果数据不符合预期的格式或规则,可以使用错误开始事件来捕获并处理验证失败的情况。
  2. 权限验证失败:在某些情况下,只有特定的用户或用户组才能启动某个工作流,当非授权用户尝试启动工作流时,可以使用错误开始事件来捕获并处理权限验证失败的情况。
  3. 前置条件不满足:在工作流启动之前,可能需要满足一些前置条件,例如某个数据已经存在或某个服务可用。如果前置条件不满足,可以便用错误开始事件来捕获并处理这种情况。
  4. 数据源异常:在工作流启动过程中,可能需要从外部数据源获取数据。如果数据源出现异常导致无法获取数据,可以便用错误开始事件来捕获并处理数据源异常的情况。

总的来说,错误开始事件可以用于捕获工作流启动时可能出现的各种错误情况,并根据具体的业务需求进行相应的处理。

错误边界事件

当某个任务发生错误时,可以通过错误边界事件来捕获并处理该错误,以保证流程的正常执行。
错误边界事件 可以在流程中的任务节点上定义,并与该任务节点关联。当任务节点执行过程中发生错误时,错误边界事件会被触发,并执行相应的处理逻辑,如发送错误通知、重新分配任务、跳转到其他节点等。
错误边界事件可以捕获多种类型的错误,如异常、超时、网络故障等。通过使用错误边界事件,可以增加流程的容错性,并提供更好的错误处理机制,保证流程的稳定性和可靠性。
需要注意的是,错误边界事件只能与任务节点关联,而不能与其他类型的节点(如网关、开始节点、结束节点)关联。此外,在设计流程时,需要准确定义错误边界事件的触发条件和处理逻辑,以确保错误能够被正确捕获和处理。
image.png
错误边界事件分别绑定error2error3
当我们启动流程后控制台打印错误边界事件1错误边界事件4日志。

错误结束事件

在Activiti中,错误结束事件(Error End Event)是一个用于标记流程实例在特定错误条件下结束的节点。当流程实例执行到错误结束事件时,流程实例将立即终止执行,并且流程实例的状态将被标记为“错误结束”。
错误结束事件可以与错误边界事件(Error Boundary Event)结合使用,用于在流程中捕获和处理特定的错谩,当错误边界事件触发时,流程会跳转到与错误边界事件关联的错误结束事件,从而便流程实例结束。
错误结束事件可以配置一个错误代码,用于标识特定的错误类型。在流程定义中,可以定义多个错误结束事件,每个事件可以有不同的错误代码。当流程实例执行到错误结束事件时,可以根据错误代码进行相应的处理,例如记录日志、发送通知等。
错误结束事件可以用于处理各种错误情况,使如系统异常、业务规则异常等。通过使用错误结束事件,可以使流程能够在错误发生时进行合理的处理,提高系统的可靠性和稳定性。
总之,错误结束事件是Activit中的一个节点,用于标记流程实例在特定错误条件下结束,它可以与错误边界事件结合使用,用于捕获和处理特定的错误。通过使用错误结束事件,可以实现对流程中各种错误情况的处理和管理。
image.png
当子流程中的支付失败的情况下会触发错误结束事件。该事件会被错误边界事件捕获。错误边界事件捕获后会重新发起支付的流程。

信号事件

信号事件是Activiti中的一种事件类型,用于在流程执行过程中通知其他流程实例或任务实例。
信号事件是一种全局事件 ,可以在任何流程实例或任务实例中触发和捕获,当一个流程实例或任务实例触发了一个信号事件,其他等待捕获相同信号的流程实例或任务实例将被唤醒并继续执行。
信号事件可以用于以下场景:

  1. 并行流程实例之间的协作:当一个流程实例需要与其他并行流程实例进行协作时,可以触发一个信号事件来通知其他流程实例执行相应的任务。
  2. 动态流程控制:当流程的执行需要根据外部条件进行动态调整时,可以使用信号事件来触发相应的流程变化
  3. 异第处理:当发生异常情况时,可以触发一个信号事件来通知其他流程实例或任务实例进行异常处理。

使用信号事件需要以下几个步骤:

  1. 定义信号事件:在流程定义中定义一个信号事件,指定信号的名称和他属性。
  2. 触发信号事件:在流程实例或任务实例中触发一个信号事件。
  3. 捕获信号事件:在其他流程实例或任务实例中插获相同名称的信号事件。
  4. 响应信号事件:在捕获的信号事件中定义相应的处理逻辑,例如执行任务或流程变化。

信号事件我们可以分为开始事件 、中间捕获事件、中间抛出事件、边界事件。

信号开始事件

  • 启动事件是一个特殊的信号事件,用于在流程启动时触发。
  • 当流程启动时,如果存在一个启动事件,并且该事件匹配到了被触发的信号,流将会被启动
  • 启动事件可以用于实现流程启动前的条件判断,例如当某个条件满足时,才允许启动流程。

image.png
在定义信号的时候有一个Scope 属性可以设置为Global或processInstance

  • Global:全局范国的信号定义,表示可以在任何流程实例中触发和捕获信号,当一个信号事件被触发时,所有等待捕获该信号的节点都会被唤醒。
  • processlnsance:流程实例范国的信号定义,表示只能在当前流程实例中触发和捕获信号。当一个信号事件被触发时,只有等待在当前流程实例中捕获该信号的节点会被唤醒。

而当前的启动事件是在流程实例启动时触发的事件,用于执行一些初始化操作。启动事件可以在流程定义的开始节点上定义,并在开始节点上设置事件类型为start,启动事件只有一个全局范围的信号定义,即scope属性只能设置为Global,当一个启动事件被触发时,所有等待捕获该信号的节点都会被唤。
image.png
部署流程,然后发起流程。

@Test
public void test2() throws InterruptedException {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    runtimeService.signalEventReceived("signal01");
    Thread.sleep(Integer.MAX_VALUE);
}

通过信号启动事件 发起一个流程:

  • 通过 runtimeService 中提供的api来发送信号
  • 通过其他流程实例中的信号中间抛出事件来触发
  • 作为普通的流程实例来启动
@Test
public void test3() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    ProcessInstance processInstance = runtimeService.startProcessInstanceById("event-signal-start:1:3");
    System.out.println("流程定义的ID = " + processInstance.getProcessDefinitionId());
    System.out.println("流程实例的ID = " + processInstance.getId());
}

信号中间事件

信号中间事件 分为捕获事件抛出事件,当流程流转到信号中间捕获事件时会中断并等待触发,直到接收到相应的信号后沿信号中间插获事件的外出顺序流继续流转。信号事件默认是全局的,与其他事件(如错误事件)不同,其信号不会在捕获之后被消费。如果存在多个引用了相同信号的事件被激活,即使它们不在同一个流程实例当接收到该信号时,这些事件也会被一并触发。
image.png
消息定义我们用的scope是 processinstance。也就是只在当前流程实例生效,部署运行后可以看具体的效果
image.png
注意:如果我们scope用的是Global,那么信号开始事件的案例也会被执行。
image.png
流程步骤说明:

  1. 部署后再启动流程,在act_ru_event_subscr表中记录信号相关的信息,同时记录了作用域信息。
  2. 审批用户后,节点进入抛出信号事件的节点。
  3. 审批任务完成后,可以看到信号中间事件1信号中间事件2执行了。
  4. 同时因为socpe是 processinstance 在act_ru_event_subscr中记录的信号事件也被消费了。如果是global则该信号还是继续监听。

信号边界事件

信号边界事件 会捕获与其信号事件定义引用的信号具有相同信号名称的信号,当流程流转到信号边界事件依附的流程活动(如用户任务、子流程等)时,工作流引聚会创建一个捕获事件,在其依附的流理活动的生命周期内等待一个抛出信号。该信导可以由信号中间抛出事件抛出或由API触发。信号边界事件被触发后流程会沿其外出顺序流维续流转,如果该边界事件设置为中断,则依附的流活动将被止。
image.png
流程步骤说明:

  1. 部署流程、启动流程,在act_ru_event_subscr表中记录信号事件信息。
  2. 流程会进入用户任务1节点,可以正常审批,还可以发布相关的信号事件。
  3. 通过信号启动事件发起流程后,因为是非中断的,所以控制台打印信号边界事件1触发了,同时用户任务1还在。
  4. 审批通过用户任务1,会进入到用户任务2,审批通过用户任务2会触发信号抛出事件,然后被信号边界事件捕获,控制台打印信号边界事件3
  5. 后面的自动任务2用户任务3都不会执行了。

其他事件

终止结束事件

终止结束事件也称为中断结束事件,主要是对流程进行终止的事件,可以在一个复杂的流程中,如果某方想要提前中断这个流程,可以采用这个事件来处理,可以在并行处理任务中。如果你是在流程实例层处理,整个流程都会被中断,如果是在子流程中使用,那么当前作用和作用域内的所有的内部流程都会被终止。

  • 案例一:终止结束事件是在主流程中触发的场景

image.png
注意:设置终止结束事件。里面有一个terminate all默认为false。含义是当终止结束事件 在多实例或者嵌套的子流程中。那么不会终止整个流程。如果设置为true,那么不管是否嵌套都会终止整个的流程实例。
image.png
通过案例的演示,我们发下在用户任务1用户任何2没有审批的情况下,当用户任务3审批通过后同时 flag 设置为false 的情况下触发了终止结束事件那么整个流程实例都被终止了。
image.png
通过查看act_hi_taskinst表数据,可知e3为正常审批,e1、e2是被终止结束流程处理的。

  • 案例二:终止结束事件是在子流程中触发的场景

image.png
通过案例的演示,我们发下在用户任务1用户任何2没有审批的情况下,当用户任务3审批通过进入子流程中,当用户任务4审批通过后同时 flag 设置为false 的情况下触发了终止结束事件那么用户任务5流程实例会被终止了,其他两个不会被终止。
image.png
本案例我们将terminate all默认为false。如果想全部终止改为true即可。

取消结束事件

取消结束事件(cancelend event)只能与BPMN事务子流程(BPMN transaction subprocess)一起便用,当到达取消结束事件时,会抛出取消事件,且必须由取消边界事件(cancel boundany event)捕获,取消边界事件将取消事务,并触发补偿(compensation)。
image.png
注意:事件子流程:Is a transaction sub process设置为true
补偿自动任务:Is for compensation设置为true

这里触发了取消结束事件后,进入取消边界事件,同时会进入补偿自动任务方法执行。

补偿事件

在Activiti中,补偿事件(Compensation vent)是一种用于处现流程中发生异常或错误的特殊事件。当流程中的某个任务或活动发生错误或无法继续执行时,补偿事件可以被触发来回滚或修复之前已经完成的任务或活动。
补偿事件通常与错误边界事件(Error Boundary Event)结合使用,错误边界事件是在流程中的任务或活动周围设置的捕获异常的事件。当任务或活动发生异常时,错误边界事件将被触发,进而触发相应的补偿事件。
补偿事件可以执行一系列的补偿操作,包括撤销之前已经亮成的任务、还原数据、发送通知等。补偿操作的具体步骤和逻辑可以在流程定义中定义,并且可以使用Java代码或脚本来实现。
补偿事件的触发和执行是自动完成的,无需人工干预,一旦补偿事件被触发,Activiti引擎会自动査找相应的补偿事件,并按照定义的补偿操作进行执行。
通过使用补偿事件,可以有效地处理流程中的异常情况,提高流程的鲁棒性和容错性,补偿事件可以帮助流程在发生错误时自动进行修复,确保流程能够正常完成。


如果有收获! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客
  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值