activiti学习笔记

activiti 工作流

学习地址

工作流涉及到的表

表名 中文含义
act_re_deployment 部署信息表
act_re_model 流程设计模型部署表
act_re_procdef 流程定义数据表
act_ru_excution 运行时流程执行实例表
act_ru_identitylink 运行时流程人员表,主要存储任务节点与参与者相关信息
act_tu_task 运行时任务节点表
act_ru_variable 运行时流程标量数据表
act_hi_actinst 历史节点表
act_hi_attachment 历史附件表
act_hi_comment 历史意见表
act_hi_identitylink 历史流程人员表
act_hi_detail 历史详情表,提供历史变量的查询
act_hi_procinst 历史流程实例表
act_hi_taskinst 历史任务实例表
act_hi_varinst 历史变量表
act_id_group 用户组信息表
act_id_info 用户扩展信息表
act_id_membership 用户与用户组对应信息表
act_id_user 用户信息表
act_ge_bytearray 二进制数据表
act_ge_property 属性数据表,存储整个流程引擎级别的数据,初始化表结构时,会默认插入三条记录

常用Service

Service 说明
RepositoryService 资源管理类
RuntimeService 流程运行管理类
TaskService 任务管理类
HistoryService 历史管理类
ManagerService 引擎管理类

RepositoryService

是activiti的资源管理类,提供了管理和控制流程发布包和流程定义的操作。

使用工作流建模工具设计的业务流程图需要使用此service将流程定义文件的内容部署到计算机。
除了部署流程定义以外还可以:
查询引擎中的发布包和流程定义。
暂停或激活发布包,对应全部和特定流程定义。 暂停意味着它们不能再执行任何操作了,激活是对应的反向操作。
获得多种资源,像是包含在发布包里的文件, 或引擎自动生成的流程图。
获得流程定义的pojo版本, 可以用来通过java解析流程,而不必通过xml

RuntimeService

它是activiti的流程运行管理类。可以从这个服务类中获取很多关于流程执行相关的信息

TaskService

是activiti的任务管理类。可以从这个类中获取任务的信息

HistoryService

是activiti的历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据(根据配置),比如流程实例启动时间,任务的参与者, 完成任务的时间,每个流程实例的执行路径,等等。

这个服务主要通过查询功能来获得这些数据。

ManagementService

是activiti的引擎管理类,提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护。

bpmn组件

组件 名称
Connection 连接
Event 事件
Task 任务
Gateway 网关
Container 容器
Boundary event 边界事件
Intermediate event 中间事件

画流程图

bpmn1.png
bpmn2.png

myholiday为流程的key

bpmn3.png
bpmn4.png

assignee为处理人

普通使用

