Activiti工作流框架学习笔记(一)「建议收藏」

Activiti工作流框架学习笔记(一)「建议收藏」工作流的概念先看下面两张图:对以上两张图进行说明:假设这两张图就是华谊兄弟的请假流程图图的组成部分:人物:范冰冰、冯小刚、王中军事件(动作):请假、批准、不批准通过以上分析我们就可以抽象成:接下来给出工作流的书面化概念:工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

工作流的概念

先看下面两张图:
这里写图片描述

这里写图片描述
对以上两张图进行说明:

  1. 假设这两张图就是华谊兄弟的请假流程图
  2. 图的组成部分:
    • 人物:范冰冰、冯小刚、王中军
    • 事件(动作):请假、批准、不批准

通过以上分析我们就可以抽象成:
这里写图片描述
接下来给出工作流的书面化概念:

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

对于第一次接触工作流的小伙伴来说,觉得难以理解,也无可厚非,说得好像我自己就能深刻理解一样,我也只是将学习Activiti工作流框架中的一些知识点记录下来而已,也希望能和大家讨论。

不管了,下面也给出工作流管理系统的概念:

工作流管理系统(Workflow Management System, WfMS)是一个软件系统,它完成工作量的定义和管理,并按照在系统中预先定义好的工作流逻辑进行工作流实例的执行。工作流管理系统不是企业的业务系统,而是为企业的业务系统的运行提供了一个软件的支撑环境。

除此之外,工作流管理联盟(WfMC,Workflow Management Coalition)也给出了关于工作流管理系统的定义:

工作流管理系统是一个软件系统,它通过执行经过计算的流程定义去支持一批专门设定的业务流程。工作流管理系统被用来定义、管理和执行工作流程。

而工作流管理系统的目标为:

管理工作的流程以确保工作在正确的时间被期望的人员所执行——在自动化进行的业务过程中插入人工的执行和干预。

说完工作流,不可避免就要阐述一下工作流框架的概念,工作流框架即用于处理工作流相关问题的框架。常见的工作流框架有:

  1. activiti5.13
  2. JBPM4.4
  3. OSWorkflow

我本人使用的是activiti5.13这个工作流框架。

Activiti介绍

Activiti5是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理、工作流、服务协作等领域的一个开源的、灵活的、易扩展的可执行流程语言框架。Activiti基于Apache许可的开源BPM平台,创始人Tom Baeyens是JBoss JBPM的项目架构师,它的特色是提供了eclipse插件,开发人员可以通过插件直接绘画出业务流程图。
大家可能听说过业务流程图一嘴,这里给出两个业务流程图:

  • 请假流程图
    这里写图片描述
  • 不知道什么情况的业务流程图
    这里写图片描述

Activiti框架的目录结构

Activiti框架的目录结构如下图所示:
这里写图片描述
你会发现bin目录是空的。database目录下有3个目录,一个目录是create,里面存放的是建表语句,一个目录是drop,里面存放的是删除表的语句,最后一个目录是upgrade,里面存放的是升级Activiti的语句。libs目录下是一些jar包,最核心的jar包是activiti-engine-5.13.jar。wars目录下存放的是Activiti框架官方的学习demo,我们初次学习Activiti框架必然要借鉴其中的案例。
我想大家可能会好奇Activiti框架里面为何会有一些建表语句。因为工作流框架底层是有一套数据库提供支持的,针对不同的数据库提供不同的sql建表语句。Activiti5.13框架对应23张表,JBPM4.4框架对应18张表,开发人员不需要自己编写sql语句操作这些表,框架底层会生成sql语句操作。Activiti5.13框架底层使用mybatis框架操作数据库,JBPM框架底层使用hibernate框架操作数据库。

安装activiti插件——流程设计器插件

要在eclipse上安装activiti插件,可参考我的文章4.5版本eclipse安装activiti插件
下面我就来用这个插件设计一个请假流程图:
【第一步】,建一个普通的java项目,例如activiti_02,在src目录下右键→NewOther...
这里写图片描述
【第二步】,在弹出的对话框中,在输入项中输入activiti,快速找到Activiti Diagram,选中它,点击Next
这里写图片描述
【第三步】,在弹出的对话框中,写入流程图的名称,当然了亦可使用默认名称——MyProcess,然后点击Next
这里写图片描述
【第四步】,在最后弹出的对话框中直接点击Finish
这里写图片描述
【第五步】,观看以下gif动图,读者即可创建一个请假流程图
这里写图片描述

创建Activiti框架提供的数据库表

使用Activiti框架提供的sql脚本建表

Activiti框架提供了sql脚本文件用于建表,这些sql脚本文件就位于Activiti框架database目录下的create目录中,如下:
这里写图片描述
下面我就来使用Activiti框架提供的sql脚本建表,步骤如下:
【第一步】,手动创建一个数据库
这里写图片描述
【第二步】,进入数据库,执行框架提供的sql脚本文件
这里写图片描述

