Activiti7快速入门(包含整合过程)

Activiti基础篇

一、工作流介绍

1.1 概念

工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。

1.2 工作流系统

一个软件系统中具有工作流的功能,我们把它称为工作流系统,一个系统中工作流的功能是什么?就是对系统的业务流程进行自动化管理,所以工作流是建立在业务流程的基础上,所以一个软件的系统核心根本上还是系统的业务流程,工作流只是协助进行业务流程管理。即使没有工作流业务系统也可以开发运行,只不过有了工作流可以更好的管理业务流程,提高系统的可扩展性。

二、Activiti7概述

2.1 介绍

Alfresco软件在2010年5月17日宣布Activiti业务流程管理(BPM)开源项目的正式启动,其首席架构师由业务流程管理BPM的专家 Tom Baeyens担任,Tom Baeyens就是原来jbpm的架构师,而jbpm是一个非常有名的工作流引擎,当然activiti也是一个工作流引擎。

Activiti是一个工作流引擎, activiti可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。

官方网站:Open Source Business Automation | Activiti

2.1.1 BPM

BPM(Business Process Management),即业务流程管理,是一种规范化的构造端到端的业务流程,以持续的提高组织业务效率。常见商业管理教育如EMBA、MBA等均将BPM包含在内。

2.1.2 BPM软件

BPM软件就是根据企业中业务环境的变化,推进人与人之间、人与系统之间以及系统与系统之间的整合及调整的经营方法与解决方案的IT工具。

通过BPM软件对企业内部及外部的业务流程的整个生命周期进行建模、自动化、管理监控和优化,使企业成本降低,利润得以大幅提升。

BPM软件在企业中应用领域广泛,凡是有业务流程的地方都可以BPM软件进行管理,比如企业人事办公管理、采购流程管理、公文审批流程管理、财务管理等。

2.1.3 BPMN

BPMN(Business Process Model AndNotation)- 业务流程模型和符号 是由BPMI(BusinessProcess Management Initiative)开发的一套标准的业务流程建模符号,使用BPMN提供的符号可以创建业务流程。

BPMN 是目前被各 BPM 厂商广泛接受的 BPM 标准。Activiti 就是使用 BPMN 2.0 进行流程建模、流程执行管理,它包括很多的建模符号,比如:

Event

用一个圆圈表示,它是流程中运行过程中发生的事情。

活动用圆角矩形表示,一个流程由一个活动或多个活动组成

Bpmn图形是通过xml表示业务流程,上边的.bpmn文件使用文本编辑器打开:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
  <process id="myProcess" name="My process" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <userTask id="usertask1" name="创建请假单"></userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
    <userTask id="usertask2" name="部门经理审核"></userTask>
    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
    <userTask id="usertask3" name="人事复核"></userTask>
    <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow4" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_myProcess">
    <bpmndi:BPMNPlane bpmnElement="myProcess" id="BPMNPlane_myProcess">
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="130.0" y="160.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
        <omgdc:Bounds height="55.0" width="105.0" x="210.0" y="150.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
        <omgdc:Bounds height="55.0" width="105.0" x="360.0" y="150.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
        <omgdc:Bounds height="55.0" width="105.0" x="510.0" y="150.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="660.0" y="160.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="165.0" y="177.0"></omgdi:waypoint>
        <omgdi:waypoint x="210.0" y="177.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="315.0" y="177.0"></omgdi:waypoint>
        <omgdi:waypoint x="360.0" y="177.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="465.0" y="177.0"></omgdi:waypoint>
        <omgdi:waypoint x="510.0" y="177.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="615.0" y="177.0"></omgdi:waypoint>
        <omgdi:waypoint x="660.0" y="177.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

2.2 使用步骤

部署activiti

Activiti是一个工作流引擎(其实就是一堆jar包API),业务系统访问(操作)activiti的接口,就可以方便的操作流程相关数据,这样就可以把工作流环境与业务系统的环境集成在一起。

流程定义

使用activiti流程建模工具(activity-designer)定义业务流程(.bpmn文件) 。

.bpmn文件就是业务流程定义文件,通过xml定义业务流程。

流程定义部署

activiti部署业务流程定义(.bpmn文件)。

使用activiti提供的api把流程定义内容存储起来,在Activiti执行过程中可以查询定义的内容

Activiti执行把流程定义内容存储在数据库中

启动一个流程实例

流程实例也叫:ProcessInstance

启动一个流程实例表示开始一次业务流程的运行。

在员工请假流程定义部署完成后,如果张三要请假就可以启动一个流程实例,如果李四要请假也启动一个流程实例,两个流程的执行互相不影响。

用户查询待办任务(Task)

因为现在系统的业务流程已经交给activiti管理,通过activiti就可以查询当前流程执行到哪了,当前用户需要办理什么任务了,这些activiti帮我们管理了,而不需要开发人员自己编写在sql语句查询。

用户办理任务

用户查询待办任务后,就可以办理某个任务,如果这个任务办理完成还需要其它用户办理,比如采购单创建后由部门经理审核,这个过程也是由activiti帮我们完成了。

流程结束

当任务办理完成没有下一个任务结点了,这个流程实例就完成了。

三、Activiti环境

3.1 加入依赖

 

1) Database

activiti运行需要有数据库的支持,支持的数据库有:h2, mysql, oracle, postgres, mssql, db2。

3.2.2 流程设计器IDEA下安装

在IDEA的File菜单中找到子菜单”Settings”,后面我们再选择左侧的“plugins”菜单,如下图所示:

在新版本中,我们通过实验可以发现IdentityService,FormService两个Serivce都已经删除了。

所以后面我们对于这两个Service也不讲解了,但老版本中还是有这两个Service,同学们需要了解一下

4.4 工作流引擎创建

工作流引擎(ProcessEngine),相当于一个门面接口,整合微服务后,从容器中直接获取

4.5 Servcie服务接口

Service是工作流引擎提供用于进行工作流部署、执行、管理的服务接口,我们使用这些接口可以就是操作服务对应的数据表

4.5.1 Service创建方式

通过ProcessEngine创建Service

方式如下:

RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();

4.5.2 Service总览

service名称service作用
RepositoryServiceactiviti的资源管理类
RuntimeServiceactiviti的流程运行管理类
TaskServiceactiviti的任务管理类
HistoryServiceactiviti的历史管理类
ManagerServiceactiviti的引擎管理类

简单介绍:

RepositoryService

是activiti的资源管理类,提供了管理和控制流程发布包和流程定义的操作。使用工作流建模工具设计的业务流程图需要使用此service将流程定义文件的内容部署到计算机。

除了部署流程定义以外还可以:查询引擎中的发布包和流程定义。

暂停或激活发布包,对应全部和特定流程定义。 暂停意味着它们不能再执行任何操作了,激活是对应的反向操作。获得多种资源,像是包含在发布包里的文件, 或引擎自动生成的流程图。

获得流程定义的pojo版本, 可以用来通过java解析流程,而不必通过xml。

RuntimeService

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

TaskService

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

HistoryService

Activiti的历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据(根据配置),比如流程实例启动时间,任务的参与者, 完成任务的时间,每个流程实例的执行路径,等等。 这个服务主要通过查询功能来获得这些数据。

ManagementService

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

五、Activiti入门

创建Activiti工作流主要包含以下几步:

1、定义流程,按照BPMN的规范,使用流程定义工具,用流程符号把整个流程描述出来

2、部署流程,把画好的流程定义文件,加载到数据库中,生成表的数据

3、启动流程,使用java代码来操作数据库表中的内容

5.1 流程符号

BPMN 2.0是业务流程建模符号2.0的缩写。

它由Business Process Management Initiative这个非营利协会创建并不断发展。作为一种标识,BPMN 2.0是使用一些符号来明确业务流程设计流程图的一整套符号规范,它能增进业务建模时的沟通效率。

目前BPMN2.0是最新的版本,它用于在BPM上下文中进行布局和可视化的沟通。

接下来我们先来了解在流程设计中常见的 符号。

BPMN2.0的基本符合主要包含:

事件 Event

新建后右击打开绘图面板

右键面板绘图

生成.png图片文件

绘图后右键生成png图片

6.2 流程定义部署

概述

将上面在设计器中定义的流程部署到activiti数据库中,就是流程定义部署。

通过调用activiti的api将流程定义的bpmn和png两个文件一个一个添加部署到activiti中,也可以将两个文件打成zip包进行部署。

单个文件部署方式

分别将bpmn文件和png图片文件部署。

package com.itheima.test;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.junit.Test;

public class ActivitiDemo {
    /**
     * 部署流程定义
     */
    @Test
    public void testDeployment(){
//        1、创建ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、得到RepositoryService实例
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        3、使用RepositoryService进行部署
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("bpmn/evection.bpmn") // 添加bpmn资源
                .addClasspathResource("bpmn/evection.png")  // 添加png资源
                .name("出差申请流程")
                .deploy();
//        4、输出部署信息
        System.out.println("流程部署id:" + deployment.getId());
        System.out.println("流程部署名称:" + deployment.getName());
    }
}

执行此操作后activiti会将上边代码中指定的bpm文件和图片文件保存在activiti数据库。

压缩包部署方式

将evection.bpmn和evection.png压缩成zip包。