pom文件

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <activiti-version>5.18.0</activiti-version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-engine</artifactId>
        <version>${activiti-version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-spring</artifactId>
        <version>${activiti-version}</version>
    </dependency>
    <dependency>
        <groupId>org.codehaus.groovy</groupId>
        <artifactId>groovy-all</artifactId>
        <version>2.4.3</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.19</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.6</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-jdk14</artifactId>
        <version>1.7.6</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.6</version>
    </dependency>
</dependencies>

activiti.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/activiti?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="maxIdle" value="1"/>
        <property name="maxActive" value="3"/>
    </bean>
    <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <property name="dataSource" ref="dataSource"/>
        <property name="databaseSchemaUpdate" value="true"/>
    </bean>
</beans>

databaseSchemaUpdate

作用
false(默认) 检查数据库表的版本和依赖库的版本, 如果版本不匹配就抛出异常
true 构建流程引擎时,执行检查,如果需要就执行更新。 如果表不存在,就创建
create-drop 构建流程引擎时创建数据库表, 关闭流程引擎时删除这些表
drop-create 先删除表再创建表
create 构建流程引擎时创建数据库表, 关闭流程引擎时不删除这些表

测试代码

public class TestActiviti {

     private ProcessEngine getProcessEngine() {
        ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
        return processEngineConfiguration.buildProcessEngine();
    }
    
    @Test
    public void testProcessEngine() {
        ProcessEngine processEngine = getProcessEngine();
    }

    /**
     * 部署流程
     */
    @Test
    public void testDeployProcess() {
        ProcessEngine processEngine = getProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        Deployment deployment = repositoryService.createDeployment().addClasspathResource("myholiday.bpmn").addClasspathResource("myholiday" +
                ".png").name("请假流程").deploy();
        System.out.println("流程部署ID:" + deployment.getId());
        System.out.println("流程名称:" + deployment.getName());
    }

    /**
     * 启动一个流程实例
     */
    @Test
    public void testStartProcessInstance() {
        ProcessEngine processEngine = getProcessEngine();
        RuntimeService runtimeService = processEngine.getRuntimeService();
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myholiday");
        System.out.println("流程定义ID:" + processInstance.getProcessDefinitionId());
        System.out.println("流程实例ID:" + processInstance.getId());
        System.out.println("当前活动ID:" + processInstance.getActivityId());
    }

    /**
     * 查询个人待执行任务
     */
    @Test
    public void findPersonalTaskList() {
        String assignee = "zhangsan";
        ProcessEngine processEngine = getProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        List<Task> taskList = taskService.createTaskQuery().processDefinitionKey("myholiday").taskAssignee(assignee).list();
        for (Task task : taskList) {
            System.out.println("流程实例ID:" + task.getProcessInstanceId());
            System.out.println("任务ID:" + task.getId());
            System.out.println("任务负责人:" + task.getAssignee());
            System.out.println("任务名称:" + task.getName());
        }
    }

    /**
     * 完成任务
     */
    @Test
    public void completeTask() {
        String taskId = "12504";
        TaskService taskService = getProcessEngine().getTaskService();
        taskService.complete(taskId);
        System.out.println("任务完成ID = " + taskId);
    }

    /**
     * 流程定义查询
     */
    @Test
    public void queryProcessDefinition() {
        String processDefinitionKey = "myholiday";
        RepositoryService repositoryService = getProcessEngine().getRepositoryService();
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey).orderByProcessDefinitionVersion().desc().list();
        for (ProcessDefinition processDefinition : list) {
            System.out.println("------------------------");
            System.out.println("流程部署id:" + processDefinition.getDeploymentId());
            System.out.println("流程定义id:" + processDefinition.getId());
            System.out.println("流程定义名称:" + processDefinition.getName());
            System.out.println("流程定义key:" + processDefinition.getKey());
            System.out.println("流程定义版本:" + processDefinition.getVersion());
        }
    }

    /**
     * 删除流程定义
     */
    @Test
    public void deleteDeployment() {
        String deploymentId = "1";
        RepositoryService repositoryService = getProcessEngine().getRepositoryService();
        // 删除流程定义,如果该流程定义已有流程实例启动,则删除时会抛出异常
        repositoryService.deleteDeployment(deploymentId);

        // 设置true表示级联删除流程定义,即使有已经启用的流程实例也可以删除,默认为false,级联删除会将流程及相关所有记录全部删除
        repositoryService.deleteDeployment(deploymentId, true);

    }

    /**
     * 获取部署资源
     *
     * @throws IOException
     */
    @Test
    public void testGetProcessResource() throws IOException {
        ProcessEngine processEngine = getProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId("1").singleResult();
        String resourceBpmn = processDefinition.getResourceName();
        String resourcePng = processDefinition.getDiagramResourceName();
        try (InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
                resourceBpmn)) {
            FileUtils.copyInputStreamToFile(resourceAsStream, new File("H:/test.bpmn"));
        } catch (Exception e) {
            e.printStackTrace();
        }

        try (InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
                resourcePng)) {
            FileUtils.copyInputStreamToFile(resourceAsStream, new File("H:/test.png"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testHistory(){
        HistoryService historyService = getProcessEngine().getHistoryService();
        List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery().processInstanceId("12501").list();
        for (HistoricActivityInstance historicActivityInstance : list) {
            System.out.println(historicActivityInstance.getActivityId());
            System.out.println(historicActivityInstance.getActivityName());
            System.out.println(historicActivityInstance.getProcessDefinitionId());
            System.out.println(historicActivityInstance.getProcessInstanceId());
            System.out.println("==============================");
        }
    }
}

流程实例

参与者(可以是用户也可以是程序)按照流程定义内容发起一个流程,这就是一个流程实例。是动态的。

bpmn7.png

启动流程实例

@Test
public void testStartProcessInstance() {
    ProcessEngine processEngine = getProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myholiday");
    System.out.println("流程定义ID:" + processInstance.getProcessDefinitionId());
    System.out.println("流程实例ID:" + processInstance.getId());
    System.out.println("当前活动ID:" + processInstance.getActivityId());
}

businessKey 业务标识

bpmn5.png
@Test
public void testStartProcessInstance() {
    ProcessEngine processEngine = getProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myholiday","testBusinessKey");
    System.out.println("流程定义ID:" + processInstance.getProcessDefinitionId());
    System.out.println("流程实例ID:" + processInstance.getId());
    System.out.println("当前活动ID:" + processInstance.getActivityId());
}

启动流程时指定businessKey,就会在act_ru_execution(流程实例的执行表)中存储businessKey。

Businesskey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。

业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据。
比如:请假流程启动一个流程实例,就可以将请假单的id作为业务标识存储到activiti中,将来查询activiti的流程实例信息就可以获取请假单的id从而关联查询业务系统数据库得到请假单信息

所操作库表

SELECT * FROM act_ru_execution #流程实例执行表,记录当前流程实例的执行情况

流程实例执行,如果当前只有一个分支时,一个流程实例只有一条记录且执行表的主键id和流程实例id相同,如果当前有多个分支正在运行则该执行表中有多条记录,存在执行表的主键和流程实例id不相同的记录。

不论当前有几个分支总会有一条记录的执行表的主键和流程实例id相同
一个流程实例运行完成,此表中与流程实例相关的记录删除。

SELECT * FROM act_ru_task #任务执行表,记录当前执行的任务

启动流程实例,流程当前执行到第一个任务结点,此表会插入一条记录表示当前任务的执行情况,如果任务完成则记录删除

SELECT * FROM act_ru_identitylink #任务参与者,记录当前参与任务的用户或组

SELECT * FROM act_hi_procinst #流程实例历史表

流程实例启动,会在此表插入一条记录,流程实例运行完成记录也不会删除

SELECT * FROM act_hi_taskinst #任务历史表,记录所有任务

开始一个任务,不仅在act_ru_task表插入记录,也会在历史任务表插入一条记录,任务历史表的主键就是任务id,任务完成此表记录不删除。

SELECT * FROM act_hi_actinst #活动历史表,记录所有活动

活动包括任务,所以此表中不仅记录了任务,还记录了流程执行过程的其它活动,比如开始事件、结束事件

关联businessKey

在activiti实际应用时,查询流程实例列表时可能要显示出业务系统的一些相关信息,

比如:查询当前运行的请假流程列表需要将请假单名称、请假天数等信息显示出来,请假天数等信息在业务系统中存在,而并没有在activiti数据库中存在,所以是无法通过activiti的api查询到请假天数等信息。

在查询流程实例时,通过businessKey(业务标识 )关联查询业务系统的请假单表,查询出请假天数等信息。
通过下面的代码就可以获取activiti中所对应实例保存的业务Key。

而这个业务Key一般都会保存相关联的业务操作表的主键,再通过主键ID去查询业务信息,比如通过请假单的ID,去查询更多的请假信息(请假人,请假时间,请假天数,请假事由等)

String businessKey = processInstance.getBusinessKey();

流程实例的挂起与激活

某些情况可能由于流程变更需要将当前运行的流程暂停而不是直接删除,流程暂停后将不会继续执行

全部流程实例挂起

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

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

@Test
public void suspendOrActivateProcessDefinition() {
    // 流程定义id
    String processDefinitionId = "myholiday:1:4";
    RepositoryService repositoryService = getProcessEngine().getRepositoryService();
    ProcessDefinition processDefinition =
        repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
    System.out.println(processDefinition.getDeploymentId());
    boolean suspend = processDefinition.isSuspended();
    if (suspend) {
        //如果暂停则激活,这里将流程定义下的所有流程实例全部激活
        repositoryService.activateProcessDefinitionById(processDefinitionId, true, null);
        System.out.println("流程定义:" + processDefinitionId + "激活");
    } else {
        //如果激活则挂起,这里将流程定义下的所有流程实例全部挂起
        repositoryService.suspendProcessDefinitionById(processDefinitionId, true, null);
        System.out.println("流程定义:" + processDefinitionId + "挂起");
    }
}

单个流程实例挂起

操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不再继续执行,完成该流程实例的当前任务将报异常

@Test
public void suspendOrActiveProcessInstance() {
    // 流程实例id
    String processInstanceId = "2501";
    // 获取RunTimeService
    RuntimeService runtimeService = getProcessEngine().getRuntimeService();
    //根据流程实例id查询流程实例
    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
    boolean suspend = processInstance.isSuspended();
    if (suspend) { //如果暂停则激活
        runtimeService.activateProcessInstanceById(processInstanceId);
        System.out.println("流程实例:" + processInstanceId + "激活");
    } else {
        //如果激活则挂起
        runtimeService.suspendProcessInstanceById(processInstanceId);
        System.out.println("流程实例:" + processInstanceId + "挂起");
    }
}

个人任务

分配任务负责人

固定分配

bpmn8.png

在properties视图中,填写Assignee项为任务负责人。

注意事项

由于固定分配方式,任务只管一步一步执行任务,执行到每一个任务将按照bpmn的配置去分配任务负责人。

表达式分配

UEL 表达式

activiti支持UEL-value和UEL-method

  • UEL-value
bpmn9.png

assignee这个变量是activiti的一个流程变量

bpmn10.png

user也是activiti的一个流程变量,user.assignee表示通过调用user的getter方法获取值

  • UEL-method
bpmn11.png

holidayBean是Spring容器中的一的bean,表示调用该bean的getUsername()方法

  • 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}

使用流程变量分配任务

定义任务分配流程变量

bpmn9.png

设置流程变量

//启动流程实例时设计流程变量 
//定义流程变量 
Map<String, Object> variables = new HashMap<String, Object>(); 
//设置流程变量assignee 
variables.put("assignee", "张三"); 
ProcessInstance processInstance = runtimeService .startProcessInstanceByKey(processDefinitionKey, variables);
注意事项

由于使用了表达式分配,必须保证在任务执行过程表达式执行成功,比如:
某个任务使用了表达式${order.price > 100 && order.price < 250},当执行该任务时必须保证order在流程变量中存在,否则activiti异常

监听器分配

任务监听器是发生对应的任务相关事件时,执行自定义Java逻辑或表达式

任务监听事件包括:

bpmn12.png

create:任务创建后触发

assignment:任务分配后触发

complete:任务完成后触发

all:所有事件发生都触发

java逻辑 或表达式:
表达式参考上边的介绍的UEL表达式

定义任务监听类,且类必须实现org.activiti.engine.delegate.TaskListener接口

注意事项

使用监听器分配方式,按照监听事件去执行监听类的notify方法,方法如果不能正常执行也会影响任务的执行。

查询任务

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

// 查询当前个人待执行的任务
@Test
public void findPersonalTaskList1() {
    // 流程定义key
    String processDefinitionKey = "myholiday"; 
    // 任务负责人
    String assignee = "lisi";
    // 创建TaskService
    TaskService taskService = getProcessEngine().getTaskService();
    List<Task> list = taskService.createTaskQuery() .processDefinitionKey(processDefinitionKey).includeProcessVariables().taskAssignee(assignee).list();
    for (Task task : list) {
        System.out.println("----------------------------");
        System.out.println("流程实例id:" + task.getProcessInstanceId());
        System.out.println("任务id:" + task.getId());
        System.out.println("任务负责人:" + task.getAssignee());
        System.out.println("任务名称:" + task.getName());
    }
}

关联businessKey

在查询待办任务时,通过businessKey(业务标识 )关联查询业务系统的请假单表,查询出请假天数等信息

@Test
public void testGetBusinessKey(){
    // 1.获取流程引擎
    ProcessEngine processEngine = getProcessEngine();
    // 2.创建runtimeService对象和TaskService对象
    RuntimeService runtimeService = processEngine.getRuntimeService();
    TaskService taskService = processEngine.getTaskService();
    // 3.通过taskService查询到个人任务 一般用list查询
    Task task = taskService.createTaskQuery().processDefinitionKey("myholiday").taskAssignee("lisi").singleResult();
    // 4.通过task对象获取流程实例id
    String processInstanceId = task.getProcessInstanceId();
    // 5.通过流程实例id获取流程实例对象
    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
    // 6.获取businessKey
    String businessKey = processInstance.getBusinessKey();
    // 7.根据businessKey可以获取到请假单信息
    System.out.println(businessKey);
}

办理任务

//完成任务
@Test
public void completeTask() {
    String taskId = "12504";
    TaskService taskService = getProcessEngine().getTaskService();
    taskService.complete(taskId);
    System.out.println("任务完成ID = " + taskId);
}

实际应用中,应校验任务的负责人是否具有该任务的办理权限

@Test
public void comleteTask(){
    String taskId = "12504";
    String assignee = "zhaoliu";
    TaskService taskService = getProcessEngine().getTaskService();
    Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(assignee).singleResult();
    if(task!=null){
        taskService.complete(taskId);
        System.out.println("任务完成ID = " + taskId);
    }
}

流程变量

流程变量在activiti中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和activiti结合时少不了流程变量,流程变量就是activiti在管理工作流时根据管理需要而设置的变量。
比如在请假流程流转时如果请假天数大于3天则由总经理审核,否则由人事直接审核,请假天数就可以设置为流程变量,在流程流转时使用。

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

流程变量类型

  • string
  • integer
  • short
  • double
  • boolean
  • long
  • date
  • binary
  • serializable

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

流程变量作用域

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

这三个作用域流程实例的范围最大,可以称为global变量,任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大,称为local变量

  • global变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。
  • local变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。
  • local变量名也可以和global变量名相同,没有影响。

流程变量的使用方法

第一步:设置流程变量

第二部:通过UEL表达式使用流程变量

​ 1)可以在assignee处设置UEL表达式,表达式的值为任务的负责人