这里写图片描述
照理来说我们可以使用source命令来执行这些sql脚本文件的,但不知为何,我就是不行。我就没纠结这个问题了,直接将以下三个sql脚本文件拖入Navicat for MySQL图形化工具中。

  1. activiti.mysql.create.engine.sql
  2. activiti.mysql.create.history.sql
  3. activiti.mysql.create.identity.sql

使用Activiti框架自动建表

在上面创建好了一个普通的java项目——activiti_02之后,要使用Activiti框架自动建表,还必须导入Activiti框架所需的jar包,那这些jar包到哪儿去找呢?还记得之前我讲过Activiti框架下的wars目录中存放的是Activiti框架官方的学习demo吗?所以读者可将wars目录中的activiti-rest.war文件解压缩,在activiti-5.13\wars\activiti-rest\WEB-INF\lib目录下可找到Activiti框架所需的所有jar包,如下:
这里写图片描述
接着在activiti_02项目下新建一个lib目录,将以上Activiti框架所需的所有jar包导入到lib目录中,除此之外,还要导入MySQL数据库驱动的jar包:
这里写图片描述
读者可千万别忘了这个jar包哟!!!

在没有提供xml配置文件的情况下使用Activiti框架自动建表

在src目录下创建一个cn.itcast.activiti包,并在该包下编写一个HelloWorld单元测试类,并在该类中编写如下单元测试方法:

public class HelloWorld { 
   
    /** * 使用activiti框架提供的自动建表方式创建23张表-----没有提供配置文件 */ 
    @Test
    public void test1() {
        // 创建一个流程引擎配置对象
        ProcessEngineConfiguration conf = 
                ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
        // 设置jdbc连接参数
        conf.setJdbcDriver("com.mysql.jdbc.Driver"); conf.setJdbcUrl("jdbc:mysql://localhost:3306/activiti_01");
        conf.setJdbcUsername("root");
        conf.setJdbcPassword("yezi");
        // 设置自动建表
        conf.setDatabaseSchemaUpdate("true");
        // 使用配置对象创建一个流程引擎对象,并且在创建过程中可以自动建表
        ProcessEngine processEngine = conf.buildProcessEngine();
    }

}

执行以上test1方法,即可在activiti_01数据库中创建好23张表。

在提供xml配置文件的情况下使用Activiti框架自动建表

在没有提供xml配置文件的情况时使用Activiti框架自动建表,我是把jdbc连接参数写死在程序中的,想都不要想,这种方式是愚蠢的。更合理的做法是把这些jdbc连接参数配置到一个配置文件中,而不是在java代码中写死。
在activiti_02项目下新建一个config源码目录,并在该目录下创建一个activiti-context.xml配置文件,内容如下:

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <!-- 配置一个流程引擎配置对象 -->
    <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <property name="jdbcDriver" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activiti_01"></property>
        <property name="jdbcUsername" value="root"></property>
        <property name="jdbcPassword" value="yezi"></property>
        <property name="databaseSchemaUpdate" value="true"></property>
    </bean>

    <!-- 配置一个流程引擎工厂bean,用于创建流程引擎对象 -->
    <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
        <property name="processEngineConfiguration" ref="processEngineConfiguration"></property>
    </bean>
</beans>

其实,以上id为processEngine的bean可不用配置,当然了配了也没关系,只不过是为后面的学习做铺垫而已,这里无伤大雅啊!
接着在HelloWorld单元测试类中编写如下单元测试方法:

public class HelloWorld { 
   

    /** * 使用activiti框架提供的自动建表方式创建23张表-----提供配置文件 */ 
    @Test
    public void test2() {
        // 获得一个流程引擎配置对象
        ProcessEngineConfiguration conf = ProcessEngineConfiguration
                .createProcessEngineConfigurationFromResource(
                        "activiti-context.xml", "processEngineConfiguration");
        // 使用配置对象创建一个流程引擎对象,并且在创建过程中可以自动建表
        ProcessEngine processEngine = conf.buildProcessEngine();
    }

}

执行以上test2方法,也可在activiti_01数据库中创建好23张表。

在提供默认配置文件的情况下使用Activiti框架自动建表

在实际开发中,建议在提供默认配置文件的情况下使用Activiti框架自动建表。但须注意:配置文件必须在类路径的根路径下,配置文件的名称必须为activiti-context.xml或者为activiti.cfg.xml,xml配置文件中必须配置流程引擎配置对象,id必须为processEngineConfiguration,且必须配置流程引擎工厂bean,id必须为processEngine
由此可知,我在config源码目录下编写的activiti-context.xml配置文件完全符合以上要求,activiti-context.xml配置文件的内容为:

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <!-- 配置一个流程引擎配置对象 -->
    <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <property name="jdbcDriver" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activiti_01"></property>
        <property name="jdbcUsername" value="root"></property>
        <property name="jdbcPassword" value="yezi"></property>
        <property name="databaseSchemaUpdate" value="true"></property>
    </bean>

    <!-- 配置一个流程引擎工厂bean,用于创建流程引擎对象 -->
    <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
        <property name="processEngineConfiguration" ref="processEngineConfiguration"></property>
    </bean>