@Test
	public void deployProcessByZip() {
		// 定义zip输入流
		InputStream inputStream = this
				.getClass()
				.getClassLoader()
				.getResourceAsStream(
						"bpmn/evection.zip");
		ZipInputStream zipInputStream = new ZipInputStream(inputStream);
		// 获取repositoryService
		RepositoryService repositoryService = processEngine
				.getRepositoryService();
		// 流程部署
		Deployment deployment = repositoryService.createDeployment()
				.addZipInputStream(zipInputStream)
				.deploy();
		System.out.println("流程部署id:" + deployment.getId());
		System.out.println("流程部署名称:" + deployment.getName());
	}

执行此操作后activiti会将上边代码中指定的bpm文件和图片文件保存在activiti数据库。

操作数据表

流程定义部署后操作activiti的3张表如下:

act_re_deployment 流程定义部署表,每部署一次增加一条记录

act_re_procdef 流程定义表,部署每个新的流程定义都会在这张表中增加一条记录

act_ge_bytearray 流程资源表

接下来我们来看看,写入了什么数据:

SELECT * FROM act_re_deployment #流程定义部署表,记录流程部署信息

结果:

注意:

act_re_deployment和act_re_procdef一对多关系,一次部署在流程部署表生成一条记录,但一次部署可以部署多个流程定义,每个流程定义在流程定义表生成一条记录。每一个流程定义在act_ge_bytearray会存在两个资源记录,bpmn和png。

建议:一次部署一个流程,这样部署表和流程定义表是一对一有关系,方便读取流程部署及流程定义信息。

6.3 启动流程实例

流程定义部署在activiti后就可以通过工作流管理业务流程了,也就是说上边部署的出差申请流程可以使用了。

针对该流程,启动一个流程表示发起一个新的出差申请单,这就相当于java类与java对象的关系,类定义好后需要new创建一个对象使用,当然可以new多个对象。对于请出差申请流程,张三发起一个出差申请单需要启动一个流程实例,出差申请单发起一个出差单也需要启动一个流程实例。

代码如下:

    /**
     * 启动流程实例
     */
    @Test
    public void testStartProcess(){
//        1、创建ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、获取RunTimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
//        3、根据流程定义Id启动流程
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey("myEvection");
//        输出内容
        System.out.println("流程定义id:" + processInstance.getProcessDefinitionId());
        System.out.println("流程实例id:" + processInstance.getId());
        System.out.println("当前活动Id:" + processInstance.getActivityId());
    }

输出内容如下:

操作数据表

act_hi_actinst 流程实例执行历史

act_hi_identitylink 流程的参与用户历史信息

act_hi_procinst 流程实例历史信息

act_hi_taskinst 流程任务历史信息

act_ru_execution 流程执行信息

act_ru_identitylink 流程的参与用户信息

act_ru_task 任务信息

6.4 任务查询

流程启动后,任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务。

/**
     * 查询当前个人待执行的任务
      */
    @Test
    public void testFindPersonalTaskList() {
//        任务负责人
        String assignee = "zhangsan";
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        创建TaskService
        TaskService taskService = processEngine.getTaskService();
//        根据流程key 和 任务负责人 查询任务
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("myEvection") //流程Key
                .taskAssignee(assignee)//只查询该任务负责人的任务
                .list();

        for (Task task : list) {

            System.out.println("流程实例id:" + task.getProcessInstanceId());
            System.out.println("任务id:" + task.getId());
            System.out.println("任务负责人:" + task.getAssignee());
            System.out.println("任务名称:" + task.getName());

        }
    }

输出结果如下:

流程实例id:2501
任务id:2505
任务负责人:zhangsan
任务名称:创建出差申请

6.5

任务负责人查询待办任务,选择任务进行处理,完成任务。

// 完成任务
    @Test
    public void completTask(){
//        获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取taskService
        TaskService taskService = processEngine.getTaskService();

//        根据流程key 和 任务的负责人 查询任务
//        返回一个任务对象
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("myEvection") //流程Key
                .taskAssignee("zhangsan")  //要查询的负责人
                .singleResult();

//        完成任务,参数:任务id
        taskService.complete(task.getId());
    }

6.6 流程定义信息查询

查询流程相关信息,包含流程定义,流程部署,流程定义版本

    /**
     * 查询流程定义
     */
    @Test
    public void queryProcessDefinition(){
        //        获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        得到ProcessDefinitionQuery 对象
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
//          查询出当前所有的流程定义
//          条件:processDefinitionKey =evection
//          orderByProcessDefinitionVersion 按照版本排序
//        desc倒叙
//        list 返回集合
        List<ProcessDefinition> definitionList = processDefinitionQuery.processDefinitionKey("myEvection")
                .orderByProcessDefinitionVersion()
                .desc()
                .list();
//      输出流程定义信息
        for (ProcessDefinition processDefinition : definitionList) {
            System.out.println("流程定义 id="+processDefinition.getId());
            System.out.println("流程定义 name="+processDefinition.getName());
            System.out.println("流程定义 key="+processDefinition.getKey());
            System.out.println("流程定义 Version="+processDefinition.getVersion());
            System.out.println("流程部署ID ="+processDefinition.getDeploymentId());
        }

    }

输出结果:

流程定义id:myEvection:1:4
流程定义名称:出差申请单
流程定义key:myEvection
流程定义版本:1

6.7 流程删除

public void deleteDeployment() {
		// 流程部署id
		String deploymentId = "1";
		
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 通过流程引擎获取repositoryService
		RepositoryService repositoryService = processEngine
				.getRepositoryService();
		//删除流程定义,如果该流程定义已有流程实例启动则删除时出错
		repositoryService.deleteDeployment(deploymentId);
		//设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程
		//repositoryService.deleteDeployment(deploymentId, true);
	}

说明:

  1. 使用repositoryService删除流程定义,历史表信息不会被删除

  2. 如果该流程定义下没有正在运行的流程,则可以用普通删除。

如果该流程定义下存在已经运行的流程,使用普通删除报错,可用级联删除方法将流程及相关记录全部删除。

先删除没有完成流程节点,最后就可以完全删除流程定义信息

项目开发中级联删除操作一般只开放给超级管理员使用.

6.8 流程资源下载

现在我们的流程资源文件已经上传到数据库了,如果其他用户想要查看这些资源文件,可以从数据库中把资源文件下载到本地。

解决方案有:

1、jdbc对blob类型,clob类型数据读取出来,保存到文件目录

2、使用activiti的api来实现

使用commons-io.jar 解决IO的操作

引入commons-io依赖包

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

通过流程定义对象获取流程定义资源,获取bpmn和png

import org.apache.commons.io.IOUtils;

@Test
    public void deleteDeployment(){
//        获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        根据部署id 删除部署信息,如果想要级联删除,可以添加第二个参数,true
        repositoryService.deleteDeployment("1");
    }

    public void  queryBpmnFile() throws IOException {
//        1、得到引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、获取repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        3、得到查询器:ProcessDefinitionQuery,设置查询条件,得到想要的流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey("myEvection")
                .singleResult();
//        4、通过流程定义信息,得到部署ID
        String deploymentId = processDefinition.getDeploymentId();
//        5、通过repositoryService的方法,实现读取图片信息和bpmn信息
//        png图片的流
        InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
//        bpmn文件的流
        InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
//        6、构造OutputStream流
        File file_png = new File("d:/evectionflow01.png");
        File file_bpmn = new File("d:/evectionflow01.bpmn");
        FileOutputStream bpmnOut = new FileOutputStream(file_bpmn);
        FileOutputStream pngOut = new FileOutputStream(file_png);
//        7、输入流,输出流的转换
        IOUtils.copy(pngInput,pngOut);
        IOUtils.copy(bpmnInput,bpmnOut);
//        8、关闭流
        pngOut.close();
        bpmnOut.close();
        pngInput.close();
        bpmnInput.close();
    }

说明:

  1. deploymentId为流程部署ID

  2. resource_name为act_ge_bytearray表中NAME_列的值

  3. 使用repositoryService的getDeploymentResourceNames方法可以获取指定部署下得所有文件的名称

  4. 使用repositoryService的getResourceAsStream方法传入部署ID和资源图片名称可以获取部署下指定名称文件的输入流

最后的将输入流中的图片资源进行输出。

6.9 流程历史信息的查看

即使流程定义已经删除了,流程执行的历史信息通过前面的分析,依然保存在activiti的act_hi_*相关的表中。所以我们还是可以查询流程执行的历史信息,可以通过HistoryService来查看相关的历史记录。

    /**
     * 查看历史信息
     */
    @Test
    public void findHistoryInfo(){
//      获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取HistoryService
        HistoryService historyService = processEngine.getHistoryService();
//        获取 actinst表的查询对象
        HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
//        查询 actinst表,条件:根据 InstanceId 查询
//        instanceQuery.processInstanceId("2501");
//        查询 actinst表,条件:根据 DefinitionId 查询
        instanceQuery.processDefinitionId("myEvection:1:4");
//        增加排序操作,orderByHistoricActivityInstanceStartTime 根据开始时间排序 asc 升序
        instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
//        查询所有内容
        List<HistoricActivityInstance> activityInstanceList = instanceQuery.list();
//        输出
        for (HistoricActivityInstance hi : activityInstanceList) {
            System.out.println(hi.getActivityId());
            System.out.println(hi.getActivityName());
            System.out.println(hi.getProcessDefinitionId());
            System.out.println(hi.getProcessInstanceId());
            System.out.println("<==========================>");
        }
    }

