Activiti7 业务实战

2,502 阅读14分钟

一、流程实例

1.1 流程实例和业务的整合

在实际业务中,我们的业务信息会有一张自己的表,而这些信息会存在我们自己系统的表中,而不是Activiti的25张表中,我们会通过BussinessKey将他们进行关联,接下来我们来实现关联。

BussinessKey存在于Activiti中的act_ru_execution表中


import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance;

/*
流程添加business实例
 */
public class BusinessKeyAdd {
    public static void main(String[] args) {
        //1.得到ProcessEngine对象
        ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
        //2.得到RunService对象
        RuntimeService runtimeService = defaultProcessEngine.getRuntimeService();
        //3.创建流程实例,同时要添加业务标识BusinessKey(第二个参数),假设请假单的id是10001
        ProcessInstance holiday = runtimeService.startProcessInstanceByKey("holiday","10001");
        //4.输出流程实例的相关信息
        System.out.println("BusinessKey:"+holiday.getBusinessKey());
    }
}

1.2流程定义的挂起与激活

当公司制度发生改变,流程发生改变的情况下,有可能会需要将未执行完成的任务挂起

流程定义为挂起状态下,该流程定义下将不允许启动新的流程实例,已经发起的该流程定义的流程实例不受影响(如果选择级联挂起则流程实例也会被挂起)。

package com.bin.activiti.two;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.ProcessDefinition;


/*
全部流程实例的挂起和激活
 */
public class SuspendProcessInstance {
    public static void main(String[] args) {
        //1.得到ProcessEngine对象
        ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
        //2.得到RunService对象
        RepositoryService repositoryService = defaultProcessEngine.getRepositoryService();
        //3.查询流程定义对象
        ProcessDefinition holiday = repositoryService.createProcessDefinitionQuery().processDefinitionKey("holiday").singleResult();
        //4.得到当前流程定义的实例是否都为暂停状态
        boolean suspended = holiday.isSuspended();
        //5.判断
        if (suspended){//如果是暂停则激活
            repositoryService.activateProcessDefinitionById(holiday.getId());
            System.out.println("流程定义"+holiday.getId()+"激活");
        } else {//如果是激活则暂停
            repositoryService.suspendProcessDefinitionById(holiday.getId());//普通挂起
            repositoryService.suspendProcessDefinitionById(holiday.getId(),true,null);//级联挂起
            System.out.println("流程定义"+holiday.getId()+"挂起");
        }
    }
} 

1.3流程实例的挂起和激活

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


import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;


/*
单个流程实例的挂起和激活
 */
public class SuspendProcessInstance2 {
    public static void main(String[] args) {
        //1.得到ProcessEngine对象
        ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
        //2.得到RunService对象
        RuntimeService runtimeService = defaultProcessEngine.getRuntimeService();
        //3.查询流程定义对象
        ProcessInstance holiday = runtimeService.createProcessInstanceQuery().processInstanceId("15001").singleResult();
        //4.得到当前流程定义的实例是否都为暂停状态
        boolean suspended = holiday.isSuspended();
        //5.判断
        if (suspended){//如果是暂停则激活
            runtimeService.activateProcessInstanceById(holiday.getId());
            System.out.println("流程实例"+holiday.getId()+"激活");
        } else {//如果是激活则暂停
            runtimeService.suspendProcessInstanceById(holiday.getId());
            System.out.println("流程实例"+holiday.getId()+"挂起");
        }
    }
}

将流程实例挂起后执行测试

 //1.获取ProcessEngine对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取taskService
        TaskService taskService = processEngine.getTaskService();
        //3.根据任务流程的key和任务负责人查询任务
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("holiday")
                 .taskAssignee("zhangsan").singleResult();
        System.out.println(task.getId());
        //执行任务
        taskService.complete(task.getId()); 

执行抛出异常

二、个人任务

2.1 固定分配任务负责人

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

2.2 UEL表达式分配

Activiti使用 UEL 表达式,UEL 是 java EE6规范的一部分,UEL(Unified Expression Language)即统一表达式语言,activiti支持两个 UEL表达式:UEL-value 和 UEL-method。

UEL-value 的形式

UEL-method的形式

2.2.1 实现UEl表达式

    public static void main(String[] args) {
        //1.得到ProcessEngine对象
        ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
        //2.得到RunService对象
        RuntimeService runtimeService = defaultProcessEngine.getRuntimeService();
        //3.创建流程实例
        HashMap<String, Object> map = new HashMap<>();
        map.put("asssignee","zhaoqi");
        ProcessInstance holiday = runtimeService.startProcessInstanceByKey("holiday2",map);
        //4.输出流程实例的相关信息
        System.out.println("名称:"+holiday.getName());
        System.out.println("ID:"+holiday.getId());
        System.out.println("商业KEY:"+holiday.getBusinessKey());
        System.out.println("实例ID:"+holiday.getActivityId());
    }

2.3 监听器分配

任务监听器是发生对应的任务相关事件时执行自定义 java 逻辑 或表达式。 任务相当事件包括:

Create:任务创建后触发