</beans>

这个activiti-context.xml配置文件就是Activiti核心配置文件,主要配置流程引擎创建工具的基本参数和数据库连接池参数。
定义数据库配置参数:

  • jdbcUrl:数据库的JDBC URL
  • jdbcDriver:对应不同数据库类型的驱动
  • jdbcUsername:连接数据库的用户名
  • jdbcPassword:连接数据库的密码

基于JDBC参数配置的数据库连接会使用默认的MyBatis连接池。下面的参数可以用来配置连接池(来自MyBatis参数):

  • jdbcMaxActiveConnections:连接池中处于被使用状态的连接的最大值。默认为10
  • jdbcMaxIdleConnections:连接池中处于空闲状态的连接的最大值
  • jdbcMaxCheckoutTime:连接被取出使用的最长时间,超过时间会被强制回收。默认为20000(20秒)
  • jdbcMaxWaitTime:这是一个底层配置,让连接池可以在长时间无法获得连接时, 打印一条日志,并重新尝试获取一个连接。(避免因为错误配置导致沉默的操作失败)。默认为20000(20秒)

在这里我给出一个示例数据库配置:
这里写图片描述
当然了也可以使用javax.sql.DataSource。(比如,Apache Commons的DBCP):
这里写图片描述
以上就当作了解,哈哈,我也没这样写过!初次学习Activiti工作流框架的小白也不需要接触到这些配置,真到要用的时候,再回来看呗!
不说远了,接着再在HelloWorld单元测试类中编写如下单元测试方法:

public class HelloWorld { 
   

    /** * 使用activiti框架提供的自动建表方式创建23张表-----使用默认配置文件 */ 
    @Test
    public void test3() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    }

}

执行以上test3方法,同样也可在activiti_01数据库中创建好23张表。

了解Activiti框架提供的23张表

Activiti的后台是有数据库的支持的,所有的表都以ACT_开头。第二部分是表示用途的两个字母标识。用途也和服务的API对应。

  1. ACT_RE_*:’RE’表示repository。这个前缀的表包含了流程定义和流程静态资源 (图片,规则等等)。
  2. ACT_RU_*:’RU’表示runtime。这些是运行时的表,包含流程实例,任务,变量,异步任务等运行中的数据。Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。这样运行时表可以一直很小且速度很快。
  3. ACT_ID_*:’ID’表示identity。这些表包含身份信息,比如用户,组等等。
  4. ACT_HI_*:’HI’表示history。这些表包含历史数据,比如历史流程实例,变量,任务等等。
  5. ACT_GE_*:通用数据,用于不同场景下。

资源库流程规则表

  1. act_re_deployment:部署信息表
  2. act_re_model:流程设计模型部署表
  3. act_re_procdef:流程定义数据表

运行时数据库表

  1. act_ru_execution:运行时流程执行实例表
  2. act_ru_identitylink:运行时流程人员表,主要存储任务节点与参与者的相关信息
  3. act_ru_task:运行时任务节点表
  4. act_ru_variable:运行时流程变量数据表

历史数据库表

  1. act_hi_actinst:历史节点表
  2. act_hi_attachment:历史附件表
  3. act_hi_comment:历史意见表
  4. act_hi_identitylink:历史流程人员表
  5. act_hi_detail :历史详情表,提供历史变量的查询
  6. act_hi_procinst:历史流程实例表
  7. act_hi_taskinst:历史任务实例表
  8. act_hi_varinst:历史变量表

组织机构表

  1. act_id_group :用户组信息表
  2. act_id_info:用户扩展信息表
  3. act_id_membership:用户与用户组对应信息表
  4. act_id_user:用户信息表

这四张表很常见,基本的组织机构管理,关于用户认证方面建议还是自己开发一套,组件自带的功能太简单,使用中有很多需求难以满足。

通用数据表

  1. act_ge_bytearray:二进制数据表
  2. act_ge_property:属性数据表存储整个流程引擎级别的数据,初始化表结构时会默认插入三条记录

BPMN

业务流程建模与标注(Business Process Model and Notation,BPMN) ,描述流程的基本符号,包括这些图元如何组合成一个业务流程图(Business Process Diagram)。

Activiti框架的API使用

首先使用流程设计器插件设计一个请假流程,读者不妨按照如下gif动图来设计:
在这儿特此作出申明,由于【使用流程设计器插件设计一个请假流程.gif】大小已超过2M的限制,所以未能上传,但读者可点击使用流程设计器插件设计一个请假流程.gif进行下载并查看,给大家带来一些阅读上的麻烦,还请谅解!!!
读者在设计请假流程图时,必然要知道Assignee的意思,它指定任务的办理人。恐怕大家可能会有一个疑问:如果像上面那样设计的话,只有张三一个人能提交请假申请,其他人是提交不了申请的。我们现在是为了测试的方便,所以就指定死了,后面我们会有办法来动态地指定。

