Activiti学习

lishihuan大约 26 分钟

Activiti学习

  1. 官方文档:https://www.activiti.org/userguide/open in new window
  2. 示例代码:https://github.com/Activiti/activiti-examplesopen in new window
  3. 在线课程:https://www.activiti.org/resources/trainingopen in new window
  4. 论坛与社区支持:https://community.alfresco.com/community/activitiopen in new window
  5. 源码:
activiti5.22:https://github.com/Activiti/Activiti/releases/download/activiti-5.22.0/activiti-5.22.0.zip

activiti6.0.0:https://github.com/Activiti/Activiti/releases/download/activiti-6.0.0/activiti-6.0.0.zip

https://gitee.com/MiyueSC/bpmn-process-designeropen in new window

delete from act_hi_actinst;
delete from act_hi_detail;
delete from act_hi_identitylink;
delete from act_hi_procinst;
delete from act_hi_taskinst;
delete from act_hi_varinst;
delete from act_ru_identitylink;
delete from act_ru_task;
delete from act_ru_variable;
delete from act_ru_execution;

PROC_INST_ID_ --> 指的是 act_ru_execution 表中,每个实例的第一条记录的id

学习参考:

https://vip.tulingxueyuan.cn/detail/p_6098c3afe4b0b07e9ccb0d7d/6open in new window

https://vip.tulingxueyuan.cn/detail/v_6098c453e4b0706df77ca75e/3?from=p_6098c3afe4b0b07e9ccb0d7d&type=6&parent_pro_id=open in new window

https://blog.csdn.net/m0_46527847/article/details/113363266open in new window

https://blog.csdn.net/qq_40451631/article/details/84937251open in new window

异常:https://blog.csdn.net/dongpo11/article/details/120254281open in new window

工作流监听器:https://www.cnblogs.com/no-celery/p/15967880.htmlopen in new window

代办人和候选人和候选组

步骤

首先需要在 java 工程中加入 ProcessEngine 所需要的 jar 包,包括:

  1. activiti-engine-7.0.0.beta1.jar

  2. activiti 依赖的 jar 包:mybatis、alf4j、log4j 等

  3. activiti 依赖的 spring 包

  4. 数据库驱动

  5. 第三方数据连接池 dbcp

  6. 单元测试 Junit-4.12.jar

我们使用 maven 来实现项目的构建,所以应当导入这些 jar 所对应的坐标到 pom.xml 文件中。

1. 安装Activiti数据库

1.1. actiBPM 插件

idea: actiBPM 插件 https://blog.csdn.net/shipfei_csdn/article/details/105157702open in new window

https://plugins.jetbrains.com/plugin/7429-actibpm/versionsopen in new window

本地地址: actibpm.jar

1.2. 创建 mysql 数据库 activiti

CREATE DATABASE activiti DEFAULT CHARACTER SET utf8;

1.3. 配置maven

pom.xml

<properties>
        <activiti.version>7.1.0.M6</activiti.version>
        <activiti.cloud.version>7.0.0.Beta1</activiti.cloud.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>
        <!-- bpmn 模型处理 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-model</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn 转换 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-converter</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn json数据转换 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-json-converter</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn 布局 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-layout</artifactId>
            <version>${activiti.version}</version>
        </dependency>
    </dependencies>

1.4. 创建配置文件

创建配置文件 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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
						http://www.springframework.org/schema/contex http://www.springframework.org/schema/context/spring-context.xsd
						http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--数据源配置dbcp-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/activiti?nullCatalogMeansCurrent=true" />
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>
    <!--activiti单独运行的ProcessEngine配置对象(processEngineConfiguration),使用单独启动方式
        默认情况下:bean的id=processEngineConfiguration
    -->

    <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
    <!--代表数据源-->
    <property name="dataSource" ref="dataSource"></property>


   <!-- <property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activiti" />
    <property name="jdbcUsername" value="root" />
    <property name="jdbcPassword" value="root" />-->
    <!-- activiti数据库表处理策略 -->
    <property name="databaseSchemaUpdate" value="true"/>
    </bean>
</beans>