七.服务任务节点

概念及使用场景

下一个任务我须要自动执行一些操做,而且这个节点不须要任何的人工干涉,也就是说这个节点是自动化的。那么,这个当前面一个经办人员把任务发送下去的时候,天然而然的下一个节点就会开始立刻执行。这个时候。咱们就须要使用Activiti工做流的ServiceTask任务

实现步骤

(1)编写委托类

public class ServiceTask implements JavaDelegate{
 //流程变量 
 private Expression text1;

 //重写委托的提交方法
 @Override
 public void execute(DelegateExecution execution) throws Exception {
 	System.out.println("serviceTask已经执行!"); 
 	String value1 = (String) text1.getValue(execution);
 	System.out.println(value1);
    execution.setVariable("var1", new StringBuffer(value1).reverse().toString());
 	}
 }

(2)把委托类设置到流程的监听类上面

Activiti进阶篇

一、流程实例

什么是流程实例

流程实例(ProcessInstance)代表流程定义的执行实例。

一个流程实例包括了所有的运行节点。我们可以利用这个对象来了解当前流程实例的进度等信息。

例如:用户或程序按照流程定义内容发起一个流程,这就是一个流程实例。

流程定义和流程实例的图解:

启动流程实例 并添加Businesskey(业务标识)

流程定义部署在activiti后,就可以在系统中通过activiti去管理该流程的执行,执行流程表示流程的一次执行。

比如部署系统出差流程后,如果某用户要申请出差这时就需要执行这个流程,如果另外一个用户也要申请出差则也需要执行该流程,每个执行互不影响,每个执行是单独的流程实例。

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

Businesskey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据。

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

    /**
     * 启动流程实例,添加businessKey
     */
    @Test
    public void addBusinessKey(){
//        1、得到ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、得到RunTimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
//        3、启动流程实例,同时还要指定业务标识businessKey,也就是出差申请单id,这里是1001
        ProcessInstance processInstance = runtimeService.
                startProcessInstanceByKey("myEvection","1001");
//        4、输出processInstance相关属性
        System.out.println("业务id=="+processInstance.getBusinessKey());

    }

Activiti的act_ru_execution中存储业务标识:

操作数据库表

启动流程实例,操作如下数据库表:

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 #活动历史表,记录所有活动

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

查询流程实例

流程在运行过程中可以查询流程实例的状态,当前运行结点等信息。

@Test
	public void queryProcessInstance() {
		// 流程定义key
		String processDefinitionKey = "evection";
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
		// 获取RunTimeService
		RuntimeService runtimeService = processEngine.getRuntimeService();
		List<ProcessInstance> list = runtimeService
				.createProcessInstanceQuery()
				.processDefinitionKey(processDefinitionKey)//
				.list();

		for (ProcessInstance processInstance : list) {
			System.out.println("----------------------------");
			System.out.println("流程实例id:"
					+ processInstance.getProcessInstanceId());
			System.out.println("所属流程定义id:"
					+ processInstance.getProcessDefinitionId());
			System.out.println("是否执行完成:" + processInstance.isEnded());
			System.out.println("是否暂停:" + processInstance.isSuspended());
			System.out.println("当前活动标识:" + processInstance.getActivityId());
		}
	}

关联BusinessKey

需求:

在activiti实际应用时,查询流程实例列表时可能要显示出业务系统的一些相关信息,比如:查询当前运行的出差流程列表需要将出差单名称、出差天数等信息显示出来,出差天数等信息在业务系统中存在,而并没有在activiti数据库中存在,所以是无法通过activiti的api查询到出差天数等信息。

实现:

在查询流程实例时,通过businessKey(业务标识 )关联查询业务系统的出差单表,查询出出差天数等信息。

通过下面的代码就可以获取activiti中所对应实例保存的业务Key。而这个业务Key一般都会保存相关联的业务操作表的主键,再通过主键ID去查询业务信息,比如通过出差单的ID,去查询更多的请假信息(出差人,出差时间,出差天数,出差目的地等)

String businessKey = processInstance.getBusinessKey();

在activiti的act_ru_execution表,字段BUSINESS_KEY就是存放业务KEY的。

挂起、激活流程实例

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

全部流程实例挂起

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

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

/**
     * 全部流程实例挂起与激活
     */
    @Test
    public void SuspendAllProcessInstance(){
//        获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        查询流程定义的对象
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().
                processDefinitionKey("myEvection").
                singleResult();
//        得到当前流程定义的实例是否都为暂停状态
        boolean suspended = processDefinition.isSuspended();
//        流程定义id
        String processDefinitionId = processDefinition.getId();
//        判断是否为暂停
        if(suspended){
//         如果是暂停,可以执行激活操作 ,参数1 :流程定义id ,参数2:是否激活,参数3:激活时间
            repositoryService.activateProcessDefinitionById(processDefinitionId,
                    true,
                    null
            );
            System.out.println("流程定义:"+processDefinitionId+",已激活");
        }else{
//          如果是激活状态,可以暂停,参数1 :流程定义id ,参数2:是否暂停,参数3:暂停时间
            repositoryService.suspendProcessDefinitionById(processDefinitionId,
                    true,
                    null);
            System.out.println("流程定义:"+processDefinitionId+",已挂起");
        }

    }

单个流程实例挂起

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

/**
     * 单个流程实例挂起与激活
     */
    @Test
    public void SuspendSingleProcessInstance(){
//        获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        RuntimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
//        查询流程定义的对象
        ProcessInstance processInstance = runtimeService.
                createProcessInstanceQuery().
                processInstanceId("15001").
                singleResult();
//        得到当前流程定义的实例是否都为暂停状态
        boolean suspended = processInstance.isSuspended();
//        流程定义id
        String processDefinitionId = processInstance.getId();
//        判断是否为暂停
        if(suspended){
//         如果是暂停,可以执行激活操作 ,参数:流程定义id
            runtimeService.activateProcessInstanceById(processDefinitionId);
            System.out.println("流程定义:"+processDefinitionId+",已激活");
        }else{
//          如果是激活状态,可以暂停,参数:流程定义id
            runtimeService.suspendProcessInstanceById( processDefinitionId);
            System.out.println("流程定义:"+processDefinitionId+",已挂起");
        }

    }

    /**
     * 测试完成个人任务
     */
    @Test
    public void completTask(){
//        获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取操作任务的服务 TaskService
        TaskService taskService = processEngine.getTaskService();
//        完成任务,参数:流程实例id,完成zhangsan的任务
        Task task = taskService.createTaskQuery()
                .processInstanceId("15001")
                .taskAssignee("rose")
                .singleResult();


        System.out.println("流程实例id="+task.getProcessInstanceId());
        System.out.println("任务Id="+task.getId());
        System.out.println("任务负责人="+task.getAssignee());
        System.out.println("任务名称="+task.getName());
        taskService.complete(task.getId());
    }

二、个人任务

2.1、分配任务负责人

2.1.1、固定分配

在进行业务流程建模时指定固定的任务负责人, 如图:

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

2.1.2、表达式分配

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

2.1.2.1、UEL 表达式

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

1)UEL-value 定义

如图:

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

或者使用这种方式定义:

如图:

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

2)UEL-method 方式

如图:

userBean 是 spring 容器中的一个 bean,表示调用该 bean 的 getUserId()方法。

3)UEL-method 与 UEL-value 结合

再比如: ${ldapService.findManagerForEmployee(emp)} ldapService 是 spring 容器的一个 bean,findManagerForEmployee 是该 bean 的一个方法,emp 是 activiti 流程变量, emp 作为参数传到 ldapService.findManagerForEmployee 方法中。

4)其它

表达式支持解析基础类型、 bean、 list、 array 和 map,也可作为条件判断。 如下: ${order.price > 100 && order.price < 250}

2.1.2.2、编写代码配置负责人
1)定义任务分配流程变量

如图:

2)设置流程变量

在启动流程实例时设置流程变量,如下:

 /**
     * 设置流程负责人
     */
    @Test
    public void assigneeUEL(){
//      获取流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取 RuntimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
//        设置assignee的取值,用户可以在界面上设置流程的执行
        Map<String,Object> assigneeMap = new HashMap<>();
        assigneeMap.put("assignee0","张三");
        assigneeMap.put("assignee1","李经理");
        assigneeMap.put("assignee2","王总经理");
        assigneeMap.put("assignee3","赵财务");
//        启动流程实例,同时还要设置流程定义的assignee的值
        runtimeService.startProcessInstanceByKey("myEvection1",assigneeMap);
//       输出
        System.out.println(processEngine.getName());
    }

执行成功后,可以在act_ru_variable表中看到刚才map中的数据

2.1.2.3、注意事项

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

2.1.3、监听器分配

可以使用监听器来完成很多Activiti流程的业务。

在本章我们使用监听器的方式来指定负责人,那么在流程设计时就不需要指定assignee。

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

Event的选项包含:

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

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

public class MyTaskListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        if(delegateTask.getName().equals("创建出差申请")&&
                delegateTask.getEventName().equals("create")){
            //这里指定任务负责人
            delegateTask.setAssignee("张三");
        }
    }
} 

DelegateTask对象的内容如下:

2.1.3.1、注意事项

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