​ 比如:${assignee},assignee就是一个流程变量名称

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

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

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

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

使用global变量控制流程走向

bpmn13.png

请假天数小于3天,直接进入人事经理存档,大于等于3天,需要总经理审批

设置global流程变量

在部门经理审核前设置流程变量,变量值为请假单信息(包括请假天数),部门经理审核后可以根据流程变量的值决定流程走向

启动流程时设置

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

通过map<key,value>设置流程变量,map中可以设置多个变量,这个key就是流程变量的名字。

@Test
public void testStartProcessInstanceSetVariable() {
    ProcessEngine processEngine = getProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    Map<String, Object> variables = new HashMap();
    variables.put("num", 3);
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myholiday", "testBusinessKey",variables);
    System.out.println("流程定义ID:" + processInstance.getProcessDefinitionId());
    System.out.println("流程实例ID:" + processInstance.getId());
    System.out.println("当前活动ID:" + processInstance.getActivityId());
}

说明:

startProcessInstanceByKey(processDefinitionKey, variables)流程变量作用域是一个流程实例,流程变量使用Map存储,同一个流程实例设置变量map中key相同,后者覆盖前者。

任务办理时设置

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

@Test
public void completeTaskSetVariable() {
    String taskId = "12504";
    TaskService taskService = getProcessEngine().getTaskService();
    Map<String, Object> variables = new HashMap<>();
    variables.put("num",3);
    taskService.complete(taskId,variables);
    System.out.println("任务完成ID = " + taskId);
}

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