说明: 配置文件 activiti.cfg.xml 如果不叫这个名称,则 需要通过

ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml", "processEngineConfiguration"); 来指定

关于 processEngineConfiguration 中的 databaseSchemaUpdate **参数,**通过此参数设计 activiti 数据表的处理策略,参数如下:

false(默认):检查数据库表的版本和依赖库的版本, 如果版本不匹配就抛出异常。

true: 构建流程引擎时,执行检查,如果需要就执行更新。 如果表不存在,就创建。

create-drop: 构建流程引擎时创建数据库表, 关闭流程引擎时删除这些表。

drop-create:先删除表再创建表。

create: 构建流程引擎时创建数据库表, 关闭流程引擎时不删除这些表。

1.5. 配置日志

log4j.properties

1.6. 通过程序创建表

创建工作流 25张表

创建 ProcessEngineConfiguration,通过 ProcessEngineConfiguration 创建 ProcessEngine,在创建 ProcessEngine 时会自动创建数据库

public static void main(String[] args) {
    //条件:1.activiti配置文件名称:activiti.cfg.xml   2.bean的id="processEngineConfiguration"
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    System.out.println(processEngine);
}

如果 工作流配置文件命名不是默认的 activiti.cfg.xml 则通过 下面的方式 创建

public static void main(String[] args) {
        //1.创建ProcessEngineConfiguration对象  第一个参数:配置文件名称  第二个参数是processEngineConfiguration的bean的id
        ProcessEngineConfiguration configuration = ProcessEngineConfiguration
                .createProcessEngineConfigurationFromResource("activiti.cfg.xml","processEngineConfiguration");
        //2.创建ProcesEngine对象
        ProcessEngine processEngine = configuration.buildProcessEngine();
        //3.输出processEngine对象
        System.out.println(processEngine);
}

2. Activiti 服务架构

image-20220405123509917
image-20220405123509917

在新版本中IdentityService,FormService 两个 Serivce 都已经删除了。所以后面我们对于这两个 Service 也不讲解了,但老版本中还是有这两个 Service

通过 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); 获取 processEngine ,从而可以访问各个service 接口

RepositoryServiceactiviti 的资源管理类,提供了管理和控制流程发布包和流程定义的操作
RuntimeServiceactiviti 的流程运行管理类,可以从这个服务类中获取很多关于流程执行相关的信息
TaskServiceactiviti 的任务管理类,可以从这个类中获取任务的信息
HistoryServiceactiviti 的历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据(根据配置),比如流程实例启动时间,任务的参与者, 完成任务的时间,每个流程实例的执行路径,等等。 这个
ManagerServiceactiviti 的引擎管理类提,供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动
IdentityService组织机构管理

3. 操作数据库表

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

image-20220407213210529
image-20220407213210529

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

image-20220407213428589
image-20220407213428589
image-20220407213513250
image-20220407213513250

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

image-20220407213548577
image-20220407213548577

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

image-20220407213633620
image-20220407213633620

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

image-20220407213727626
image-20220407213727626

4. Activiti 使用

4.1 流程定义

编辑 bpmn文件

Activiti-Designer 使用

在 eclipse 或 idea 中安装 activiti-designer 插件即可使用,画板中包括以下结点:

Connection—连接

Event---事件

Task---任务

Gateway---网关

Container—容器

Boundary event—边界事件

Intermediate event- -中间事件

流程图设计完毕保存生成.bpmn 文件

4.2 流程部署

设计到的表:act_ge_bytearray act_re_deployment

  • act_ge_bytearray :存储png 和bpmn文件信息

  • act_re_deployment:存储流程定义的一下信息

1.文件上传方式

    /**
     * 部署流程定义  文件上传方式
     */
    @Test
    public void testDeployment(){
//        1、创建ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、得到RepositoryService实例
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        3、使用RepositoryService进行部署
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("bpmn/Leave.bpmn") // 添加bpmn资源
                //png资源命名是有规范的。Leave.myLeave.png|jpg|gif|svg  或者Leave.png|jpg|gif|svg
                .addClasspathResource("bpmn/Leave.myLeave.png")  // 添加png资源
                .name("请假申请流程")
                .deploy();
//        4、输出部署信息
        System.out.println("流程部署id:" + deployment.getId());
        System.out.println("流程部署名称:" + deployment.getName());
    }