部署流程定义

部署流程定义即将请假规则应用到数据库里面去。部署流程定义操作的数据库表有:部署表(act_re_deployment)、流程定义表(act_re_procdef)和二进制表(act_ge_bytearray)。
想必大家肯定想知道部署流程定义怎样用代码来实现,我在HelloWorld单元测试类中编写如下单元测试方法:

public class HelloWorld { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    /** * 部署流程定义——即把请假规则应用到数据库里面去 */
    @Test
    public void test4() {

        // 创建一个部署构建器对象,用于加载流程定义文件(bpmn文件和png文件)
        DeploymentBuilder deploymentBuilder = processEngine.getRepositoryService().createDeployment();
        deploymentBuilder.addClasspathResource("qjlc.bpmn");
        deploymentBuilder.addClasspathResource("qjlc.png");
        // 部署,并返回一个部署对象(其实Deployment是一个接口)
        Deployment deployment = deploymentBuilder.deploy();
        System.out.println(deployment.getId());
    }

}

整个Activiti框架最核心的组件是ProcessEngine,部署流程定义就需要用到它,只要是跟工作流相关的任何操作都要使用到流程引擎对象。为了能让接下来编写的所有单元测试方法都能使用到它,故使其成为成员变量。以下这句代码:

Deployment deployment = deploymentBuilder.deploy();

返回的是一个部署对象,注意Deployment是一个接口。其实只要一调用deploy方法,Activiti框架就会帮我们发出sql语句,来操作数据库表。一旦我们部署一次,对应地就会向部署表(act_re_deployment)里面插入一条数据,如下:
这里写图片描述
同时向流程定义表(act_re_procdef)里面插入一条数据,如下:
这里写图片描述
流程定义表(act_re_procdef)里面的KEY_字段非常关键,KEY_字段的值是由流程图的id值来决定的。注意:KEY_这个字段代表的是流程定义的标识,也即说只要KEY_相同,那么说明它们就是同一个流程,但版本号可能不相同。读者不妨再部署两次,你将看到流程定义表(act_re_procdef)就是这样的:
这里写图片描述

查询流程定义

查询流程定义操作的数据表是流程定义表(act_re_procdef)。我在HelloWorld单元测试类中编写如下单元测试方法:

public class HelloWorld { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    /** * 查询流程定义 */ 
    @Test
    public void test5() {
        // 流程定义查询对象,用于查询流程定义表(act_re_procdef)
        ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery();
        // 以下查询的是所有的流程定义
        List<ProcessDefinition> list = query.list();
        for (ProcessDefinition pd : list) {
            System.out.println(pd.getId() + " " + pd.getName() + " " + pd.getVersion());
        }
    }
}

以上查询的是所有的流程定义,我们亦可根据流程定义的key来过滤,如下:

public class HelloWorld { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    /** * 查询流程定义 */ 
    @Test
    public void test5() {
        // 流程定义查询对象,用于查询流程定义表(act_re_procdef)
        ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery();
        // 根据流程定义的key来过滤
        query.processDefinitionKey("qjlc");
        List<ProcessDefinition> list = query.list();
        for (ProcessDefinition pd : list) {
            System.out.println(pd.getId() + " " + pd.getName() + " " + pd.getVersion());
        }
    }
}

万一除了要根据流程定义的key来过滤,还要排序,咋办?以码明示:

public class HelloWorld { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    /** * 查询流程定义 */ 
    @Test
    public void test5() {
        // 流程定义查询对象,用于查询流程定义表(act_re_procdef)
        ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery();
        // 根据流程定义的key来过滤
        query.processDefinitionKey("qjlc");
        // 添加排序条件
        query.orderByProcessDefinitionVersion().desc();
        List<ProcessDefinition> list = query.list();
        for (ProcessDefinition pd : list) {
            System.out.println(pd.getId() + " " + pd.getName() + " " + pd.getVersion());
        }
    }
}

上面是根据流程定义表(act_re_procdef)的版本号来降序排列的!那万一我们还要分页查询呢?以码明示:

public class HelloWorld { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    /** * 查询流程定义 */ 
    @Test
    public void test5() {
        // 流程定义查询对象,用于查询流程定义表(act_re_procdef)
        ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery();
        // 根据流程定义的key来过滤
        query.processDefinitionKey("qjlc");
        // 添加排序条件
        query.orderByProcessDefinitionVersion().desc();
        // 分页查询(伪代码)
        query.listPage("从哪开始查", "查几条");
        List<ProcessDefinition> list = query.list();
        for (ProcessDefinition pd : list) {
            System.out.println(pd.getId() + " " + pd.getName() + " " + pd.getVersion());
        }
    }
}

启动流程实例

什么是流程实例?根据某个流程定义的一次具体执行过程,就是一个流程实例。流程定义和流程实例是一对多的关系。在本例中,根据请假流程定义来具体地请一次假,就是启动流程实例了。
启动流程实例操作的数据表有流程实例表(act_ru_execution)、任务表(act_ru_task)。我在HelloWorld单元测试类中编写如下单元测试方法:

public class HelloWorld { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    /** * 启动流程实例 */
    @Test
    public void test6() {
        String processDefinitionId = "qjlc:2:104"; // 流程定义id
        ProcessInstance processInstance = processEngine.getRuntimeService()
                .startProcessInstanceById(processDefinitionId); // 根据请假流程定义来具体地请一次假,即启动流程实例
        System.out.println(processInstance.getId());
    }
}

运行以上方法,流程实例表(act_ru_execution)里面就会插入一条数据,如下:
这里写图片描述
ACT_ID_字段的值意味着流程向下推进到哪个地步了,上面表中ACT_ID_字段的值是usertask1,表示流程推进到【提交请假申请】这一步了。
除此之外,任务表(act_ru_task)里面也会插入一条数据,如下:
这里写图片描述
从上表可知,张三有一个任务——提交请假申请要办理。

查询任务

查询任务操作的数据表是任务表(act_ru_task)。我在HelloWorld单元测试类中编写如下单元测试方法:

public class HelloWorld { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    /** * 查询任务 */
    @Test
    public void test7() {
        // 任务查询对象,操作的是任务表(act_ru_task)
        TaskQuery query = processEngine.getTaskService().createTaskQuery();
        // 根据任务的办理人过滤
        query.taskAssignee("张三"); // 只查询张三的任务,其他人的任务不查
        // query.taskAssignee("李四"); 
        // query.taskAssignee("王五"); 
        List<Task> list = query.list();
        for (Task task : list) {
            System.out.println(task.getId() + "\t" + task.getName() + "\t" + task.getAssignee());
        }
    }
}

上面只查询了张三的任务,其他人的任务没查,因为从任务表(act_ru_task)知道张三有一个任务——提交请假申请要办理嘛。当流程一步一步向下推进,任务也会不断发生变化,具体地就要根据任务的办理人来过滤了。

办理任务

办理任务操作的数据表有任务表(act_ru_task)、流程实例表(act_ru_execution)。我在HelloWorld单元测试类中编写如下单元测试方法:

public class HelloWorld { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    /** * 办理任务 */
    @Test
    public void test8() {
        String taskId = "304"; // 任务的id
        processEngine.getTaskService().complete(taskId);
    }
}

我们查询出张三的任务之后,张三就要办理它,办理完之后,流程向下推进到【项目经理审批】这一步,故任务表(act_ru_task)就要发生变化,如下:
这里写图片描述
从上表可知,李四现在有一个任务——项目经理审批要办理了。除此之外,流程实例表(act_ru_execution)也要发生变化,如下:
这里写图片描述
上面表中ACT_ID_字段的值是usertask2,就已经表示流程推进到【项目经理审批】这一步了。
现在我们就要明确一个概念,流程一步一步向下推进,并不是我们去控制的,而是由工作流框架来帮我们推进的。我们要做的事就是将任务查出来,把它办理完,办理完之后,它会自动地由工作流框架来推进到下一个任务,所以,由工作流框架负责任务一步一步地向下推进,因为我们当时已经把流程部署进去了,也即说这个规则工作流框架是知道的,所以,我们只需要将任务查出来,把它办理完。流程实例表(act_ru_execution)也要发生变化,ACT_ID_这个字段的值更新了,因为流程向下推进了一步,所以ACT_ID_这个字段的值也需要更新。
下面就很简单了,将李四的任务查询出来,然后办理之。这样流程向下推进到【部门经理审批】这一步,故任务表(act_ru_task)就要发生变化,如下:
这里写图片描述
从上表可知,王五现在有一个任务——部门经理审批要办理了。除此之外,流程实例表(act_ru_execution)也要发生变化,如下:
这里写图片描述
上面表中ACT_ID_字段的值是usertask3,就已经表示流程推进到【部门经理审批】这一步了。
接着再将王五的任务查询出来,然后办理之。这样一来,任务表(act_ru_task)和流程实例表(act_ru_execution)就没有任何数据了,整个请假流程就走完了。

部署流程定义的两种方式

部署流程定义其实有两种方式,第一种方式是加载单个的流程定义文件,正如我之前所讲解的那样。下面再来讲一下这种方式,大家可以加深印象。
在cn.itcast.activiti包下再新建一个ActivitiAPITest单元测试类,并在该类中编写如下单元测试方法:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 部署流程定义 */
    @Test
    public void test1() {
        DeploymentBuilder deploymentBuilder = processEngine
                .getRepositoryService().createDeployment();
        // 方式一:加载单个的流程定义文件
        deploymentBuilder.addClasspathResource("qjlc.bpmn");
        deploymentBuilder.addClasspathResource("qjlc.png");
        deploymentBuilder.deploy();
    }

}