通过当前流程实例设置

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

@Test
public void setGlobalVariableByExecutionId(){
    //当前流程实例执行 id,通常设置为当前执行的流程实例
    String executionId="2601";
    RuntimeService runtimeService = getProcessEngine().getRuntimeService();
    // 可以设置为对象,根据UEL表达式来设置
    Holiday holiday = new Holiday();
    //通过流程实例 id设置流程变量
    runtimeService.setVariable(executionId, "holiday", holiday);
    //一次设置多个值
    // runtimeService.setVariables(executionId, variables)
}

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

通过当前任务设置

@Test
public void setGlobalVariableByTaskId(){
    //当前待办任务id
    String taskId="1404";
    TaskService taskService = getProcessEngine().getTaskService();
    taskService.setVariable(taskId,"num",3);
}

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

如果该任务已结束,会抛出异常:org.activiti.engine.ActivitiObjectNotFoundException

也可以通过taskService.getVariable()获取流程变量

注意事项

  1. 如果UEL表达式中流程变量名不存在则报错。
  2. 如果UEL表达式中流程变量值为空NULL,流程不按UEL表达式去执行,而流程结束 。
  3. 如果UEL表达式都不符合条件,流程结束
  4. 如果连线不设置条件,会走flow序号小的那条线

操作数据库