2.zip压缩文件上传方式

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

4.3 启动一个流程实例

流程定义 **act_re_procdef ** ====> processInstance.getProcessDefinitionId()

act_hi_actinst 已完成的活动信息 act_hi_identitylink 参与者信息 act_hi_procinst 流程实例 =====> processInstance.getId() act_hi_taskinst 任务实例 act_ru_execution 执行表 act_ru_identitylink 参与者信息 act_ru_task 任务

 //1.得到ProcessEngine对象
 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

//2.得到RunService对象
RuntimeService runtimeService = processEngine.getRuntimeService();

//3.创建流程实例  流程定义的key需要知道 holiday
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday");

//4.输出实例的相关信息
System.out.println("流程部署ID"+processInstance.getDeploymentId());//null
System.out.println("流程定义ID"+processInstance.getProcessDefinitionId());//holiday:1:4
System.out.println("流程实例ID"+processInstance.getId());//2501
System.out.println("活动ID"+processInstance.getActivityId());//null

4.4 任务查询

public static void main(String[] args) {
		//1.得到ProcessEngine对象
		ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

		//2.得到TaskService对象
		TaskService taskService = processEngine.getTaskService();

		//3.根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
		Task task = taskService.createTaskQuery()
				.processDefinitionKey("myLeave")
				.taskAssignee("worker")
				.singleResult();

		//4.任务列表的展示
		System.out.println("流程实例ID:"+task.getProcessInstanceId());
		System.out.println("任务ID:"+task.getId());  //5002
		System.out.println("任务负责人:"+task.getAssignee());
		System.out.println("任务名称:"+task.getName());


	}
	//zhangsan任务列表的查询
   public static void main(String[] args) {
        //1.得到ProcessEngine对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        //2.得到TaskService对象
        TaskService taskService = processEngine.getTaskService();

        //3.根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
        List<Task> taskList = taskService.createTaskQuery()
                .processDefinitionKey("myLeave")
                .taskAssignee("worker")
                .list();

        //4.任务列表的展示
        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());
        }

    }

4.5 任务处理

act_hi_actinst 已完成的活动信息 act_hi_identitylink 参与者信息 act_hi_taskinst act_ru_identitylink act_ru_task


    //查询当前用户wangwu的任务并处理掉
    public static void main(String[] args) {
        //1.得到ProcessEngine对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        //2.得到TaskService对象
        TaskService taskService = processEngine.getTaskService();

        //3.查询当前用户的任务
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("myLeave")
                .taskAssignee("worker")
                .singleResult();

        //4.处理任务,结合当前用户任务列表的查询操作的话,任务ID:task.getId()
        taskService.complete(task.getId());

        //5.输出任务的id
        System.out.println(task.getId());
    }

4.6 历史查询

    public static void main(String[] args) throws IOException {
        //1.得到ProcessEngine对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        //2.得到HistoryService
        HistoryService historyService = processEngine.getHistoryService();

        //3.得到HistoricActivitiInstanceQuery对象
        HistoricActivityInstanceQuery historicActivityInstanceQuery = historyService.createHistoricActivityInstanceQuery();

        historicActivityInstanceQuery.processInstanceId("2501");//设置流程实例的id

        //4.执行查询
        List<HistoricActivityInstance> list = historicActivityInstanceQuery
                .orderByHistoricActivityInstanceStartTime().asc().list();//排序StartTime

        //5.遍历查询结果
        for (HistoricActivityInstance instance :list){
            System.out.println(instance.getActivityId());
            System.out.println(instance.getActivityName());
            System.out.println(instance.getProcessDefinitionId());//流程定义 ===> act_re_procdef
            System.out.println(instance.getProcessInstanceId());//流程实例 ====> act_re_deployment
            System.out.println("=============================");
        }
    }

5. 流程实例

先有 流程定义 ===> act_re_procdef 在有 流程实例 ====> act_re_deployment

image-20220405223408160
image-20220405223408160