运行以上方法,即可在部署表(act_re_deployment)里面新增一条记录,如下:
这里写图片描述
部署流程定义的第二种方式是加载zip压缩文件。我们可以将process源码目录下的两个流程定义文件压缩为一个zip格式的压缩文件,比如process.zip。
这里写图片描述
如此一来,就要将test1方法修改为:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 部署流程定义 */
    @Test
    public void test1() {
        DeploymentBuilder deploymentBuilder = processEngine
                .getRepositoryService().createDeployment();
        // 方式二:加载zip压缩文件
        ZipInputStream zipInputStream = new ZipInputStream(this.getClass()
                .getClassLoader().getResourceAsStream("process.zip")); // 从类路径下读取process.zip压缩文件,并把它包装成一个输入流
        deploymentBuilder.addZipInputStream(zipInputStream );
        deploymentBuilder.deploy();
    }

}

运行以上方法,又在部署表(act_re_deployment)里面新增一条记录,如下:
这里写图片描述

查询部署信息

在ActivitiAPITest单元测试类中编写如下单元测试方法:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 查询部署信息 */
    @Test
    public void test2() {
        // 部署查询对象,查询部署表
        DeploymentQuery query = processEngine.getRepositoryService().createDeploymentQuery();
        List<Deployment> list = query.list();
        for (Deployment deployment : list) {
            System.out.println(deployment.getId() + "\t" + deployment.getDeploymentTime());
        }
    }

}

运行以上方法即可查询出部署表(act_re_deployment)中所有的记录。

删除部署信息

删除部署信息时,同时对应操作的数据库表有部署表(act_re_deployment)、流程定义表(act_re_procdef)和二进制表(act_ge_bytearray)。我在ActivitiAPITest单元测试类中编写如下单元测试方法:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 删除部署信息 */
    @Test
    public void test3() {
        String deploymentId = "801"; // 部署id
        processEngine.getRepositoryService().deleteDeployment(deploymentId);
    }

}

运行以上方法,部署表(act_re_deployment)里面ID_为801的部署信息被删除掉了,附带着流程定义表(act_re_procdef)里面DEPLOYMENT_ID_为801的流程定义信息也被删掉了,当然了二进制表(act_ge_bytearray)里面DEPLOYMENT_ID_为801的两条记录同样也被删除掉了。
void deleteDeployment(String deploymentId);方法有一个重载方法:

  • void deleteDeployment(String deploymentId, boolean cascade);
    cascade:是否级联删除,若cascade=false,则不级联删除;若cascade=true,则级联删除。

先将cascade置为false,为了便于测试,我先启动流程定义id为qjlc:1:4的流程实例, 从流程定义表(act_re_procdef)中可以很明显的看出该实例的DEPLOYMENT_ID_字段的值为1,如下:
这里写图片描述
启动流程定义id为qjlc:1:4的流程实例的代码为:

public class HelloWorld { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 启动流程实例 */
    @Test
    public void test6() {
        String processDefinitionId = "qjlc:1:4"; // 流程定义id
        ProcessInstance processInstance = processEngine.getRuntimeService()
                .startProcessInstanceById(processDefinitionId); // 根据请假流程定义来具体地请一次假,即启动流程实例
        System.out.println(processInstance.getId());
    }

}

运行以上方法,流程实例表(act_ru_execution)里面就会插入一条数据,如下:
这里写图片描述
ACT_ID_字段的值意味着流程向下推进到哪个地步了,上面表中ACT_ID_字段的值是usertask1,表示流程推进到【提交请假申请】这一步了。
除此之外,任务表(act_ru_task)里面也会插入一条数据,如下:
这里写图片描述
从上表可知,张三有一个任务——提交请假申请要办理。
启动流程实例完毕之后,把ActivitiAPITest单元测试类中的test3方法修改为:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 删除部署信息 * 删除部署信息时,同时对应操作的数据库表有act_re_deployment、act_re_procdef、act_ge_bytearray */
    @Test
    public void test3() {
        String deploymentId = "1"; // 部署id
        boolean cascade = false; // 是否级联删除,false表示不级联删
        processEngine.getRepositoryService().deleteDeployment(deploymentId, cascade);
    }

}

运行以上方法,这样当删除部署id为1的部署信息时,就会抛出一个org.apache.ibatis.exceptions.PersistenceException异常,可见并没有删除成功,因为有外键约束。
我们就想删除成功呢?则可以将cascade置为true,但不建议这么做。再次把ActivitiAPITest单元测试类中的test3方法修改为:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 删除部署信息 * 删除部署信息时,同时对应操作的数据库表有act_re_deployment、act_re_procdef、act_ge_bytearray */
    @Test
    public void test3() {
        String deploymentId = "1"; // 部署id
        boolean cascade = true;
        processEngine.getRepositoryService().deleteDeployment(deploymentId, cascade);
    }

}

这样当删除部署id为1的部署信息时,不禁发现部署表(act_re_deployment)里面ID_为1的部署信息被删除掉了,附带着流程定义表(act_re_procdef)里面DEPLOYMENT_ID_为1的流程定义信息也被删掉了,还有流程实例表(act_ru_execution)和任务表(act_ru_task)中PROC_DEF_ID_字段的值为qjlc:1:4的记录也被删除掉了。

