Spring Boot 整合 Activiti进阶

前言

在上一篇文章中说了Spring Boot + Activiti 7的基本使用,本文章将更深入的带你了解Activiti 7

个人任务

在上一篇文章其实有介绍到关于任务负责人的这个概念(Assignee)。指的时当任务执行到某个节点时,若该节点的负责人为:张三,那么就可以由Assignee查询到张三的代办任务,真实场景,就可以根据负责人来查询并执行任务。

固定分配负责人

固定分配负责人是指:在绘制流程图的时候为每个节点设置固定的任务负责人,如下图:

使用UEL表达式

Activiti 使用 UEL 表达式,UELjava EE6 规范的一部分,UELUnified Expression Language)即统一表达式语言,activiti 支持两个 UEL 表达式:UEL-valueUEL-method

UEL-value

设置Assignee的值为:${userId},这样在任务执行时,只需要把Assignee的值设置到流程变量中,注意:key需要和你设置的表达式中的值一致,比如此处的userId哦。

UEL-value还支持对象导航,比如你可以设置为:${user.userId},这样在设置流程变量的时候,只需要把查询到的user存入流程变量,当执行到该节点时,就可以自动从流程变量的user对象中取到userId的值。

UEL-method

设置Assignee的值为:${userService.getUserId()},其中userServiceSpring管理的Bean,这样在执行该节点时,就会调用userServicegetUserId方法。

UEL-value + UEL-method结合使用

设置Assignee的值为:${userService.getUserId(username)},其中userServiceSpring管理的Beanusername为流程变量,执行到该节点的时候,会自动将username作为参数传入getUserId方法并获取调用结果的值。

注意:使用表达式时,必须保证流程变量或方法存在,否则会导致流程出现异常。

流程变量

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

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

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

使用流程变量控制流程

绘制流程图

在部门经理审批的连线上设置UEL表达式,控制流程的走向。

这里为了我后面测试方便,我分别在填写请假单部门经理审批,总经理审批添加了任务负责,这样在就可以使用任务负责人获取对应节点的任务了,对应如下:

  • 填写请假单:员工
  • 部门经理审批:部门经理
  • 总经理审批:总经理

代码

package com.imxushuai;

import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.*;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ActivitiDemo2Test {

    @Autowired
    private ProcessEngine processEngine;

    private RuntimeService runtimeService;
    private TaskService taskService;
    private RepositoryService repositoryService;
    private HistoryService historyService;

    @Before
    public void init() {
        runtimeService = processEngine.getRuntimeService();
        taskService = processEngine.getTaskService();
        repositoryService = processEngine.getRepositoryService();
        historyService = processEngine.getHistoryService();
    }


    /**
     * 请假单流程执行,我事先给每个节点添加了任务负责人
     * 填写请假单:员工
     * 部门经理审批:部门经理
     * 总经理审批:总经理
     */
    @Test
    public void start() {
        // 开启流程
        ProcessInstance qingjia = runtimeService.startProcessInstanceByKey("qingjia");
        log.info("请假流程已开启:流程ID:[{}]", qingjia.getId());

        // 填写请假单(由于我这里确认只有一个任务,我直接使用singleResult,实际应为list)
        Task task = taskService.createTaskQuery().processDefinitionKey("qingjia").taskAssignee("员工").singleResult();
        if (task != null) {
            log.info("请填写请假单:任务ID:[{}]", task.getId());
            // 构造请假单参数
            Map<String, Object> params1 = new HashMap<>();
            // 请假人,请假天数,请假的开始日期,请假原因
            params1.put("name", "xushuai");
            // 修改num参数的值是否小于3 可以控制流程的走向
            params1.put("num", 2);
            params1.put("date", new Date());
            params1.put("remark", "回家有事");

            // 提交请假单
            taskService.complete(task.getId(), params1);
            log.info("请假单填写完毕,请假单内容:{}", params1.toString());
        }

        // 查询部门经理任务
        task = taskService.createTaskQuery().processDefinitionKey("qingjia").taskAssignee("部门经理").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("请部门经理审批请假单,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("部门经理审核通过");
        }

        // 查询总经理任务
        task = taskService.createTaskQuery().processDefinitionKey("qingjia").taskAssignee("总经理").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("请总经理审核请假单,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("总经理审核通过");
        } else {
            log.info("总经理暂无任务");
        }

    }

}
  • 分为四部分:创建流程实例,填写请假单,部门经理审核,总经理审核