5.1 查询流程定义信息

    public static void main(String[] args) {
        //1.得到ProcessEngine对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        //2.创建RepositoryService对象
        RepositoryService repositoryService = processEngine.getRepositoryService();

        //3.得到ProcessDefinitionQuery对象,可以认为它就是一个查询器
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();

        //4.设置条件,并查询出当前的所有流程定义   查询条件:流程定义的key=holiday
        //orderByProcessDefinitionVersion() 设置排序方式,根据流程定义的版本号进行排序
        List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKey("holiday")
                .orderByProcessDefinitionVersion()
                .desc().list();

        //5.输出流程定义信息
        for(ProcessDefinition processDefinition :list){
            System.out.println("流程定义ID:"+processDefinition.getId());
            System.out.println("流程定义名称:"+processDefinition.getName());
            System.out.println("流程定义的Key:"+processDefinition.getKey());
            System.out.println("流程定义的版本号:"+processDefinition.getVersion());
            System.out.println("流程部署的ID:"+processDefinition.getDeploymentId());

        }
    }

5.2 删除已经部署的流程定义

act_ge_bytearray act_re_deployment act_re_procdef

    /**
     * 注意事项:
     *     1.当我们正在执行的这一套流程没有完全审批结束的时候,此时如果要删除流程定义信息就会失败
     *     2.如果公司层面要强制删除,可以使用repositoryService.deleteDeployment("1",true);
     *     //参数true代表级联删除,此时就会先删除没有完成的流程结点,最后就可以删除流程定义信息  false的值代表不级联
     *
     * @param args
     */
    public static void main(String[] args) {
        //1.得到ProcessEngine对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        //2.创建RepositoryService对象
        RepositoryService repositoryService = processEngine.getRepositoryService();

        //3.执行删除流程定义  参数代表流程部署的id
        repositoryService.deleteDeployment("1");
    }

5.3读取两个资源文件

需求: 1.从Activiti的act_ge_bytearray表中读取两个资源文件 2.将两个资源文件保存到路径: G:\Activiti7开发计划\Activiti7-day03\资料

技术方案: 1.第一种方式使用actviti的api来实现 2.第二种方式:其实就是原理层面,可以使用jdbc的对blob类型,clob类型数据的读取,并保存 IO流转换,最好commons-io.jar包可以轻松解决IO操作

public static void main(String[] args) throws IOException {
        //1.得到ProcessEngine对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        //2.得到RepositoryService对象
        RepositoryService repositoryService = processEngine.getRepositoryService();

        //3.得到查询器:ProcessDefinitionQuery对象
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();

        //4.设置查询条件
        processDefinitionQuery.processDefinitionKey("holiday");//参数是流程定义的key

        //5.执行查询操作,查询出想要的流程定义
        ProcessDefinition processDefinition = processDefinitionQuery.singleResult();

        //6.通过流程定义信息,得到部署ID
        String deploymentId = processDefinition.getDeploymentId();

        //7.通过repositoryService的方法,实现读取图片信息及bpmn文件信息(输入流)
        //getResourceAsStream()方法的参数说明:第一个参数部署id,第二个参数代表资源名称
        //processDefinition.getDiagramResourceName() 代表获取png图片资源的名称
        //processDefinition.getResourceName()代表获取bpmn文件的名称
        InputStream pngIs = repositoryService
                .getResourceAsStream(deploymentId,processDefinition.getDiagramResourceName());
        InputStream bpmnIs = repositoryService
                .getResourceAsStream(deploymentId,processDefinition.getResourceName());

        //8.构建出OutputStream流
        OutputStream pngOs =
                new FileOutputStream("G:\\Activiti7开发计划\\Activiti7-day03\\资料\\"+processDefinition.getDiagramResourceName());

        OutputStream bpmnOs =
                new FileOutputStream("G:\\Activiti7开发计划\\Activiti7-day03\\资料\\"+processDefinition.getResourceName());

        //9.输入流,输出流的转换  commons-io-xx.jar中的方法
        IOUtils.copy(pngIs,pngOs);
        IOUtils.copy(bpmnIs,bpmnOs);
        //10.关闭流
        pngOs.close();
        bpmnOs.close();
        pngIs.close();
        bpmnIs.close();

    }