设置流程变量会在当前执行流程变量表插入记录,同时也会在历史流程变量表也插入记录。
SELECT * FROM act_ru_variable #当前流程变量表
记录当前运行流程实例可使用的流程变量,包括 global和local变量
Id_:主键
Type_:变量类型
Name_:变量名称
Execution_id_:所属流程实例执行id,global和local变量都存储
Proc_inst_id_:所属流程实例id,global和local变量都存储
Task_id_:所属任务id,local变量存储
Bytearray_:serializable类型变量存储对应act_ge_bytearray表的id
Double_:double类型变量值
Long_:long类型变量值
Text_:text类型变量值
SELECT * FROM act_hi_varinst #历史流程变量表
记录所有已创建的流程变量,包括 global和local变量
字段意义参考当前流程变量表

设置local流程变量

任务办理时设置

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

通过taskService.setVariablesLocal()方法进行设置

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

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

组任务

Candidate-users 任务候选人

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

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


bpmn14.png

办理组任务

流程

  1. 查询组任务

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

    候选人不能办理任务

  2. 拾取(claim)任务

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

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

    如果失去后不想办理该组任务,需要将拾取的个人任务归还到组里,将个人任务变成组任务

  3. 查询个人任务

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

  4. 办理个人任务

用户查询组任务