获得流程定义文件名称和输入流

假设现在有这样一个需求:查询最新版本的流程定义数据。给出流程定义表(act_re_procdef),如下:
这里写图片描述
要实现这样一个需求,特简单,在ActivitiAPITest单元测试类中编写如下测试方法:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 查询最新版本的流程定义数据 */
    @Test
    public void test4() {
        // 流程定义查询对象,查询的是流程定义表(act_re_procdef)
        ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery();
        // 最新版本过滤
        query.latestVersion();
        List<ProcessDefinition> list = query.list();
        for (ProcessDefinition processDefinition : list) {
            System.out.println(processDefinition.getId());
        }
    }

}

好了,回到这一小节的主题,关于如何获得流程定义文件名称和输入流,我个人总结为两种方式。
【第一种方式】,根据客户端传过来的部署id进行获取。在ActivitiAPITest单元测试类中编写如下测试方法:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 查询一次部署对应的流程定义文件名称和输入流 * @throws IOException */
    @Test
    public void test5() throws IOException {
        String deploymentId = "201"; // 部署id
        // 获得两个流程定义文件的名称
        List<String> names = processEngine
                .getRepositoryService().getDeploymentResourceNames(deploymentId);
        for (String name : names) {
            System.out.println(name);
            // 获得两个流程定义文件对应的输入流
            InputStream in = processEngine
                    .getRepositoryService().getResourceAsStream(deploymentId, name);
            // 读取输入流写到指定的本地磁盘上
            FileUtils.copyInputStreamToFile(in, new File("F:\\" + name));
            in.close();
        }
    }

}

运行以上方法,除了在Eclipse控制台打印两个流程定义文件的名称,F盘上也会生成两个流程定义文件:

  • qjlc.bpmn
  • qjlc.png

【第二种方式】,根据客户端传过来的流程定义id进行获取。在ActivitiAPITest单元测试类中编写如下测试方法:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 获得文件名称和输入流 * @throws IOException */
    @Test
    public void test6() throws IOException {
        String processDefinitionId = "qjlc:2:104"; // 流程定义id
        // 直接获得png图片的名称
        // 根据流程定义id查询流程定义对象
        ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery();
        query.processDefinitionId(processDefinitionId);
        ProcessDefinition processDefinition = query.singleResult();
        // 根据流程定义对象获得png图片的名称
        String pngName = processDefinition.getDiagramResourceName();

        // 直接获得png图片对应的输入流
        InputStream pngStream = processEngine.getRepositoryService().getProcessDiagram(processDefinitionId);
        // 读取输入流写到指定的本地磁盘上
        FileUtils.copyInputStreamToFile(pngStream, new File("F:\\" + pngName));
        pngStream.close();
    }

}

这种方式只是获取到png图片的名称和其对应的输入流。

流程实例操作(启动、查询、删除)

启动流程实例

启动流程实例可分为两种方式:

  • 方式一:根据流程定义的id来启动流程实例
  • 方式二:根据流程定义的key来启动流程实例,建议使用

先讲第一种方式,我们之前启动流程实例时就是使用的这种方式。现在再讲一遍加深印象。给出流程定义表(act_re_procdef),如下:
这里写图片描述
现在我们想启动流程定义id为qjlc:2:104的流程实例,可在ActivitiAPITest单元测试类中编写如下测试方法:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 启动流程实例 */
    @Test
    public void test7() {
        String processDefinitionId = "qjlc:2:104"; // 流程定义的id
        // 方式一:根据流程定义的id来启动流程实例
        ProcessInstance processInstance = processEngine.getRuntimeService()
                .startProcessInstanceById(processDefinitionId);
        System.out.println(processInstance.getId());
    }

}

再讲第二种方式,这种方式也是被推荐使用的,即根据流程定义的key来启动流程实例,该方式可以自动选择最新版本的流程定义来启动流程实例。以码明示,将ActivitiAPITest单元测试类中的test7方法改为:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 启动流程实例 */
    @Test
    public void test7() {
        String processDefinitionKey = "qjlc"; // 流程定义的key
        // 方式二:根据流程定义的key来启动流程实例(建议)——可以自动选择最新版本的流程定义来启动流程实例
        ProcessInstance processInstance = processEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey);
        System.out.println(processInstance.getId());
    }

}

运行以上方法,启动的是流程定义id为qjlc:4:704的流程实例,给出流程实例表(act_ru_execution),如下:
这里写图片描述

查询流程实例

查询流程实例操作的是流程实例表(act_ru_execution)。在ActivitiAPITest单元测试类中编写如下测试方法:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 查询流程实例 */
    @Test
    public void test8() {
        // 流程实例查询对象,操作的是流程实例表(act_ru_execution)
        ProcessInstanceQuery query = processEngine.getRuntimeService().createProcessInstanceQuery();
        List<ProcessInstance> list = query.list();
        for (ProcessInstance processInstance : list) {
            System.out.println(processInstance.getId());
        }
    }

}