5.4 Activiti与业务系统整合

通过 act_ru_execution 存储 businesskey

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

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

// 根据流程定义的key启动一个流程实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey,businessKey);

5.5 查询流程实例

6. 个人任务

6.1分配任务负责人

总共三种写法

  • 固定分配

  • 表达式分配

    • UEL 表达式
    • 使用流程变量分配任务
  • 监听器分配

6.1.1 固定分配

Assignee 项为任务负责人

image-20220407220254972
image-20220407220254972

6.1.2 表达式分配

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

6.1.2.1 UEL-value 定义如下

2种写法,${assignee}${user.assignee}

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

6.1.2.2 UEL-method 方式如下:

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

image-20220407220945473
image-20220407220945473
6.1.2.3 UEL-method 与 UEL-value 结合

再比如:${ldapService.findManagerForEmployee(emp)}

ldapService 是 spring 容器的一个 bean,findManagerForEmployee 是该 bean 的一个方法,emp 是 activiti流程变量,emp 作为参数传到ldapService.findManagerForEmployee 方法中。

6.1.2.4 其它

表达式支持解析基础类型、bean、list、array 和 map,也可作为条件判断。

如下:

${order.price > 100 && order.price < 250}

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

6.1.3 监听器分配

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

任务相当事件包括:

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

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

public class MyTaskListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        //这里指定任务负责人
        delegateTask.setAssignee("张三");
    } 
}

6.2 查询任务

// 查询当前个人待执行的任务
@Test
public void findPersonalTaskList() {
    // 流程定义key
    String processDefinitionKey = "holiday";
    // 任务负责人
    String assignee = "张三丰";
    // 创建TaskService
    TaskService taskService = processEngine.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(业务标识 )关联查询业务系统的请假单表,查询出请假天数等信息

image-20220407222449447
image-20220407222449447

6.3 办理任务

指定任务 id,调用 TaskService 完成任务

// 完成任务
@Test
public void completTask() {
    //任务id
    String taskId = "10305";
    // 创建TaskService
    TaskService taskService = processEngine.getTaskService();
    taskService.complete(taskId);
    System.out.println("完成任务");
}

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

    //完成任务  zhangsan  -----lishi----判断流程变量的请假天数,1天----分支:人事经理存档(zhaoliu)
    public static void main3(String[] args) {
        //1.得到ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        //2.得到TaskService
        TaskService taskService = processEngine.getTaskService();

        //3.查询当前用户是否有任务
        String key = "myProcess_1";
        Task task = taskService.createTaskQuery().processDefinitionKey(key)
                .taskAssignee("zhaoliu").singleResult();

        //4.判断task!=null,说明当前用户有任务
        if(task!=null){
            taskService.complete(task.getId());
            System.out.println("任务执行完毕");
        }

    }

7 流程变量

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

流程变量的数据类型: string,duble,boolean,short,long,Integer,binary,date日期、serializable。如果流程变量需要用到pojo当中的属性字段,则该pojo需要实现序列化接口

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

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

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

  • Local 变量名也可以和 global 变量名相同,没有影响。

	private String getVariableByNameForHis(String taskId, String variableName) {
		HistoricVariableInstance businessDept = historyService.createHistoricVariableInstanceQuery()
				.taskId(taskId)
				.variableName(variableName)
				.singleResult();
		if(businessDept==null){
			return "";
		}
		return (String) businessDept.getValue();
	}
	/**
	 * 从历史中获取 属性值,启动时获取会有异常
	 * @param procInsId
	 * @param variableName
	 * @return
	 */
	private String getVariableByNameForHis(String procInsId, String variableName) {
		this.historyService = BeanContext.getApplicationContext().getBean(HistoryService.class);
		HistoricVariableInstance businessDept = historyService.createHistoricVariableInstanceQuery()
				.processInstanceId(procInsId)
				.variableName(variableName)//business_office  "business_dept"
				.excludeTaskVariables().singleResult();
		return (String) businessDept.getValue();
	}

操作数据库表

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

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 类型变量值

Text_:text 类型变量值

SELECT * FROM act_hi_varinst #历史流程变量表

记录所有已创建的流程变量,包括 global 和 local 变量

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

Authentication.setAuthenticatedUserId(currentUserId);// 记录 流程申请人
// 启动流程
ProcessInstance procIns = runtimeService.startProcessInstanceByKey(procDefKey, businessTable + ":" + businessId, vars);

//runtimeService.startProcessInstanceByKey(procDefKey, vars);// 设置全局变量
  • 查询流程变量的数值
List<HistoricVariableInstance> business_dept = historyService.createHistoricVariableInstanceQuery()
				.processInstanceId(procInsId)
				.variableName("business_dept")
				.excludeTaskVariables()
				.list();
		System.out.println(business_dept);
HistoricVariableInstance business_dept = historyService.createHistoricVariableInstanceQuery()
				.processInstanceId(procInsId)
				.variableName("business_dept")
				.excludeTaskVariables().singleResult();
		System.out.println(business_dept.getValue());ja

8. 组任务

8.1 设置候选人

Candidate-users 候选人

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

image-20220408211840227
image-20220408211840227

8.2 办理组任务

8.2.1 组任务办理流程

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

  • 第二步:拾取(claim)任务 该组任务的所有候选人都能拾取。将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人。 如果拾取后不想办理该任务? 需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。

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

  • 第四步:办理个人任务

8.2.2 用户查询组任务

根据候选人查询组任务

@Test
public void findGroupTaskList() {
    // 流程定义key
    String processDefinitionKey = "holiday4";
    // 任务候选人
    String candidateUser = "zhangsan";
    // 创建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());
    } 
}