2.2、查询任务

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

代码如下:

// 查询当前个人待执行的任务
@Test
public void findPersonalTaskList() {
    // 流程定义key
    String processDefinitionKey = "myEvection1";
    // 任务负责人
    String assignee = "张三";
    // 获取TaskService
    TaskService taskService = processEngine.getTaskService();
    List<Task> taskList = taskService.createTaskQuery()
    	.processDefinitionKey(processDefinitionKey)
    	.includeProcessVariables()
        .taskAssignee(assignee)
        .list();
    for (Task task : taskList) {
        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

需求: 在 activiti 实际应用时,查询待办任务可能要显示出业务系统的一些相关信息。

比如:查询待审批出差任务列表需要将出差单的日期、 出差天数等信息显示出来。

出差天数等信息在业务系统中存在,而并没有在 activiti 数据库中存在,所以是无法通过 activiti 的 api 查询到出差天数等信息。 实现: 在查询待办任务时,通过 businessKey(业务标识 )关联查询业务系统的出差单表,查询出出差天数等信息。

@Test
    public void findProcessInstance(){
//        获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取TaskService
        TaskService taskService = processEngine.getTaskService();
//        获取RuntimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
//        查询流程定义的对象
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("myEvection1")
                .taskAssignee("张三")
                .singleResult();
//        使用task对象获取实例id
        String processInstanceId = task.getProcessInstanceId();
//          使用实例id,获取流程实例对象
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
//        使用processInstance,得到 businessKey
        String businessKey = processInstance.getBusinessKey();

        System.out.println("businessKey=="+businessKey);

    }

2.3、办理任务

注意:在实际应用中,完成任务前需要校验任务的负责人是否具有该任务的办理权限 。

/**
     * 完成任务,判断当前用户是否有权限
     */
    @Test
    public void completTask() {
        //任务id
        String taskId = "15005";
//        任务负责人
        String assingee = "张三";
        //获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 创建TaskService
        TaskService taskService = processEngine.getTaskService();
//        完成任务前,需要校验该负责人可以完成当前任务
//        校验方法:
//        根据任务id和任务负责人查询当前任务,如果查到该用户有权限,就完成
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .taskAssignee(assingee)
                .singleResult();
        if(task != null){
            taskService.complete(taskId);
            System.out.println("完成任务");
        }
    }

三、流程变量

3.1、什么是流程变量

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

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

3.2、流程变量类型

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

3.3、流程变量作用域

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

3.3.1、globa变量

流程变量的默认作用域是流程实例。当一个流程变量的作用域为流程实例时,可以称为 global 变量

注意:

如: Global变量:userId(变量名)、zhangsan(变量值)

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

3.3.2、local变量

任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大, 称为 local 变量。

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

3.4、流程变量的使用方法

3.4.1、在属性上使用UEL表达式

可以在 assignee 处设置 UEL 表达式,表达式的值为任务的负责人,比如: ${assignee}, assignee 就是一个流程变量名称。

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

3.4.2、在连线上使用UEL表达式

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

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

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

3.5、使用Global变量控制流程

3.5.1、需求

员工创建出差申请单,由部门经理审核,部门经理审核通过后出差3天及以下由人财务直接审批,3天以上先由总经理审核,总经理审核通过再由财务审批。

3.5.2、流程定义

1)、出差天数大于等于3连线条件

也可以使用对象参数命名,如evection.num:

2)、出差天数小于3连线条件

也可以使用对象参数命名,如:

3.5.3、设置global流程变量

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

在设置流程变量时,可以在启动流程时设置,也可以在任务办理时设置

3.5.3.1、创建POJO对象

创建出差申请pojo对象

package com.itheima.demo.pojo;

import java.io.Serializable;
import java.util.Date;

/**
 * 出差申请 pojo
 */
public class Evection implements Serializable {
    /**
     * 主键id
     */
    private Long id;
    /**
     * 出差申请单名称
     */
    private String evectionName;
    /**
     * 出差天数
     */
    private Double num;
    /**
     * 预计开始时间
     */
    private Date beginDate;
    /**
     * 预计结束时间
     */
    private Date endDate;
    /**
     * 目的地
     */
    private String destination;
    /**
     * 出差事由
     */
    private String reson;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getEvectionName() {
        return evectionName;
    }

    public void setEvectionName(String evectionName) {
        this.evectionName = evectionName;
    }

    public Date getBeginDate() {
        return beginDate;
    }

    public void setBeginDate(Date beginDate) {
        this.beginDate = beginDate;
    }

    public Date getEndDate() {
        return endDate;
    }

    public void setEndDate(Date endDate) {
        this.endDate = endDate;
    }

    public String getDestination() {
        return destination;
    }

    public void setDestination(String destination) {
        this.destination = destination;
    }

    public String getReson() {
        return reson;
    }

    public void setReson(String reson) {
        this.reson = reson;
    }

    public Double getNum() {
        return num;
    }

    public void setNum(Double num) {
        this.num = num;
    }
}

3.5.3.2、启动流程时设置变量

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

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

    /**
     * 启动流程实例,设置流程变量的值
     */
    @Test
    public void startProcess(){
//        获取流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取RunTimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
//        流程定义key
        String key = "myEvection2";
//       创建变量集合
        Map<String, Object> map = new HashMap<>();
//        创建出差pojo对象
        Evection evection = new Evection();
//        设置出差天数
        evection.setNum(2d);
//      定义流程变量,把出差pojo对象放入map
        map.put("evection",evection);
//      设置assignee的取值,用户可以在界面上设置流程的执行
        map.put("assignee0","张三");
        map.put("assignee1","李经理");
        map.put("assignee2","王总经理");
        map.put("assignee3","赵财务");
//        启动流程实例,并设置流程变量的值(把map传入)
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey(key, map);
//      输出
        System.out.println("流程实例名称="+processInstance.getName());
        System.out.println("流程定义id=="+processInstance.getProcessDefinitionId());

    }
    /**
     * 完成任务,判断当前用户是否有权限
     */
    @Test
    public void completTask() {
        //任务id
        String key = "myEvection2";
//        任务负责人
        String assingee = "张三";
        //获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 创建TaskService
        TaskService taskService = processEngine.getTaskService();
//        完成任务前,需要校验该负责人可以完成当前任务
//        校验方法:
//        根据任务id和任务负责人查询当前任务,如果查到该用户有权限,就完成
        Task task = taskService.createTaskQuery()
                .processDefinitionKey(key)
                .taskAssignee(assingee)
                .singleResult();
        if(task != null){
            taskService.complete(task.getId());
            System.out.println("任务执行完成");
        }
    }

说明:

startProcessInstanceByKey(processDefinitionKey, variables)

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

3.5.3.2、任务办理时设置变量

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

这里需要在创建出差单任务完成时设置流程变量

    /**
     * 完成任务,判断当前用户是否有权限
     */
    @Test
    public void completTask() {
        //任务id
        String key = "myEvection2";
//        任务负责人
        String assingee = "张三";
//       获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//       创建TaskService
        TaskService taskService = processEngine.getTaskService();
//       创建变量集合
        Map<String, Object> map = new HashMap<>();
//        创建出差pojo对象
        Evection evection = new Evection();
//        设置出差天数
        evection.setNum(2d);
//      定义流程变量
        map.put("evection",evection);
//        完成任务前,需要校验该负责人可以完成当前任务
//        校验方法:
//        根据任务id和任务负责人查询当前任务,如果查到该用户有权限,就完成
        Task task = taskService.createTaskQuery()
                .processDefinitionKey(key)
                .taskAssignee(assingee)
                .singleResult();
        if(task != null){
            //完成任务是,设置流程变量的值
            taskService.complete(task.getId(),map);
            System.out.println("任务执行完成");
        }
    }

说明:

通过当前任务设置流程变量,需要指定当前任务id,如果当前执行的任务id不存在则抛出异常。

任务办理时也是通过map<key,value>设置流程变量,一次可以设置多个变量。

3.5.3.3、通过当前流程实例设置

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

    @Test
    public void setGlobalVariableByExecutionId(){
//    当前流程实例执行 id,通常设置为当前执行的流程实例
        String executionId="2601";
//     获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取RuntimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
//        创建出差pojo对象
        Evection evection = new Evection();
//        设置天数
        evection.setNum(3d);
//      通过流程实例 id设置流程变量
        runtimeService.setVariable(executionId, "evection", evection);
//      一次设置多个值
//      runtimeService.setVariables(executionId, variables)
    }

注意:

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

3.5.3.4、通过当前任务设置
@Test
	public void setGlobalVariableByTaskId(){
		
		//当前待办任务id
		String taskId="1404";
//     获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
		TaskService taskService = processEngine.getTaskService();
		Evection evection = new Evection();
		evection.setNum(3);
		//通过任务设置流程变量
		taskService.setVariable(taskId, "evection", evection);
		//一次设置多个值 
		//taskService.setVariables(taskId, variables)
	}

注意:

任务id必须是当前待办任务id,act_ru_task中存在。如果该任务已结束,会报错

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

3.5.4、测试

正常测试:

设置流程变量的值大于等于3天

设计流程变量的值小于3天

异常测试:

流程变量不存在

流程变量的值为空NULL,price属性为空

UEL表达式都不符合条件

不设置连线的条件

3.5.5、注意事项

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

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

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

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