@Test
public void findGroupTaskList() {
    // 流程定义key
    String processDefinitionKey = "myholiday";
    // 任务候选人
    String candidateUser = "zhangsan";
    // 创建TaskService
    TaskService taskService = getProcessEngine().getTaskService();
    //查询组任务
    List<Task> list = taskService.createTaskQuery().processDefinitionKey(processDefinitionKey).taskCandidateUser(candidateUser).list();
    for (Task task : list) {
        System.out.println("----------------------------");
        System.out.println("流程实例id:" + task.getProcessInstanceId());
        System.out.println("任务id:" + task.getId());
        System.out.println("任务负责人:" + task.getAssignee());
        System.out.println("任务名称:" + task.getName());
    }
}

用户拾取组任务

@Test
public void claimTask() {
    TaskService taskService = getProcessEngine().getTaskService();
    //要拾取的任务id 
    String taskId = "6302";
    //任务候选人id 
    String userId = "lisi";
    //拾取任务 
    // 即使该用户不是候选人也能拾取(建议拾取时校验是否有资格) 
    // 校验该用户有没有拾取任务的资格 
    //根据候选人查询 
    Task task = taskService.createTaskQuery().taskId(taskId).taskCandidateUser(userId).singleResult();
    if (task != null) {
        taskService.claim(taskId, userId);
        System.out.println("任务拾取成功");
    }
}