8.2.3 用户拾取组任务

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

@Test
public void claimTask(){
    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("任务拾取成功");
    } 
}

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

8.2.4 用户查询个人待办任务

查询方式同个人任务查询

@Test
public void findPersonalTaskList() {
    // 流程定义key
    String processDefinitionKey = "holiday4";
    // 任务负责人
    String assignee = "zhangsan";
    // 创建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());
    } 
}

8.2.5 用户办理个人任务

同个人任务办理

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

8.2.6 归还组任务

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

// 归还组任务,由个人任务变为组任务,还可以进行任务交接
@Test
public void setAssigneeToGroupTask() {
    // 查询任务使用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 方法将任务委托给其它用户负责,注意被委托的用户可以不是候选人(建议

不要这样使用)

8.2.7 任务交接

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

@Test
public void setAssigneeToCandidateUser() {
    // 查询任务使用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) {
        // 将此任务交给其它候选人办理该 任务
        String candidateuser = "zhangsan";
        // 根据候选人和组任务id查询,如果有记录说明该 候选人有资格拾取该 任务
        Task task2 = taskService.createTaskQuery().taskId(taskId)
        	.taskCandidateUser(candidateuser).singleResult();
        if (task2 != null) {
            // 才可以交接
            taskService.setAssignee(taskId, candidateuser);
        } 
    } 
}

8.2.8 数据库表操作

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

image-20220408213949876
image-20220408213949876

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

image-20220408214013500
image-20220408214013500

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

8.3 Candiate-Groups

Candiate-Groups

9. 网关

9.1 排他网关

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

为什么要用排他网关?

不用排他网关也可以实现分支,如下图

image-20220408214243320
image-20220408214243320

上图中,在连线的 condition 条件上设置分支条件。

缺点:

如果条件都不满足,不使用排他网关,流程就结束了(是异常结束)。

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

image-20220408214321279
image-20220408214321279

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

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)

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

9.2 并行网关

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

入和外出顺序流的:

  • fork 分支:

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

  • join 汇聚:

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

注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。

例子:

image-20220408214710220
image-20220408214710220

说明:

财务结算和入库是两个 execution 分支,在 act_ru_execution 表有两条记录分别是财务结算和入库,act_ru_execution 还有一条记录表示该流程实例。

待财务结算和入库任务全部完成,在汇聚点汇聚,通过 parallelGateway 并行网关。并行网关在业务应用中常用于会签任务,会签任务即多个参与者共同办理的任务。

数据库表操作