测试

  1. 填写请假单,num变量的值为:2

请假天数 =< 3 天,所以总经理不需要审核

  1. 填写请假单,num变量的值为:5

    请假天数 > 3天,需要总经理审核。

组任务

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

设置任务候选人

Candidate Users中设置候选人,如有多个,使用英文逗号隔开。

任务办理

  1. 查询候选人的任务

    Task task = taskService.createTaskQuery().processDefinitionKey("qingjia").taskCandidateUser("人事部经理").singleResult();
    
  2. 将该任务转成个人任务

    // 将该任务分配给 “人事部经理” 处理
    taskService.claim(taskId, "人事部经理");
    

    注意:该方法调用时需要自行判断权限,一旦调用该方法,任务就会被分配给指定的负责人。

  3. 后面的处理就和普通任务的处理是一致的了。

网关

排他网关

排他网关(也叫异或(XOR)网关,或叫基于数据的排他网关),用来在流程中实现决策。当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true 则执行该分支,注意,排他网关只会选择一个为true 的分支执行。(即使有两个分支条件都为true,排他网关也会只选择一条分支去执行)

不使用排他网关也可以实现流程的控制。

在连线上使用流程变量判断控制。

缺点:如果连线上的条件都不满足,流程将直接结束,在下面的效果就是,直接填写完请假单,就请假成功了。这显然不符合实际的情况。

使用排他网关流程图

使用排他网关,必须有一个条件为true,否则系统会抛出异常,我们可以通过捕获异常获知晓情况。

建议使用排他网关。

测试代码

	/**
     * 排他网关请假流程
     * 填写请假单:员工
     * 部门经理审批:部门经理
     * 总经理审批:总经理
     */
    @Test
    public void testExclusiveGateway() {
        // 开启流程
        ProcessInstance qingjia = runtimeService.startProcessInstanceByKey("exclusiveGateway_qingjia");
        log.info("请假流程已开启:流程ID:[{}]", qingjia.getId());

        // 填写请假单(由于我这里确认只有一个任务,我直接使用singleResult,实际应为list)
        Task task = taskService.createTaskQuery().processDefinitionKey("exclusiveGateway_qingjia").taskAssignee("员工").singleResult();
        if (task != null) {
            log.info("请填写请假单:任务ID:[{}]", task.getId());
            // 构造请假单参数
            Map<String, Object> params1 = new HashMap<>();
            // 请假人,请假天数,请假的开始日期,请假原因
            params1.put("name", "xushuai");
            // 修改num参数的值是否小于3 可以控制流程的走向
            params1.put("num", 2);
//            params1.put("num", 5);
            params1.put("date", new Date());
            params1.put("remark", "回家吃饭");

            // 提交请假单
            taskService.complete(task.getId(), params1);
            log.info("请假单填写完毕,请假单内容:{}", params1.toString());
        }

        // 查询部门经理任务
        task = taskService.createTaskQuery().processDefinitionKey("exclusiveGateway_qingjia").taskAssignee("部门经理").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("请部门经理审批请假单,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("部门经理审核通过");
        }

        // 查询总经理任务
        task = taskService.createTaskQuery().processDefinitionKey("exclusiveGateway_qingjia").taskAssignee("总经理").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("请总经理审核请假单,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("总经理审核通过");
        } else {
            log.info("总经理暂无任务");
        }

    }

测试结果

并行网关

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

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

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

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

并行网关的分支必须全部执行完毕才会走下一流程节点,如上图的“人事存档”和“行政考勤”,必须两个都完成,流程才会结束。