删除流程实例

何谓删除流程实例?举个例子,某人把请假流程启动之后,又不想请假了,那意味着后面的人就不用帮他审批了,所以就需要把这个流程实例删除掉。如要删除流程实例id为1001的那个流程实例,则可在ActivitiAPITest单元测试类中编写如下测试方法进行测试:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 删除流程实例 */
    @Test
    public void test9() {
        String processInstanceId = "1001"; // 流程实例id
        String deleteReason = "不请假了"; // 删除原因,任君写
        processEngine.getRuntimeService().deleteProcessInstance(processInstanceId, deleteReason);
    }

}

运行以上方法,流程实例id为1001的流程实例被删除掉了,附带着任务表(act_ru_task)里面EXECUTION_ID_字段为1001的那条记录也被删除掉了。

任务操作(查询、办理)

查询任务

查询任务对应操作的数据库表是任务表(act_ru_task)。在ActivitiAPITest单元测试类中编写如下测试方法进行测试:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 查询任务 */
    @Test
    public void test10() {
        // 任务查询对象,对应操作的数据库表是任务表(act_ru_task)
        TaskQuery query = processEngine.getTaskService().createTaskQuery();
        query.taskAssignee("张三");
        List<Task> list = query.list();
        for (Task task : list) {
            System.out.println(task.getId() + "\t" + task.getName());
        }
    }

}

上面只查询了张三的任务,其他人的任务没查,因为从任务表(act_ru_task)知道张三有一个任务——提交请假申请要办理嘛。当流程一步一步向下推进,任务也会不断发生变化,具体地就要根据任务的办理人来过滤了。

办理任务

办理任务操作的数据表有任务表(act_ru_task)、流程实例表(act_ru_execution)。我在ActivitiAPITest单元测试类中编写如下单元测试方法:

public class ActivitiAPITest { 
   

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /** * 办理任务 */
    @Test
    public void test11() {
        String taskId = "1104"; // 任务id
        processEngine.getTaskService().complete(taskId);
    }

}

我们查询出张三的任务之后,张三就要办理它,办理完之后,流程向下推进到【项目经理审批】这一步,故任务表(act_ru_task)就要发生变化,如下:
这里写图片描述
从上表可知,李四现在有一个任务——项目经理审批要办理了。除此之外,流程实例表(act_ru_execution)也要发生变化,如下:
这里写图片描述
上面表中ACT_ID_字段的值是usertask2,就已经表示流程推进到【项目经理审批】这一步了。
现在我们就要明确一个概念,流程一步一步向下推进,并不是我们去控制的,而是由工作流框架来帮我们推进的。我们要做的事就是将任务查出来,把它办理完,办理完之后,它会自动地由工作流框架来推进到下一个任务,所以,由工作流框架负责任务一步一步地向下推进,因为我们当时已经把流程部署进去了,也即说这个规则工作流框架是知道的,所以,我们只需要将任务查出来,把它办理完。流程实例表(act_ru_execution)也要发生变化,ACT_ID_这个字段的值更新了,因为流程向下推进了一步,所以ACT_ID_这个字段的值也需要更新。
下面就很简单了,将李四的任务查询出来,然后办理之。这样流程向下推进到【部门经理审批】这一步,故任务表(act_ru_task)就要发生变化,如下:
这里写图片描述
从上表可知,王五现在有一个任务——部门经理审批要办理了。除此之外,流程实例表(act_ru_execution)也要发生变化,如下:
这里写图片描述
上面表中ACT_ID_字段的值是usertask3,就已经表示流程推进到【部门经理审批】这一步了。
接着再将王五的任务查询出来,然后办理之。这样一来,任务表(act_ru_task)和流程实例表(act_ru_execution)就没有任何数据了,整个请假流程就走完了。

总结activiti中的几个对象

  • 几个和流程相关的对象
    • Deployment:部署对象,和部署表(act_re_deployment)对应
    • ProcessDefinition:流程定义对象,和流程定义表(act_re_procdef)对应
    • ProcessInstance:流程实例对象,和流程实例表(act_ru_execution)对应
    • Task:任务对象,和任务表(act_ru_task)对应
  • 几个Service对象
    • RepositoryService:操作部署、流程定义等静态资源信息
    • RuntimeService:操作流程实例,启动流程实例、查询流程实例、删除流程实例等动态信息
    • TaskService:操作任务,查询任务、办理任务等和任务相关的信息
    • HistoryService:操作历史信息的,查询历史信息
    • IdentityService:操作用户和组
  • 几个Query对象
    • DeploymentQuery:对应查询部署表(act_re_deployment)
    • ProcessDefinitionQuery:对应查询流程定义表(act_re_procdef)
    • ProcessInstanceQuery:对应查询流程实例表(act_ru_execution)
    • TaskQuery:对应查询任务表(act_ru_task)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/185428.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号