activiti 工作流
工作流涉及到的表
| 表名 | 中文含义 | 
|---|---|
| act_re_deployment | 部署信息表 | 
| act_re_model | 流程设计模型部署表 | 
| act_re_procdef | 流程定义数据表 | 
| act_ru_excution | 运行时流程执行实例表 | 
| act_ru_identitylink | 运行时流程人员表,主要存储任务节点与参与者相关信息 | 
| act_ru_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 | 中间事件 | 
画流程图


myholiday为流程的key


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&characterEncoding=UTF-8&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("==============================");
        }
    }
}
常用属性说明
ProcessDefinitionId 流程定义ID
流程部署后生成的流程定义ID,全局唯一
ProcessDeployId 流程部署ID
流程部署后生成的ID,全局唯一
ProcessInstanceId 流程实例ID
流程启动后生成的ID,全局唯一,一个流程实例表示一个流程
TaskId 任务ID
任务ID,流程启动后,每一个流程任务对应的ID
TaskDefinitionKey 任务定义ID
与流程图中,任务节点的ID相同,一个节点对应一个ID,运行是如果是多实例任务,多个任务对应同一个TaskDefinitionKey
TaskName 任务名称
与流程图中定义的节点名称相同
进阶使用
流程实例
参与者(可以是用户也可以是程序)按照流程定义内容发起一个流程,这就是一个流程实例。是动态的。

启动流程实例
@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 业务标识

@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 + "挂起");
    }
}
个人任务
分配任务负责人
固定分配

在properties视图中,填写Assignee项为任务负责人。
注意事项
由于固定分配方式,任务只管一步一步执行任务,执行到每一个任务将按照bpmn的配置去分配任务负责人。
表达式分配
UEL 表达式
activiti支持UEL-value和UEL-method
- UEL-value

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

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

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,也可作为条件判断。
如下:
$ 
使用流程变量分配任务
定义任务分配流程变量

设置流程变量
//启动流程实例时设计流程变量 
//定义流程变量 
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逻辑或表达式
任务监听事件包括:

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就是一个流程变量名称,uel表达式结果类型为布尔类型
 如果UEL表达式是true,要决定 流程执行走向。
使用global变量控制流程走向

请假天数小于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()获取流程变量
注意事项
- 如果UEL表达式中流程变量名不存在则报错。
 - 如果UEL表达式中流程变量值为空NULL,流程不按UEL表达式去执行,而流程结束 。
 - 如果UEL表达式都不符合条件,流程结束
 - 如果连线不设置条件,会走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(候选人),多个候选人之间用逗号分开!

办理组任务
流程
- 
查询组任务
指定候选人,查询该候选人当前的代办任务
候选人不能办理任务
 - 
拾取(claim)任务
改组任务的所有候选人都能拾取。
将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人
如果失去后不想办理该组任务,需要将拾取的个人任务归还到组里,将个人任务变成组任务
 - 
查询个人任务
查询方式同个人任务部分,根据assignee查询用户负责的个人任务
 - 
办理个人任务
 
用户查询组任务
@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,排他网关也会只选择一条分支去执行)

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

当使用排他网关后,如果网关出去的所以分支条件都不满足,则会抛出异常。
经过排他网关必须要有一条且只有一条分支走。
并行网关(ParallelGateway)
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的
- 
fork分支:
并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。 - 
join汇聚:
所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关 
如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支

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