Activiti工作流框架学习笔记(一) 雨点打透心脏的1/2处 2022-06-16 05:38 1757阅读 0赞 # 工作流的概念 # 先看下面两张图: ![这里写图片描述][SouthEast] ![这里写图片描述][SouthEast 1] 对以上两张图进行说明: 1. 假设这两张图就是华谊兄弟的请假流程图 2. 图的组成部分: * 人物:范冰冰、冯小刚、王中军 * 事件(动作):请假、批准、不批准 通过以上分析我们就可以抽象成: ![这里写图片描述][SouthEast 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插件,开发人员可以通过插件直接绘画出业务流程图。 大家可能听说过业务流程图一嘴,这里给出两个业务流程图: * 请假流程图 ![这里写图片描述][SouthEast 3] * 不知道什么情况的业务流程图 ![这里写图片描述][SouthEast 4] ## Activiti框架的目录结构 ## Activiti框架的目录结构如下图所示: ![这里写图片描述][SouthEast 5] 你会发现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插件][4.5_eclipse_activiti]! 下面我就来用这个插件设计一个请假流程图: 【第一步】,建一个普通的java项目,例如activiti\_02,在src目录下右键→`New`→`Other...` ![这里写图片描述][SouthEast 6] 【第二步】,在弹出的对话框中,在输入项中输入activiti,快速找到Activiti Diagram,选中它,点击`Next` ![这里写图片描述][SouthEast 7] 【第三步】,在弹出的对话框中,写入流程图的名称,当然了亦可使用默认名称——MyProcess,然后点击`Next` ![这里写图片描述][SouthEast 8] 【第四步】,在最后弹出的对话框中直接点击`Finish` ![这里写图片描述][SouthEast 9] 【第五步】,观看以下gif动图,读者即可创建一个请假流程图 ![这里写图片描述][SouthEast 10] # 创建Activiti框架提供的数据库表 # ## 使用Activiti框架提供的sql脚本建表 ## Activiti框架提供了sql脚本文件用于建表,这些sql脚本文件就位于Activiti框架database目录下的create目录中,如下: ![这里写图片描述][SouthEast 11] 下面我就来使用Activiti框架提供的sql脚本建表,步骤如下: 【第一步】,手动创建一个数据库 ![这里写图片描述][SouthEast 12] 【第二步】,进入数据库,执行框架提供的sql脚本文件 ![这里写图片描述][SouthEast 13] ![这里写图片描述][SouthEast 14] 照理来说我们可以使用`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包,如下: ![这里写图片描述][SouthEast 15] 接着在activiti\_02项目下新建一个lib目录,将以上Activiti框架所需的所有jar包导入到lib目录中,除此之外,还要导入MySQL数据库驱动的jar包: ![这里写图片描述][SouthEast 16] 读者可千万别忘了这个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秒) 在这里我给出一个示例数据库配置: ![这里写图片描述][SouthEast 17] 当然了也可以使用javax.sql.DataSource。(比如,Apache Commons的DBCP): ![这里写图片描述][SouthEast 18] 以上就当作了解,哈哈,我也没这样写过!初次学习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][.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)里面插入一条数据,如下: ![这里写图片描述][SouthEast 19] 同时向流程定义表(act\_re\_procdef)里面插入一条数据,如下: ![这里写图片描述][SouthEast 20] 流程定义表(act\_re\_procdef)里面的KEY\_字段非常关键,KEY\_字段的值是由流程图的id值来决定的。注意:KEY\_这个字段代表的是流程定义的标识,也即说只要KEY\_相同,那么说明它们就是同一个流程,但版本号可能不相同。读者不妨再部署两次,你将看到流程定义表(act\_re\_procdef)就是这样的: ![这里写图片描述][SouthEast 21] ## 查询流程定义 ## 查询流程定义操作的数据表是流程定义表(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)里面就会插入一条数据,如下: ![这里写图片描述][SouthEast 22] ACT\_ID\_字段的值意味着流程向下推进到哪个地步了,上面表中ACT\_ID\_字段的值是usertask1,表示流程推进到【提交请假申请】这一步了。 除此之外,任务表(act\_ru\_task)里面也会插入一条数据,如下: ![这里写图片描述][SouthEast 23] 从上表可知,张三有一个任务——提交请假申请要办理。 ## 查询任务 ## 查询任务操作的数据表是任务表(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)就要发生变化,如下: ![这里写图片描述][SouthEast 24] 从上表可知,李四现在有一个任务——项目经理审批要办理了。除此之外,流程实例表(act\_ru\_execution)也要发生变化,如下: ![这里写图片描述][SouthEast 25] 上面表中ACT\_ID\_字段的值是usertask2,就已经表示流程推进到【项目经理审批】这一步了。 现在我们就要明确一个概念,流程一步一步向下推进,并不是我们去控制的,而是由工作流框架来帮我们推进的。我们要做的事就是将任务查出来,把它办理完,办理完之后,它会自动地由工作流框架来推进到下一个任务,所以,由工作流框架负责任务一步一步地向下推进,因为我们当时已经把流程部署进去了,也即说这个规则工作流框架是知道的,所以,我们只需要将任务查出来,把它办理完。流程实例表(act\_ru\_execution)也要发生变化,ACT\_ID\_这个字段的值更新了,因为流程向下推进了一步,所以ACT\_ID\_这个字段的值也需要更新。 下面就很简单了,将李四的任务查询出来,然后办理之。这样流程向下推进到【部门经理审批】这一步,故任务表(act\_ru\_task)就要发生变化,如下: ![这里写图片描述][SouthEast 26] 从上表可知,王五现在有一个任务——部门经理审批要办理了。除此之外,流程实例表(act\_ru\_execution)也要发生变化,如下: ![这里写图片描述][SouthEast 27] 上面表中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)里面新增一条记录,如下: ![这里写图片描述][SouthEast 28] 部署流程定义的第二种方式是加载zip压缩文件。我们可以将process源码目录下的两个流程定义文件压缩为一个**zip格式**的压缩文件,比如process.zip。 ![这里写图片描述][SouthEast 29] 如此一来,就要将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)里面新增一条记录,如下: ![这里写图片描述][SouthEast 30] ## 查询部署信息 ## 在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,如下: ![这里写图片描述][SouthEast 31] 启动流程定义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)里面就会插入一条数据,如下: ![这里写图片描述][SouthEast 32] ACT\_ID\_字段的值意味着流程向下推进到哪个地步了,上面表中ACT\_ID\_字段的值是usertask1,表示流程推进到【提交请假申请】这一步了。 除此之外,任务表(act\_ru\_task)里面也会插入一条数据,如下: ![这里写图片描述][SouthEast 33] 从上表可知,张三有一个任务——提交请假申请要办理。 启动流程实例完毕之后,把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),如下: ![这里写图片描述][SouthEast 34] 要实现这样一个需求,特简单,在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),如下: ![这里写图片描述][SouthEast 35] 现在我们想启动流程定义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),如下: ![这里写图片描述][SouthEast 36] ### 查询流程实例 ### 查询流程实例操作的是流程实例表(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)就要发生变化,如下: ![这里写图片描述][SouthEast 37] 从上表可知,李四现在有一个任务——项目经理审批要办理了。除此之外,流程实例表(act\_ru\_execution)也要发生变化,如下: ![这里写图片描述][SouthEast 38] 上面表中ACT\_ID\_字段的值是usertask2,就已经表示流程推进到【项目经理审批】这一步了。 现在我们就要明确一个概念,流程一步一步向下推进,并不是我们去控制的,而是由工作流框架来帮我们推进的。我们要做的事就是将任务查出来,把它办理完,办理完之后,它会自动地由工作流框架来推进到下一个任务,所以,由工作流框架负责任务一步一步地向下推进,因为我们当时已经把流程部署进去了,也即说这个规则工作流框架是知道的,所以,我们只需要将任务查出来,把它办理完。流程实例表(act\_ru\_execution)也要发生变化,ACT\_ID\_这个字段的值更新了,因为流程向下推进了一步,所以ACT\_ID\_这个字段的值也需要更新。 下面就很简单了,将李四的任务查询出来,然后办理之。这样流程向下推进到【部门经理审批】这一步,故任务表(act\_ru\_task)就要发生变化,如下: ![这里写图片描述][SouthEast 39] 从上表可知,王五现在有一个任务——部门经理审批要办理了。除此之外,流程实例表(act\_ru\_execution)也要发生变化,如下: ![这里写图片描述][SouthEast 40] 上面表中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) [SouthEast]: /images/20220616/ccce824cc97b4d92a4adc6baa61bdcdd.png [SouthEast 1]: /images/20220616/c430f47c98024b789ecfd488b7324e12.png [SouthEast 2]: /images/20220616/4fc4919d4ac0484eb5f99e4fecc2deed.png [SouthEast 3]: /images/20220616/cf89095989c747c7b4e2ace562610988.png [SouthEast 4]: /images/20220616/3cfa2f2eb46c48d19dcca70ddc2fe63a.png [SouthEast 5]: /images/20220616/f7f646fe5928444dbaaa7d264829cf2b.png [4.5_eclipse_activiti]: http://blog.csdn.net/yerenyuan_pku/article/details/71246773 [SouthEast 6]: /images/20220616/44eed2a18f4a4b6c8fe39089908b2e52.png [SouthEast 7]: /images/20220616/53decfdba41d4bef8431263e0ba76b56.png [SouthEast 8]: /images/20220616/64c62b4081724393ae3a5569def824b8.png [SouthEast 9]: /images/20220616/0bc605f2325542f891a6c9cdad6fb0f5.png [SouthEast 10]: /images/20220616/c782bae3e5dd47f189f8cb2aae0d52b9.png [SouthEast 11]: /images/20220616/7453f939527a49a1bcc0ca75e88b703f.png [SouthEast 12]: /images/20220616/343df0636b1e4472aedae51a5c8c3191.png [SouthEast 13]: /images/20220616/5cbc994fbd2246ca80e4b14b8eae89bf.png [SouthEast 14]: /images/20220616/a1f75350ad014bcaa863e85f39220995.png [SouthEast 15]: /images/20220616/b24bd6814ab443cfb9ed9766d42d3cef.png [SouthEast 16]: /images/20220616/37fd3ee0a9ec4404bd341c5e29b35fab.png [SouthEast 17]: /images/20220616/ab906dd1cc7c479c86c6f4a544869747.png [SouthEast 18]: /images/20220616/26c47e897a9d4c4c952c3bb330eb21af.png [.gif]: http://download.csdn.net/detail/yerenyuan_pku/9835457 [SouthEast 19]: /images/20220616/0b56cf12cd284b50b393d56c958d87ef.png [SouthEast 20]: /images/20220616/d3f701c7b73e4c80b62d4aebc8caaee5.png [SouthEast 21]: /images/20220616/ec9a774c03c042d6864456e13ebf308e.png [SouthEast 22]: /images/20220616/380d8362421a46b089f84123cb436f78.png [SouthEast 23]: /images/20220616/10e76cd0ffb64e43bcf4ade8bc8a6af6.png [SouthEast 24]: /images/20220616/a42feb496fad4e9ca129888f21bc79bb.png [SouthEast 25]: /images/20220616/44e90eb6775740859366e993bb379cf5.png [SouthEast 26]: /images/20220616/1f8e69893e1847cab9e12f446a43c146.png [SouthEast 27]: /images/20220616/7abff9934a8b46608f5b7c42aff924a6.png [SouthEast 28]: /images/20220616/478e719d7c904025a86feb3e1a2229a0.png [SouthEast 29]: /images/20220616/73dad03d5a2e402cad971f2357128e73.png [SouthEast 30]: /images/20220616/ef5cdf19140646938c2c26aca4a7f81f.png [SouthEast 31]: /images/20220616/6a478f3b5f0f4f09a2005ffede529d85.png [SouthEast 32]: /images/20220616/198e0a109b1d4285af4a12e3bd5406ad.png [SouthEast 33]: /images/20220616/17bcf72eda2945d29848fdc5eda6cda9.png [SouthEast 34]: /images/20220616/53a4d43c20be448ea21f5b090b97f413.png [SouthEast 35]: /images/20220616/ce35e3698c1f4ec1b4884aa935a997fd.png [SouthEast 36]: /images/20220616/f808c278f8c542788ef48f8ce40254f7.png [SouthEast 37]: /images/20220616/0f814f24fac941fa826c1120823e5368.png [SouthEast 38]: /images/20220616/fb4373fb08d443819de8e04528f1c230.png [SouthEast 39]: /images/20220616/1c940c14dd194e008c276a0341b5da2f.png [SouthEast 40]: /images/20220616/b3f64d50158e48358285fb9d98e10e09.png
还没有评论,来说两句吧...