3.5.6、操作数据库表

设置流程变量会在当前执行流程变量表插入记录,同时也会在历史流程变量表也插入记录。

//当前流程变量表
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变量

字段意义参考当前流程变量表。

3.6、设置local流程变量

3.6.1、任务办理时设置

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

/*
*处理任务时设置local流程变量
*/
@Test
public void completTask() {
   //任务id
   String taskId = "1404";
//  获取processEngine
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
//  定义流程变量
   Map<String, Object> variables = new HashMap<String, Object>();
   Evection evection = new Evection ();
   evection.setNum(3d);
// 定义流程变量
   Map<String, Object> variables = new HashMap<String, Object>();
//  变量名是holiday,变量值是holiday对象
    variables.put("evection", evection);
//  设置local变量,作用域为该任务
    taskService.setVariablesLocal(taskId, variables);
//  完成任务
   taskService.complete(taskId);
}

说明:

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

3.6.2、通过当前任务设置
@Test
public void setLocalVariableByTaskId(){
//   当前待办任务id
    String taskId="1404";
//  获取processEngine
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    Evection evection = new Evection ();
    evection.setNum(3d);
//  通过任务设置流程变量
    taskService.setVariableLocal(taskId, "evection", evection);
//  一次设置多个值 
    //taskService.setVariablesLocal(taskId, variables)
}

注意:

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

3.6.3、 Local变量测试1

如果上边例子中设置global变量改为设置local变量是否可行?为什么?

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

3.6.4、 Local变量测试2

在部门经理审核、总经理审核、财务审核时设置local变量,可通过historyService查询每个历史任务时将流程变量的值也查询出来。

代码如下:

// 创建历史任务查询对象
      HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery();
      // 查询结果包括 local变量
      historicTaskInstanceQuery.includeTaskLocalVariables();
for (HistoricTaskInstance historicTaskInstance : list) {
         System.out.println("==============================");
         System.out.println("任务id:" + historicTaskInstance.getId());
         System.out.println("任务名称:" + historicTaskInstance.getName());
         System.out.println("任务负责人:" + historicTaskInstance.getAssignee());
     System.out.println("任务local变量:"+ historicTaskInstance.getTaskLocalVariables());

}

注意:查询历史流程变量,特别是查询pojo变量需要经过反序列化,不推荐使用。

四、组任务

4.1、需求

在流程定义中在任务结点的 assignee 固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn 文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。

针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务。

4.2、设置任务候选人

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

查看bpmn文件

<userTask activiti:candidateUsers="lisi,wangwu" activiti:exclusive="true" id="_3" name="经理审批"/>

我们可以看到部门经理的审核人已经设置为 lisi,wangwu 这样的一组候选人,可以使用

activiti:candiateUsers=”用户 1,用户 2,用户 3”的这种方式来实现设置一组候选人

4.3、组任务

4.3.1、组任务办理流程
a、查询组任务

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

候选人不能立即办理任务。

b、拾取(claim)任务

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

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

如果拾取后不想办理该任务?

需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。

c、查询个人任务

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

d、办理个人任务
4.3.2、 查询组任务

根据候选人查询组任务

@Test
    public void findGroupTaskList() {
       // 流程定义key
       String processDefinitionKey = "evection3";
       // 任务候选人
       String candidateUser = "lisi";
        //  获取processEngine
       ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
       // 创建TaskService
       TaskService taskService = processEngine.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());
       }
    }

4.3.3 、 拾取组任务

候选人员拾取组任务后该任务变为自己的个人任务。

@Test
    public void claimTask(){
         //  获取processEngine
       ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
       TaskService taskService = processEngine.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("任务拾取成功");
       }
    }

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

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

4.3.4、 查询个人待办任务

查询方式同个人任务查询

@Test
public void findPersonalTaskList() {
    // 流程定义key
    String processDefinitionKey = "evection1";
    // 任务负责人
    String assignee = "zhangsan";
     //  获取processEngine
       ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 创建TaskService
    TaskService taskService = processEngine.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());
    }
}
4.3.5、 办理个人任务

同个人任务办理

 /*完成任务*/
   @Test
   public void completeTask(){
//     任务ID
      String taskId = "12304";
//     获取processEngine
      ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
      processEngine.getTaskService()
                   .complete(taskId);
      System.out.println("完成任务:"+taskId);
   }

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

4.3.6、 归还组任务

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

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

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

也可以通过setAssignee方法将任务委托给其它用户负责,注意被委托的用户可以不是候选人(建议不要这样使用)

4.3.7、 任务交接

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

@Test
    public void setAssigneeToCandidateUser() {
        //  获取processEngine
       ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
       // 查询任务使用TaskService
       TaskService taskService = processEngine.getTaskService();
       // 当前待办任务
       String taskId = "6004";
       // 任务负责人
       String userId = "zhangsan2";
// 将此任务交给其它候选人办理该 任务
           String candidateuser = "zhangsan";
       // 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
       Task task = taskService
           .createTaskQuery()
           .taskId(taskId)
           .taskAssignee(userId)
           .singleResult();
       if (task != null) {
           taskService.setAssignee(taskId, candidateuser);
       }
    }

4.3.8、 数据库表操作

查询当前任务执行表

SELECT * FROM act_ru_task 

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

查询任务参与者

SELECT * FROM act_ru_identitylink

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

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

五、网关

网关用来控制流程的流向

5.1 排他网关ExclusiveGateway

5.1.1 什么是排他网关:

排他网关,用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支,

注意:排他网关只会选择一个为true的分支执行。如果有两个分支条件都为true,排他网关会选择id值较小的一条分支去执行。

为什么要用排他网关?

不用排他网关也可以实现分支,如:在连线的condition条件上设置分支条件。

在连线设置condition条件的缺点:如果条件都不满足,流程就结束了(是异常结束)。

如果 使用排他网关决定分支的走向,如下:

如果从网关出去的线所有条件都不满足则系统抛出异常。

org.activiti.engine.ActivitiException: No outgoing sequence flow of the exclusive gateway 'exclusivegateway1' could be selected for continuing the process
   at org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.leave(ExclusiveGatewayActivityBehavior.java:85)

5.1.2 流程定义

排他网关图标,红框内:

5.1.3 测试

在部门经理审核后,走排他网关,从排他网关出来的分支有两条,一条是判断出差天数是否大于3天,另一条是判断出差天数是否小于等于3天。

设置分支条件时,如果所有分支条件都不是true,报错:

org.activiti.engine.ActivitiException: No outgoing sequence flow of the exclusive gateway 'exclusivegateway1' could be selected for continuing the process

       at org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.leave(ExclusiveGatewayActivityBehavior.java:85)

5.2 并行网关ParallelGateway

5.2.1 什么是并行网关

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

l fork分支:

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

l join汇聚:

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

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

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

例子:

说明:

技术经理和项目经理是两个execution分支,在act_ru_execution表有两条记录分别是技术经理和项目经理,act_ru_execution还有一条记录表示该流程实例。

待技术经理和项目经理任务全部完成,在汇聚点汇聚,通过parallelGateway并行网关。

并行网关在业务应用中常用于会签任务,会签任务即多个参与者共同办理的任务。

5.2.2 流程定义

并行网关图标,红框内:

5.2.3 测试

当执行到并行网关数据库跟踪如下:

当前任务表:SELECT * FROM act_ru_task

上图中:有两个任务当前执行。

查询流程实例执行表:SELECT * FROM act_ru_execution

上图中,说明当前流程实例有多个分支(两个)在运行。

对并行任务的执行:

并行任务执行不分前后,由任务的负责人去执行即可。

执行技术经理任务后,查询当前任务表 SELECT * FROM act_ru_task

已完成的技术经理任务在当前任务表act_ru_task_已被删除。

在流程实例执行表:SELECT * FROM act_ru_execution有中多个分支存在且有并行网关的汇聚结点。

有并行网关的汇聚结点:说明有一个分支已经到汇聚,等待其它的分支到达。

当所有分支任务都完成,都到达汇聚结点后:

流程实例执行表:SELECT * FROM act_ru_execution,执行流程实例已经变为总经理审批,说明流程执行已经通过并行网关

总结:所有分支到达汇聚结点,并行网关执行完成。

5.3 包含网关InclusiveGateway

5.3.1 什么是包含网关

包含网关可以看做是排他网关和并行网关的结合体。

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

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

l 分支:

所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。

l 汇聚:

所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。

5.3.2 流程定义:

出差申请大于等于3天需要由项目经理审批,小于3天由技术经理审批,出差申请必须经过人事经理审批。

包含网关图标,红框内:

定义流程:

注意:通过包含网关的每个分支的连线上设置condition条件。

5.3.3 测试

如果包含网关设置的条件中,流程变量不存在,报错;

org.activiti.engine.ActivitiException: Unknown property used in expression: ${evection.num>=3}

需要在流程启动时设置流程变量evection.num。

1)、当流程执行到第一个包含网关后,会根据条件判断,当前要走哪几个分支:

流程实例执行表:SELECT * FROM act_ru_execution

第一条记录:包含网关分支。

后两条记录代表两个要执行的分支:

ACT_ID = "_13" 代表 项目经理神品

ACT_ID = "_5" 代表 人事经理审批

当前任务表:ACT_RU_TASK

上图中,项目经理审批、人事经理审批 都是当前的任务,在并行执行。