Assignment:任务分配后触发

Delete:任务完成后触发

All:所有事件发生都触发

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

三、流程变量

3.1 什么是流程变量

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

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

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

3.2 流程变量的数据类型

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

3.3 流程实例作用域

流程变量的作用域默认是一个流程实例(processInstance),也可以是一个任务(task)或一个执行实例(execution),这三个作用域流程实例的范围最大,可以称为global变量,任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大,为称为 local 变量。

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

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

3.4 流程变量

第一步:设置流程变量可以在连线上设置

第二步:通过 UEL表达式使用流程变量 UEL表达式,决定流程走向 比如:${price>=10000}${price<10000},price就是一个流程变量名称,uel 表达式结果类型为布尔类型,如果 UEL表达式是true,要决定流程执行走向。

3.5 Global变量控制流程

3.5.1 需求

员工创建请假申请单,由部门经理审核,部门经理审核通过后请假3天及以下由人事经理直接审核,3天以上先由总经理审核,总经理审核通过再由人事经理存档。

3.5.2 制作相应的流程定义文件并且部署

制作好相应的BPMN文件和PNG文件 然后部署 流程定义

package com.bin.activiti.three;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;

/*
流程定义部署
 */
public class VariableTest {
    public static void main(String[] args) {
        //1.创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.得到repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //3.
        Deployment deploy = repositoryService.
                createDeployment().
                addClasspathResource("diagram/holiday3.bpmn").
                addClasspathResource("diagram/holiday3.png").
                name("请假流程-流程变量").
                deploy();

        System.out.println(deploy.getName());
    }
}
#### 3.5.3 编写实体类和创建创建

新建一个Holiday类,使用了lombok插件

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Holiday implements Serializable {
    private Integer id;
    private Date beginDate;
    private Date endDate;
    private Float num;
    private String reason;
    private String type;
}

3.5.4 创建时设置变量执行任务并测试

新建流程实例

 //1.创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取runtimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
        //3.配置holiday
        Map<String,Object> map = new HashMap<>();
        Holiday holi = new Holiday();
        holi.setNum(1F);
        map.put("holiday",holi);
        //4.启动流程实例
        ProcessInstance holiday3 = runtimeService.startProcessInstanceByKey("holiday3",map);

        System.out.println(holiday3.getId());
        System.out.println(holiday3.getName());

查看数据表可以发现--act_ge_bytearrayact_ru_variable--这两张表中存储了holiday的信息

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

public static void runTask(String assignee){
        //1.创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取taskService
        TaskService taskService = processEngine.getTaskService();
        //3.查询任务
        Task task = taskService.createTaskQuery().taskAssignee(assignee).processDefinitionKey("holiday3").singleResult();
        //4.执行任务
        if(task != null) {taskService.complete(task.getId());}
        else {
            System.out.println("没有查到任务");
        }
    }

可以用这个方法来执行测试任务流程是否正常

3.5.5 任务办理时设置变量

在启动流程实例时,不要设置map,创建一个方法,在张三执行的时候,设置请假天数 。

public static void runTaskNum(Float num){
        //1.创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取taskService
        TaskService taskService = processEngine.getTaskService();
        //3.查询任务
        Task task = taskService.createTaskQuery().taskAssignee("zhangsan").processDefinitionKey("holiday3").singleResult();
        //4.执行任务
        Map<String,Object> map = new HashMap<>();
        Holiday holi = new Holiday();
        holi.setNum(num);
        map.put("holiday",holi);
        if(task != null) {taskService.complete(task.getId(),map);}
        else {
            System.out.println("没有查到任务");
        }
    }

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

3.5.6 通过当前流程实例ID设置

public static void instIdNum(String id,Float num){//id的实例的id 
        //1.创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取runtimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
        //3.配置holiday
        Holiday holi = new Holiday();
        holi.setNum(num);
        //设置变量
        runtimeService.setVariable(id,"holiday3",holi);
    }

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

3.5.7 通过当前任务ID设置

public static void taskIdNum(String taskid,Float num){
        //1.创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取taskService
        TaskService taskService = processEngine.getTaskService();
        //3.配置holiday
        Holiday holi = new Holiday();
        holi.setNum(num);
        //4.设置变量
        taskService.setVariable(taskid,"holiday3",holi);
    }

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

3.5.8 注意事项

1、 如果UEL表达式中流程变量名不存在则报错。

2、 如果UEL表达式中流程变量值为空NULL,流程不按UEL表达式去执行,而流程结束 。

3、 如果UEL表达式都不符合条件,流程结束

4、 如果连线不设置条件,会走flow序号小的那条线

3.6 local变量的设置

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

3.6.1 任务办理时设置

类似3.5.5

public static void runTaskNum(Float num){
        //1.创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取taskService
        TaskService taskService = processEngine.getTaskService();
        //3.查询任务
        Task task = taskService.createTaskQuery().taskAssignee("zhangsan").processDefinitionKey("holiday3").singleResult();
        //4.执行任务,设置变量
        Map<String,Object> map = new HashMap<>();
        Holiday holi = new Holiday();
        holi.setNum(num);
        map.put("holiday",holi);
        taskService.setVariablesLocal(task.getId(),map);//设置多个局部变量
        if(task != null) {taskService.complete(task.getId(),map);}
        else {
            System.out.println("没有查到任务");
        }
    }