测试代码

	/**
     * 并行网关请假流程
     * 填写请假单:员工
     * 部门经理审批:部门经理
     * 总经理审批:总经理
     * 人事审核:人事
     * 人事存档:人事
     * 行政考勤:行政
     */
    @Test
    public void testParallelGateway() {
        // 开启流程
        ProcessInstance qingjia = runtimeService.startProcessInstanceByKey("parallelGateway_qingjia");
        log.info("请假流程已开启:流程ID:[{}]", qingjia.getId());

        // 填写请假单(由于我这里确认只有一个任务,我直接使用singleResult,实际应为list)
        Task task = taskService.createTaskQuery().processDefinitionKey("parallelGateway_qingjia").taskAssignee("员工").singleResult();
        if (task != null) {
            log.info("请填写请假单:任务ID:[{}]", task.getId());
            // 构造请假单参数
            Map<String, Object> params1 = new HashMap<>();
            // 请假人,请假天数,请假的开始日期,请假原因
            params1.put("name", "xushuai");
            // 修改num参数的值是否小于3 可以控制流程的走向
//            params1.put("num", 2);
            params1.put("num", 5);
            params1.put("date", new Date());
            params1.put("remark", "回家吃饭");

            // 提交请假单
            taskService.complete(task.getId(), params1);
            log.info("请假单填写完毕,请假单内容:{}", params1.toString());
        }

        // 查询部门经理任务
        task = taskService.createTaskQuery().processDefinitionKey("parallelGateway_qingjia").taskAssignee("部门经理").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("请部门经理审批请假单,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("部门经理审核通过");
        }

        // 查询总经理任务
        task = taskService.createTaskQuery().processDefinitionKey("parallelGateway_qingjia").taskAssignee("总经理").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("请总经理审核请假单,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("总经理审核通过");
        } else {
            log.info("总经理暂无任务");
        }

        // 查询人事任务
        task = taskService.createTaskQuery().processDefinitionKey("parallelGateway_qingjia").taskAssignee("人事").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("人事审核,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("人事审核通过");
        }

        // 查询人事任务
        task = taskService.createTaskQuery().processDefinitionKey("parallelGateway_qingjia").taskAssignee("人事").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("人事存档,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("人事存档完毕");
        }

        // 查询行政任务
        task = taskService.createTaskQuery().processDefinitionKey("parallelGateway_qingjia").taskAssignee("行政").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("行政考勤,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("行政考勤记录完毕");
        }


    }

测试结果

请假天数为:5,总经理需要审批。人事存档和行政考勤也正确获取到任务。

包含网关

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

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

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

若不设置条件,默认为true

测试代码

    /**
     * 包含网关请假流程
     * 填写请假单:员工
     * 部门经理审批:部门经理
     * 总经理审批:总经理
     * 人事存档:人事
     * 行政考勤:行政
     */
    @Test
    public void testInclusiveGateway() {
        // 开启流程
        ProcessInstance qingjia = runtimeService.startProcessInstanceByKey("inclusiveGateway_qingjia");
        log.info("请假流程已开启:流程ID:[{}]", qingjia.getId());

        // 填写请假单(由于我这里确认只有一个任务,我直接使用singleResult,实际应为list)
        Task task = taskService.createTaskQuery().processDefinitionKey("inclusiveGateway_qingjia").taskAssignee("员工").singleResult();
        if (task != null) {
            log.info("请填写请假单:任务ID:[{}]", task.getId());
            // 构造请假单参数
            Map<String, Object> params1 = new HashMap<>();
            // 请假人,请假天数,请假的开始日期,请假原因
            params1.put("name", "xushuai");
            // 修改num参数的值是否小于3 可以控制流程的走向
            params1.put("num", 2);
//            params1.put("num", 5);
            params1.put("date", new Date());
            params1.put("remark", "回家吃饭");

            // 提交请假单
            taskService.complete(task.getId(), params1);
            log.info("请假单填写完毕,请假单内容:{}", params1.toString());
        }

        // 查询部门经理任务
        task = taskService.createTaskQuery().processDefinitionKey("inclusiveGateway_qingjia").taskAssignee("部门经理").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("请部门经理审批请假单,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("部门经理审核通过");
        }

        // 查询总经理任务
        task = taskService.createTaskQuery().processDefinitionKey("inclusiveGateway_qingjia").taskAssignee("总经理").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("请总经理审核请假单,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("总经理审核通过");
        } else {
            log.info("总经理暂无任务");
        }

        // 查询人事任务
        task = taskService.createTaskQuery().processDefinitionKey("inclusiveGateway_qingjia").taskAssignee("人事").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("人事审核,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("人事审核通过");
        }


        // 查询行政任务
        task = taskService.createTaskQuery().processDefinitionKey("inclusiveGateway_qingjia").taskAssignee("行政").singleResult();
        if (task != null) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            log.info("行政考勤,请假人:[{}], 请假天数:[{}]天", variables.get("name"), variables.get("num"));

            taskService.complete(task.getId());
            log.info("行政考勤记录完毕");
        }

    }

测试结果

因为请假天数为:2,所以不需要总经理审批。

代码获取

Github:

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值