以上图为例 当执行到并行网关数据库跟踪如下:

当前任务表:SELECT * FROM act_ru_task #当前任务表

image-20220408214949142
image-20220408214949142

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

通过流程实例执行表:SELECT * FROM act_ru_execution #流程实例的执行表

image-20220408215006400
image-20220408215006400

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

对并行任务的执行:

并行任务执行不分前后,由任务的负责人去执行即可。当完成并任务中一个任务后:

已完成的任务在当前任务表 act_ru_task_已被删除。

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

image-20220408215116019
image-20220408215116019

有并行网关的汇聚结点:说明有一个分支已经到汇聚,等待其它的分支到达。当所有分支任务都完成,都到达汇聚结点后:

流程实例执行表:SELECT * FROM act_ru_execution,执行流程实例不存在,说明流程执行结束。

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

9.3 包含网关

包含网关可以看做是排他网关和并行网关的结合体。 和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。

但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。包含网关的功能是基于进入和外出顺序流的

  • 分支:

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

  • 汇聚:

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

案例:

以体检为列:通过流程变量 userType 来表示,如果等于 1 表示普通员工,如果等于 2 表示领导,常规体检和抽血 普通员工和领导都可以,但是附件项只能是领导才能走

image-20220408221951519
image-20220408221951519

10 课程总结

什么是工作流? 就是通过计算机对业务流程进行自动化管理,实现多个参与者按照预定义的流程去自动执行业务流程。 什么 activiti?

Activiti 是一个工作流的引擎,开源的架构,基本 bpmn2.0 标准进行流程定义,它的是前身是 jbpm。 Activiti 通过是要嵌入到业务系统开发使用。

如何使用 activiti 开发?

  • 第一步:部署 activiti 的环境。 环境包括:jar 包和数据库(25 张表) 业务系统通过 spring 和 activiti 整合进行开发。

  • 第二步:使用 activiti 提供流程设计器(和 idea 或 eclipse 集成的 designer)工具进行流程定义 流程定义生成两个文件:.bpmn 和.png(不是必须的)。

  • 第三步;将流程定义文件部署到 activiti 的数据库 SELECT * FROM act_re_deployment #流程定义部署表 一次部署插入一条记录,记录流程定义的部署信息 SELECT * FROM act_re_procdef #流程定义表一次部署流程定义信息,如果一次部署两个流程定义,插入两条记录 建议:一次部署只部署一个流程定义,这样 act_re_deployment 和 act_re_procdef 一对一关系 常用两个方法:单个文件部署和 zip 文件部署。 建议单个文件部署。

  • 第四步: 启动一个流程实例 业务系统就可以按照流程定义去执行业务流程,执行前需要启动一个流程实例根据流程定义来启动一个流程实例。 指定一个流程定义的 key 启动一个流程实例,activiti 根据 key 找最新版本的流程定义。 指定一个流程定义的 id 启动一个流程实例。 启动一个实例需要指定 businesskey(业务标识),businessKey 是 activiti 和业务系统整合时桥梁。 比如:请假流程,businessKey 就是请假单 id。 启动一个实例还可以指定流程变量,流程变量是全局变量(生命期是整个流程实例,流程实例结束,变量就消失)

  • 第五步:查询待办任务 查询个人任务:使用 taskService,根据 assignee 查询该用户当前的待办任务。 查询组任务:使用 taskService,根据 candidateuser 查询候选用户当前的待办组任务。

  • 第六步:办理任务 办理个人任务:调用 taskService 的 complete 方法完成任务。 如果是组任务,需要先拾取任务,调用 taskService 的 claim 方法拾取任务,拾取任务之后组任务就变成了个人任务(该任务就有负责人)。 网关:

    • 排他网关:任务执行之后的分支,经过排他网关分支只有一条有效。
    • 并行网关:任务执行后,可以多条分支,多条分支总会汇聚,汇聚完成,并行网关结束。
    • 包含网关:是排他网关和并行网关结合体。

第二部分

Activiti 与spring

异常:

1.Inopen in new window order to use comments, history should be enabled

spring: 
  activiti:
    db-history-used:  true
    history-level:  audit