3.6.2 通过任务ID设置

类似3.5.7

   ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取taskService
        TaskService taskService = processEngine.getTaskService();
        //3.配置holiday
        Holiday holi = new Holiday();
        holi.setNum(num);
        //4.设置变量
        taskService.setVariableLocal(taskid,"holiday3",holi);//设置单个局部变量
    }

四、组任务

4.1 Candidate-users 候选人

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

在bpmn中 设置候选人

4.2 组任务办理流程

第一步:查询组任务

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

第二步:拾取(claim)任务

  • 该组任务的所有候选人都能拾取。 将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人

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

第三步:查询个人任务

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

第四步:办理个人任务

4.3 前期准备工作

制作一个新流程定义 ,其中部门经理审批为候选人模式

将流程部署,并且启动完成请假单的填写。观察数据库表发现没有负责人。

4.4 查询候选人组任务

在主方法中运行该任务,传入候选人的名称,可以发现task.getAssignee()的值为空

    public static void queryTask(String candidate_user){
        //1.创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取taskService
        TaskService taskService = processEngine.getTaskService();
        //3.查询任务
        List<Task> holiday4 = taskService.createTaskQuery().processDefinitionKey("holiday4").taskCandidateUser(candidate_user).list();
        for (Task task : holiday4) {
            System.out.println(task.getName());
            System.out.println(task.getId());
            System.out.println(task.getProcessInstanceId());
            System.out.println(task.getAssignee());
        }
    }

4.5 候选人拾取组任务

将候选人转化为真正的任务负责人

//候选用户拾取组任务
    public static void claimTask(String candidate_user){
        //1.创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取taskService
        TaskService taskService = processEngine.getTaskService();
        //3.查询任务
        Task task = taskService.createTaskQuery().processDefinitionKey("holiday4").taskCandidateUser(candidate_user).singleResult();
        //4.拾取任务
        if (task!=null) {
            taskService.claim(task.getId(), candidate_user);
            System.out.println(candidate_user+"拾取了组任务");
        }
    }

说明:即使该用户不是候选人也能拾取,建议拾取时校验是否有资格

组任务拾取后,该任务已有负责人,通过候选人将查询不到该任务。

4.6 负责人查询任务

//查询负责人的组任务
    public static void queryTaskByAssignee(String assignee){
        //1.创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取taskService
        TaskService taskService = processEngine.getTaskService();
        //3.查询任务
        List<Task> holiday4 = taskService.createTaskQuery().processDefinitionKey("holiday4").taskAssignee(assignee).list();
        for (Task task : holiday4) {
            System.out.println(task.getName());
            System.out.println(task.getId());
            System.out.println(task.getProcessInstanceId());
            System.out.println(task.getAssignee());
        }
    }

说明:建议完成任务前校验该用户是否是该任务的负责人。

4.7 负责人归还组任务

    //负责人归还组任务
    public static void unClaimTask(String assignee){
        //1.创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取taskService
        TaskService taskService = processEngine.getTaskService();
        //3.查询任务
        Task task = taskService.createTaskQuery().processDefinitionKey("holiday4").taskAssignee(assignee).singleResult();
        //4.拾取任务
        if (task!=null) {
            taskService.unclaim(task.getId());//方式1
            //taskService.setAssignee(taskId, null);//方式2
            System.out.println(assignee+"归还了组任务");
        }
    }

4.8 交接任务

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

//任务交接
    public static void setAssToCan(String candidate_user){
        //1.创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //2.获取taskService
        TaskService taskService = processEngine.getTaskService();
        //3.查询任务
        Task task = taskService.createTaskQuery().processDefinitionKey("holiday4").taskAssignee("lisi").singleResult();
        //4.交接任务
        if (task!=null) {
            taskService.setAssignee(task.getId(),candidate_user);//如果candidate_user为空,则该任务失去负责人,相当于归还任务
            System.out.println("交接组任务给"+candidate_user);
        }
    } 

4.9 数据库表总结

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

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

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

五、网关

5.1 排他网关

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

为什么要用排他网关? 不用排他网关也可以实现分支,如下图:

上图中,在连线的 condition 条件上设置分支条件。 缺点:如果条件都不满足,不使用排他网关,流程就结束了(是异常结束)。 如果一个条件是<=1,一个是<=5,那么两个都会执行,流程也会出错。 如果使用排他网关决定分支的走向,如下:

注:当多个流程判断都为true时,走ID小的那个。 如果所有分支都为flase,则抛出异常

5.2 并行网关

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

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

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

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

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

5.3 包含网关

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

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

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

企业体检流程,公司全体员工进行常规项检查、抽血化验,公司管理层除常规检查和抽血化验还要进行增加项检查。

员工类型:通过流程变量userType来表示,如果等于1表示普通员工,如果等于2表示领导