如果有一个分支执行先走到汇聚结点的分支,要等待其它执行分支走到汇聚。

2)、先执行项目经理审批,然后查询当前任务表:ACT_RU_TASK

当前任务还有人事经理审批需要处理。

流程实例执行表:SELECT * FROM act_ru_execution

发现人事经理的分支还存在,而项目经理分支已经走到ACT_ID = _18的节点。而ACT_ID=__18就是第二个包含网关

这时,因为有2个分支要执行,包含网关会等所有分支走到汇聚才能执行完成。

3)、执行人事经理审批

然后查询当前任务表:ACT_RU_TASK

当前任务表已经不是人事经理审批了,说明人事经理审批已经完成。

流程实例执行表:SELECT * FROM act_ru_execution

包含网关执行完成,分支和汇聚就从act_ru_execution删除。

小结:在分支时,需要判断条件,符合条件的分支,将会执行,符合条件的分支最终才进行汇聚。

5.4 事件网关EventGateway

事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获事件。 当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。与此同时,会为每个外出顺序流创建相对的事件订阅。

事件网关的外出顺序流和普通顺序流不同,这些顺序流不会真的"执行", 相反它们让流程引擎去决定执行到事件网关的流程需要订阅哪些事件。 要考虑以下条件:

  1. 事件网关必须有两条或以上外出顺序流;

  2. 事件网关后,只能使用intermediateCatchEvent类型(activiti不支持基于事件网关后连接ReceiveTask)

  3. 连接到事件网关的中间捕获事件必须只有一个入口顺序流。

5.4.1流程定义

事件网关图标,红框内

intermediateCatchEvent:

intermediateCatchEvent支持的事件类型:

Message Event: 消息事件

Singal Event: 信号事件

Timer Event: 定时事件

使用事件网关定义流程:

六. 驳回功能实现思路

单个任务执行人:

通过流程变量控制, 在连线上设置一个流程变量用来控制任务的走向, 审核通过将变量值设置为0, 驳回操作的话设置为1

多个任务执行人:

获取当前节点的信息

取得当前节点的上一个节点的信息

保存当前节点的流向

新建流向,由当前节点指向上一个节点

将当前节点的流向设置为上面新建的流向

当前节点任务完成

将当前节点的流向还原

取得之前上个节点的执行人

设置上个节点的assignee为之前的执行人

/**
 *
 * 退回到上一节点
 *
 * @param task 当前任务
 */
public void backProcess(Task task) throws Exception {

    String processInstanceId = task.getProcessInstanceId();

    // 取得所有历史任务按时间降序排序
    List<HistoricTaskInstance> htiList = historyService.createHistoricTaskInstanceQuery()
            .processInstanceId(processInstanceId)
            .orderByTaskCreateTime()
            .desc()
            .list();

    if (ObjectUtils.isEmpty(htiList) || htiList.size() < 2) {
        return;
    }

    // list里的第二条代表上一个任务
    HistoricTaskInstance lastTask = htiList.get(1);

    // list里第二条代表当前任务
    HistoricTaskInstance curTask = htiList.get(0);

    // 当前节点的executionId
    String curExecutionId = curTask.getExecutionId();


    // 上个节点的taskId
    String lastTaskId = lastTask.getId();
    // 上个节点的executionId
    String lastExecutionId = lastTask.getExecutionId();

    if (null == lastTaskId) {
        throw new Exception("LAST TASK IS NULL");
    }

    String processDefinitionId = lastTask.getProcessDefinitionId();
    BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);

    String lastActivityId = null;
    List<HistoricActivityInstance> haiFinishedList = historyService.createHistoricActivityInstanceQuery()
            .executionId(lastExecutionId).finished().list();

    for (HistoricActivityInstance hai : haiFinishedList) {
        if (lastTaskId.equals(hai.getTaskId())) {
            // 得到ActivityId,只有HistoricActivityInstance对象里才有此方法
            lastActivityId = hai.getActivityId();
            break;
        }
    }

    // 得到上个节点的信息
    FlowNode lastFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(lastActivityId);


    // 取得当前节点的信息
    Execution execution = runtimeService.createExecutionQuery().executionId(curExecutionId).singleResult();
    String curActivityId = execution.getActivityId();
    FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(curActivityId);

    //记录当前节点的原活动方向
    List<SequenceFlow> oriSequenceFlows = new ArrayList<>();
    oriSequenceFlows.addAll(curFlowNode.getOutgoingFlows());

    //清理活动方向
    curFlowNode.getOutgoingFlows().clear();

    //建立新方向
    List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
    SequenceFlow newSequenceFlow = new SequenceFlow();
    newSequenceFlow.setId("newSequenceFlowId");
    newSequenceFlow.setSourceFlowElement(curFlowNode);
    newSequenceFlow.setTargetFlowElement(lastFlowNode);
    newSequenceFlowList.add(newSequenceFlow);
    curFlowNode.setOutgoingFlows(newSequenceFlowList);

    // 完成任务
    taskService.complete(task.getId());

    //恢复原方向
    curFlowNode.setOutgoingFlows(oriSequenceFlows);

    org.activiti.engine.task.Task nextTask = taskService
            .createTaskQuery().processInstanceId(processInstanceId).singleResult();

    // 设置执行人
    if(nextTask!=null) {
        taskService.setAssignee(nextTask.getId(), lastTask.getAssignee());
    }
}

七.表单

Activiti中的表单 Activiti提供了一种方便而且灵活的方式在业务流程中以手工方式添加表单 对表单的支持有2种方式: 通过表单属性对内置表单进行渲染 通过表单属性对外置表单进行渲染 表单属性 业务流程相关联的所有信息: 包含自身的流程变量 通过流程变量的引用 Activiti支持存储复杂的Java对象作为流程变量: 序列化对象 Jpa实体对象 整个XML文档作为字符串 用户是在启动一个流程和完成用户任务时,与流程进行交互 表单需要某个UI技术渲染之后才能够与用户进行交互 为了能够使用不同UI技术变得容易,流程定义包含一个对流程变量中复杂的Java类型对象到一个properties的Map<String,String> 类型的转换逻辑 使用Activiti API的方法查看公开的属性信息.然后,任意UI技术都能够在这些属性上面构建一个表单.该属性专门为流程变量提供了一个视图. 表单所需要显示的属性可以返回值FormData中获取: StartFormData FormService.getStartFormData(String processDefinitionId) 或者

TaskFormdata FormService.getTaskFormData(String taskId) 在默认情况下,内置的表单引擎遇到这些变量就像对待流程变量一样.如果任务表单属性和流程变量是一对一的关系,那么任务表单属性就不需要进行申明了: <startEvent id="start" /> 当执行到开始事件时,所有的流程变量都是可用的,但是 formService.getStartFormData(String processDefinitionId).getFormProperties() 会是一个空值,因为没有定义一个具体的映射

表单中所有被提交的属性都将会作为流程变量被存储在Activiti使用的数据库中. 这意味着在一个表单中新添加一个简单的input输入字段,也会作为一个新的变量被存储 属性来自于流程变量,但是不一定非要作为流程变量存储: 一个流程变量可能是JPA实体如类Address.在某种UI技术中使用的表单属性StreetName可能会关联到一个表达式 #{address.street} 用户提交的表单属性应该作为流程变量进行存储 使用UEL值表达式将其作为流程变量的一个嵌套属性进行存储 提交的表单属性默认的行为是作为流程变量进行存储,除非一个 formProperty 申明了其他的规则 类型转换也可以应用于表单数据和流程变量之间的处理:

<userTask id="task">
  <extensionElements>
    <activiti:formProperty id="room" />
    <activiti:formProperty id="duration" type="long"/>
    <activiti:formProperty id="speaker" variable="SpeakerName" writable="false" />
    <activiti:formProperty id="street" expression="#{address.street}" required="true" />
  </extensionElements>
</userTask>

表单属性room将会被映射为String类型流程变量room 表单属性duration将会被映射为java.lang.Long类型流程变量duration 表单属性speaker将会被映射为流程变量SpeakerName: writable="false" 只能够在TaskFormData对象中使用.如果属性speaker提交,将会抛出一个ActivitiException的异常 readable="false" 该属性就会在FormData进行排除,但是在提交后仍然会对其进行处理 表单属性street将会映射为Java Bean address的属性street作为String类型的流程变量: 当提交的表单属性并没有提供并且required="true" 时,那么就会抛出一个异常 表单数据也可以作为FormData的一部分提供类型元数据.该FormData可以从以下方法的返回值中获取:

StartFormData FormService.getStartFormData(String processDefinitionId)
TaskFormdata FormService.getTaskFormData(String taskId)

表单属性类型: string: org.activiti.engine.impl.form.StringFormType long: org.activiti.engine.impl.form.LongFormType enum: org.activiti.engine.impl.form.EnumFormType date: org.activiti.engine.impl.form.DateFormType boolean: org.activiti.engine.impl.form.BooleanFormType 对于声明每一个表单属性,FormProperty信息可以通过以下方式获取:

List<FormProperty> formService.getStartFormData(String processDefinitionId).getFormProperties()
或者
List<FormProperty> formService.getTaskFormData(String taskId).getFormProperties()
public interface FormProperty {
  /**

  the key used to submit the property in {@link FormService#submitStartFormData(String, java.util.Map)}

   * or {@link FormService#submitTaskFormData(String, java.util.Map)} */
     String getId();
       /** the display label */
       String getName();
       /** one of the types defined in this interface like e.g. {@link #TYPE_STRING} */
       FormType getType();
       /** optional value that should be used to display in this property */
       String getValue();
       /** is this property read to be displayed in the form and made accessible with the methods
   * {@link FormService#getStartFormData(String)} and {@link FormService#getTaskFormData(String)}. */
     boolean isReadable();
       /** is this property expected when a user submits the form? */
       boolean isWritable();
       /** is this property a required input field */
       boolean isRequired();
     }

示例:

<startEvent id="start">
  <extensionElements>
<activiti:formProperty id="speaker"
  name="Speaker"
  variable="SpeakerName"
  type="string" />

<activiti:formProperty id="start"
  type="date"
  datePattern="dd-MMM-yyyy" />

<activiti:formProperty id="direction" type="enum">
  <activiti:value id="left" name="Go Left" />
  <activiti:value id="right" name="Go Right" />
  <activiti:value id="up" name="Go Up" />
  <activiti:value id="down" name="Go Down" />
</activiti:formProperty>
  </extensionElements>
</startEvent>

所有的表单属性的信息都是可以通过API进行访问的: formProperty.getType().getName(): 获取类型的名称 formProperty.getType().getInformation("datePattern"): 获取日期的匹配方式 formProperty.getType().getInformation("values"): 可以获取到枚举值 Activiti控制台支持表单属性并且可以根据表单定义对表单进行渲染:

<startEvent ... >
  <extensionElements>
    <activiti:formProperty id="numberOfDays" name="Number of days" value="${numberOfDays}" type="long" required="true"/>
    <activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" value="${startDate}" datePattern="dd-MM-yyyy hh:mm" type="date" required="true" />
    <activiti:formProperty id="vacationMotivation" name="Motivation" value="${vacationMotivation}" type="string" />
  </extensionElements>
</userTask>

当使用Activiti控制台时,会被渲染成流程的启动表单

外置表单的渲染

Activiti中的API允许执行Activiti流程引擎之外的方式渲染任务表单,可以用自定义方式对任务表单进行渲染 所有需要渲染的表单属性进行装配的服务方法有两种:

StartFormData FormService.getStartFormData(String processDefinitionId)
TaskFormdata FormService.getTaskFormData(String taskId)

表单属性提交的两种方式:

ProcessInstance FormService.submitStartFormData(String processDefinitionId, Map<String,String> properties)
void FormService.submitStartFormData(String taskId, Map<String,String> properties)

可以将任何表单模版资源放进要部署的业务文档之中(如果想要按照流程的版本进行存储).将会在部署中作为一种可用的资源 获取部署表单模版的方式有两种:

String ProcessDefinition.getDeploymentId()
InputStream RepositoryService.getResourceAsStream(String deploymentId, String resourceName)

这样就可以获取表单模版定义文件,就可以在应用中渲染或者显示表单 也可以使用该功能获取任务表单之外的其他的部署资源用于其他的目的 属性 <userTask activiti:formKey="..." 暴露方式API:

String FormService.getStartFormData(String processDefinitionId).getFormKey()
String FormService.getTaskFormData(String taskId).getFormKey()

可以使用这个存储部署的模版中的全名(例如org/activiti/example/form/my-custom-form.xml) 但是这并不是必须的: 可以在表单属性中存储一个通用的key,然后运用一种算法或者换转去得到你实际使用的模版 当需要通过不同UI技术渲染不同的表单会更加方便: 使用正常屏幕大小的web应用程序的表单 移动手机小屏幕的表单 IM表单 email表单模版

activiti7中表单的使用方式
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(processBtnDto.getDeploymentId()).singleResult();
        ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId());
        Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
        //获取task对应的表单内容
        UserTask userTask = (UserTask)repositoryService.getBpmnModel(processDefinition.getId())
                .getFlowElement(task.getTaskDefinitionKey());
        List<FormProperty> formProperties = userTask.getFormProperties();

八.携带附件

activiti负责的是业务流程,至于审批过程中要上传的附件(不单指附件,也包括表单、地图、音视频等),都需要在页面里去写,就和平时写html一样,在把页面与activiti的流程实例ID,任务ID关联上。在后续的环节中展示或者修改。

public class GetAttachment {
 
    public static void main( String[] args ) throws Exception {
        // 获取流程引擎实例
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        // 获取任务服务组件
        TaskService taskService = engine.getTaskService();
        // 获取运行服务组件
        RuntimeService runtimeService = engine.getRuntimeService();
        // 流程存储服务组件
        RepositoryService repositoryService = engine.getRepositoryService();
        // 部署流程描述文件
        Deployment dep = repositoryService.createDeployment()
                .addClasspathResource("bpmn/vacation.bpmn").deploy();
        // 查找流程定义
        ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
                .deploymentId(dep.getId()).singleResult();
        // 启动流程
        ProcessInstance pi = runtimeService
                .startProcessInstanceById(pd.getId());
        // 查找任务
        Task task = taskService.createTaskQuery().processInstanceId(pi.getId())
                .singleResult();
        // 设置任务附件
        Attachment att1 = taskService.createAttachment("web url", task.getId(), pi.getId(), "Attachement1",
                "163 web page", "http://www.163.com");
        // 创建图片输入流
        InputStream is = new FileInputStream(new File("src/main/resources/artifact/result.png"));
        // 设置输入流为任务附件
        Attachment att2 = taskService.createAttachment("web url", task.getId(), pi.getId(), "Attachement2",
                "Image InputStream", is);
        // 根据流程实例ID查询附件
        List<Attachment> attas1 = taskService.getProcessInstanceAttachments(pi.getId());
        System.out.println("流程附件数量:" + attas1.size());
        // 根据任务ID查询附件
        List<Attachment> attas2 = taskService.getTaskAttachments(task.getId());
        System.out.println("任务附件数量:" + attas2.size());
        // 根据附件ID查询附件
        Attachment attResult = taskService.getAttachment(att1.getId());
        System.out.println("附件1名称:" + attResult.getName());
        // 根据附件ID查询附件内容
        InputStream stream1 = taskService.getAttachmentContent(att1.getId());
        System.out.println("附件1的输入流:" + stream1);
        InputStream stream2 = taskService.getAttachmentContent(att2.getId());
        System.out.println("附件2的输入流:" + stream2);
    }
}