说明:

即使该用户不是候选人也能拾取,建议拾取时校验是否有资格
组任务拾取后,该任务已有负责人,通过候选人将查询不到该任务

用户查询个人代办任务

同个人任务

@Test
public void findPersonalTaskList() {
    // 流程定义key 
    String processDefinitionKey = "holiday4";
    // 任务负责人 
    String assignee = "zhangsan";
    // 创建TaskService 
    TaskService taskService = getProcessEngine().getTaskService();
    List<Task> list = taskService.createTaskQuery().processDefinitionKey(processDefinitionKey).taskAssignee(assignee).list();
    for (Task task : list) {
        System.out.println("----------------------------");
        System.out.println("流程实例id:" + task.getProcessInstanceId());
        System.out.println("任务id:" + task.getId());
        System.out.println("任务负责人:" + task.getAssignee());
        System.out.println("任务名称:" + task.getName());
    }
}

用户办理个人任务

同个人任务

@Test
public void comleteTask(){
    String taskId = "12504";
    String assignee = "zhaoliu";
    TaskService taskService = getProcessEngine().getTaskService();
    Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(assignee).singleResult();
    if(task!=null){
        taskService.complete(taskId);
        System.out.println("任务完成ID = " + taskId);
    }
}

归还组任务

如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人

// 归还组任务,由个人任务变为组任务,还可以进行任务交接
@Test
public void setAssigneeToGroupTask() {
    // 查询任务使用TaskService
    TaskService taskService = getProcessEngine().getTaskService();
    // 当前待办任务
    String taskId = "6004";
    // 任务负责人
    String userId = "zhangsan";
    // 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
    Task task = taskService.createTaskQuery().taskId(taskId)
            .taskAssignee(userId).singleResult();
    if (task != null) {
        // 如果设置为null,归还组任务,该 任务没有负责人
        taskService.setAssignee(taskId, null);
    }
}

建议归还任务前校验该用户是否是该任务的负责人

也可以通过setAssignee方法将任务委托给其它用户负责

注意被委托的用户可以不是候选人(建议不要这样使用)

任务交接

任务交接,任务负责人将任务交给其它候选人办理该任务

@Test
public void setAssigneeToCandidateUser() {
    TaskService taskService = getProcessEngine().getTaskService();
    // 当前待办任务
    String taskId = "6004";
    // 任务负责人
    String userId = "zhangsan";
    // 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
    Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(userId).singleResult();
    if (task != null) {
        // 将此任务交给其它候选人办理该 任务
        String candidateuser = "zhangsan";
        // 根据候选人和组任务id查询,如果有记录说明该 候选人有资格拾取该 任务
        Task task2 = taskService.createTaskQuery().taskId(taskId).taskCandidateUser(candidateuser).singleResult();
        if (task2 != null) {
            // 才可以交接
            taskService.setAssignee(taskId, candidateuser);
        }
    }
}

库表操作

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

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

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

网关

排他网关(ExclusiveGateway)

排他网关(也叫异或(XOR)网关,或叫基于数据的排他网关),用来在流程中实现决策。

当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支。

排他网关只会选择一个为true的分支执行。(即使有两个分支条件都为true,排他网关也会只选择一条分支去执行)

bpmn15.png

当图中连线上的condition分支条件都不满足时,流程会结束(异常结束)


bpmn16.png

当使用排他网关后,如果网关出去的所以分支条件都不满足,则会抛出异常。

经过排他网关必须要有一条且只有一条分支走。

并行网关(ParallelGateway)

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

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

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

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

bpmn17.png

财务结算和行政考勤是两个execution分支,在act_ru_execution表有两条记录分别是财务结算和行政考勤,act_ru_execution还有一条记录表示该流程实例。
待财务结算和考勤任务全部完成,在汇聚点汇聚,通过parallelGateway并行网关。
并行网关在业务应用中常用于会签任务,会签任务即多个参与者共同办理的任务。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容