九.会签

  1. 概念

    在流程业务管理中,任务是通常都是由一个人去处理的,而多个人同时处理一个任务,这种任务我们称之为

    会签任务。这种业务需求很常见,如一个请款单,领导审批环节中,就需要多个部门领导签字。在流程业务

    中,我们可以把每个领导签字的环节都定义为任务,并且这个会签的人员是不固定的,若固定的我们可以通过

    Activiti的并行任务或串行任务来处理。会签的引入说明,无非就是为了流程流转至某一环节点,其审批的人员

    是动态的,并且需要根据会签审批的结果实现流程的不同流转

  2. 实现思路

    Activiti实现会签是基于多实例任务,将节点设置成多实例,主要通过在UserTask节点的属性上配置。如果想让某些特定的流程活动的行为执行多次,则可以将活动设置为多实例,让其按照配置来执行相应的次数。User Task 会创建活动的实例, 流程是否通过 User Task ,由用户决定,这不属于活动的行为。活动的多实例可以让一个流程活动(甚至是子流程〉按顺序或者同时执行,在执行的过程中,会为活动产生多个实例,因此称该流程活动为多实例的流程活动。大部分的流程活动均可以被设置为循环执行,这些活动包 括大部分的流程任务、嵌入式子流程 和调用式子流程 。为流程活动的元素添加multilnstanceLoopCharacteristics 子元素,将该流程活动配置为一个多实例的活动,如以下的配置片断:

    <userTask id="myTask"> 
        <multiInstanceLoopCharacteristics isSequential="false">
        </multiinstanceLoopCharacteristics> 
    </userTask>

    以上配置片断中的 multilnstanceLoopCharacteristics 元素的 isSequential 属性,用于配置这 个流程活动在执行时,是按顺序执行还是同时执行,为 true 表示按顺序执行, false 则同时执行。

    多实例活动像是循环体,可以为它提供集合或者设定循环次数,让它循环,就像使用 Java

    里面的循环语句一样。

    <multiInstanceLoopCharacteristics isSequential="true">
        <loopCardinality>1</loopCardinality>
    </multiInstanceLoopCharacteristics>

    中定义了 个多实例的用户任务,该用户任务的全部实例将会按顺序 执行( isSequential=true ),设置了循环次数为2。

  3. 多实例参数说明

    会签的种类 按数量通过:达到一定数量的通过表决后,会签通过。 按比例通过:达到一定比例的通过表决后,会签通过。 一票否决:只要有一个表决是否定的,会签否决。 一票通过:只要有一个表决通过的,会签通过。

    1、多实例包含的默认变量 nrOfInstances:创建的实例总数 nrOfActiveInstances:当前活动的实例数,针对顺序类型的多实例,该变量值等于1 nrOfCompletedInstances:已执行实例数 loopCounter:表示多实例流程循环的下标 2、多实例类型 顺序(sequential ):执行顺序,必选项。true:多实例顺序执行。false:多实例并行。

    并行(parallel):多个实例会同时并行发放给处理人

    loop cardinality:循环基数(实例数量),可选项。可填整数,表示会签的人数。

    Collection:集合,可选项。会签人数的集合list,与loop cardinality二选一。

    Element variable:元素变量。选择Collection时必选,为collection集合每次遍历的元素, 任务负责人需设置为该变量。

    Completion condition:完成条件,可选项。

    3、多实例基数 定义多实例生成的实例数。当结合采集方式生成多实例时,该基数只能小于或等于采集集合的size,否则执行过程中将发生系统错误 NoSuchElementException;当基数小于采集集合的size时,则按照集合中元素的顺序生成等于基数指定数量的实例。如果不配合使用采集方式,也可以直接指定基数,则系统会同时生成指定数量的相同实例。

    4、多实例采集 用于指定一个List,自动生成等于List size数量的实例。可以将该List设置为处理人员列表,然后设置元素变量名,并将分配—固定值—处理人/代理人设置为元素变量名,则最后会为每一个集合中的人员生成一个已指派实例。

    5、多实例元素变量 表示多实例采集中元素对应的变量,该变量保存着集合中元素的最新值,可以使用表达式获取。

    6、多实例完成条件 是一个表达式,如果返回值为true则该多实例自动结束。例如

    ${nrOfCompletedInstances/nrOfInstances >= 0.6 } 只要所有流程实例完成了60%即该多实例任务结束。

    ${nrOfInstances == nrOfCompletedInstances} 表示所有人员审批完成后会签结束。

    ${ nrOfCompletedInstances == 1}表示一个人完成审批,该会签就结束。

    Activiti会签有个特性,比如设置一个人完成后会签结束,那么其他人的代办任务都会消失。

    7、分配 在使用多实例的情况下,当将分配—固定值—处理人/代理人/候选人设置为多实例元素变量名时,则生成的实例会对应自动指派给列表中配置的处理人

  4. 具体实现

    1. 流程定义时

      userTask节点配置核心属性说明 1、isSequential指定多实例是按照并行或者串行的方式进行,当isSequential=true时,表示的串行执行,即虽然该节点有多条任务,但只有上一条执行完,才可以执行下一条。当isSequential=false时,表示的并行执行,即该节点下的多条任务可以同时执行,如三个人参与会签,是三个人同时收到待办,任务实例是同时产生的。

      2、activiti:collection:用于执行该会签环节的参与参与的人,可以用形如实例中的${leaderList}获取,也可以用户可以通过定义自身的服务类来获取

      3、activiti:elementVariable:此处表示的是每一个分支都有一个名叫leader的流程变量和userTask节点属性中的activiti:assignee="${leader}"一致

      4、completionCondition:指定会签环节的结束条件,表示是任务往下跳转的完成条件,返回true是,表示条件成立,流程会跳至下一审批环节

      bpmn配置文件

      <userTask id="usertask2" name="领导审批" activiti:assignee="${leader}">
            <extensionElements>
              <activiti:taskListener event="complete" class="com.demo.activiti.SignTaskListener"></activiti:taskListener>
            </extensionElements>
            <multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${leaderList}" activiti:elementVariable="leader">
              <completionCondition>${nrOfCompletedInstances/nrOfInstances == 1}</completionCondition>
            </multiInstanceLoopCharacteristics>
      </userTask>

      注意上述文件中任务负责人变量activiti:assignee设置应为activiti:elementVariable设置的变量

      会签代码实现

      1.分配会签人员

      Map<String, Object> vars = new HashMap<>();
              List<String> leaderList = new ArrayList<>();
              leaderList.add("zhangsan");
              leaderList.add("lisi");
              leaderList.add("wangwu");
      
              vars.put("leaderList", leaderList);
      
              ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId(), vars);

      2.会签人员审批

      Map<String, Object> leaderOneAudit = new HashMap<>();
              Scanner scanner = new Scanner(System.in);
              System.out.println("请输入领导审批意见。。。。");
              String auditOne = scanner.nextLine();
              leaderOneAudit.put("audit", auditOne);
              System.out.println("张三的审批意见为:" + (auditOne.equals("yes") ? "同意" : "不同意"));
              task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).taskAssignee("zhangsan").singleResult();
              taskService.complete(task.getId(), leaderOneAudit);
    2. Java代码实现

      1、 设置会签节点属性

      public static void setMultiInstance(String modelId, String nodelId) throws Exception {
              // 获取模型
              byte[] mes = repositoryService.getModelEditorSource(modelId);
              // 转换成JsonNode
              JsonNode jsonNode = objectMapper.readTree(mes);
              // 转换成BpmnModel
              BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();
              BpmnModel bpmnModel = bpmnJsonConverter.convertToBpmnModel(jsonNode);
              // 获取物理形态的流程
              Process process = bpmnModel.getProcesses().get(0);
              // 获取节点信息
              FlowElement flowElement = process.getFlowElement(nodelId);
              // 只有人工任务才可以设置会签节点
              UserTask userTask = (UserTask) flowElement;
              // 设置受理人,这里应该和ElementVariable的值是相同的
              userTask.setAssignee("${" + Constant.ACT_MUIT_VAR_NAME + "}");
              // userTask.setOwner("${user}");
      
              // 获取多实例配置
              MultiInstanceLoopCharacteristics characteristics = new MultiInstanceLoopCharacteristics();
              // 设置集合变量,统一设置成users
              characteristics.setInputDataItem(Constant.ACT_MUIT_LIST_NAME);
              // 设置变量
              characteristics.setElementVariable(Constant.ACT_MUIT_VAR_NAME);
              // 设置为同时接收(false 表示不按顺序执行)
              characteristics.setSequential(false);
              // 设置条件(暂时处理成,全部会签完转下步)
              characteristics.setCompletionCondition("${nrOfCompletedInstances==nrOfInstances}");
      
              userTask.setLoopCharacteristics(characteristics);
              // 保存
              ObjectNode objectNode = new BpmnJsonConverter().convertToJson(bpmnModel);
              repositoryService.addModelEditorSource(modelId, objectNode.toString().getBytes("utf-8"));
          }

      2、清空会签属性

      public static void clearMultiInstance(String modelId, String nodelId) throws Exception {
              // 获取模型
              byte[] mes = repositoryService.getModelEditorSource(modelId);
              // 转换成JsonNode
              JsonNode jsonNode = new ObjectMapper().readTree(mes);
              // 转换成BpmnModel
              BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();
              BpmnModel bpmnModel = bpmnJsonConverter.convertToBpmnModel(jsonNode);
              // 获取物理形态的流程
              Process process = bpmnModel.getProcesses().get(0);
              // 获取节点信息
              FlowElement flowElement = process.getFlowElement(nodelId);
              // 只有人工任务才可以设置会签节点
              UserTask userTask = (UserTask) flowElement;
              // 清空受理人
              userTask.setAssignee("");
              // 获取多实例配置
              MultiInstanceLoopCharacteristics characteristics = userTask.getLoopCharacteristics();
              if (characteristics != null) {
                  // 清空集合
                  characteristics.setInputDataItem("");
                  // 清空变量
                  characteristics.setElementVariable("");
                  // 设置为顺序接收(true 表示不按顺序执行)
                  characteristics.setSequential(true);
                  // 清空条件
                  characteristics.setCompletionCondition("");
              }
      
              // 保存
              ObjectNode objectNode = new BpmnJsonConverter().convertToJson(bpmnModel);
              repositoryService.addModelEditorSource(modelId, objectNode.toString().getBytes("utf-8"));
          }

Activiti7与SpringBoot整合开发

Activiti7发布正式版之后,它与SpringBoot2.x已经完全支持整合开发。

2.1 SpringBoot整合Activiti7的配置

在工程的pom.xml文件中引入相关的依赖,其中activiti的依赖是:activiti-spring-boot-starter。

具体依赖如下所示:

		<dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis</artifactId>
                </exclusion>
            </exclusions>
            <version>${activiti.version}</version>
        </dependency>

2.2 SpringBoot的application.yml文件配置

为了能够实现Activiti7生成的表放到Mysql数据库中,需要在配置文件application.yml中添加相关的配置

注意:activiti7默认没有开启数据库历史记录,需要手动配置开启

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ry-cloud?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: root
    password: root
  activiti:
    #1.flase:默认值。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
    #2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
    #3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
    #4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
    database-schema-update: true
    #检测历史表是否存在 activiti7默认没有开启数据库历史记录 启动数据库历史记录
    db-history-used: true
    #记录历史等级 可配置的历史级别有none, activity, audit, full
    #none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。
    #activity:级别高于none,保存流程实例与流程行为,其他数据不保存。
    #audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。audit为history的默认值。
    #full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。
    history-level: full
    #校验流程文件,默认校验resources下的processes文件夹里的流程文件
    check-process-definitions: false

2.4 添加SpringSecurity安全框架整合配置

因为Activiti7与SpringBoot整合后,默认情况下,集成了SpringSecurity安全框架,SpringBoot的依赖包已经将SpringSecurity的依赖包也添加进项目中。它的作用是为了实现SpringSecurity框架的用户权限的配置,这样我们就可以在系统中使用用户权限信息。

package com.ruoyi.leavel.config;
​
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
​
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests().anyRequest().permitAll().and().logout().permitAll();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值