本篇文章是从网上购买的教程然后整理出来的笔记,供大家一起学习参考。
这一章节就是对工作流的基础概述,对此比较熟悉的可以直接跳过。
工作流的简单概念就是用于流程审批的。
如:请假审批流程、报销审批流程、出差审批流程、合同审批流程等。
工作流(Workflflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者 之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。 工作流引擎,主要是为了帮忙我们实现流程自动化控制,对应的Activiti引擎就可以实现自动化控制。 工作流管理系统是一个软件系统,它完成工作量的定义和管理,并按照在系统中预先定义好的工作流规则进行工作
工作流应用场景:
Activiti 是由 jBPM (BPM,Business Process Management 即业务流程管理) 的创建者 Tom Baeyens 离开 JBoss 之后建立的项目,构建在开发 jBPM 版本 1 到 4 时积累的多年经验的基础之上,旨在创建下一代的 BPM 解 决方案。 Activiti 是一个开源的工作流引擎,它实现了BPMN 2.0规范,可以发布设计好的流程定义,并通过api进行流程调 度。 Activiti 作为一个遵从 Apache 许可的工作流和业务流程管理开源平台,其核心是基于Java的超快速、超稳定的BPMN2.0 流程引擎,强调流程服务的可嵌入性和可扩展性,同时更加强调面向业务人员。
Activiti 流程引擎重点关注在系统开发的易用性和轻量性上。每一项 BPM 业务功能 Activiti 流程引擎都以服务的形 式提供给开发人员。通过使用这些服务,开发人员能够构建出功能丰富、轻便且高效的 BPM 应用程序。
Activiti是一个针对企业用户、开发人员、系统管理员的轻量级工作流业务管理平台,其核心是使用Java开发的快 速、稳定的BPMN 2.0流程引擎。Activiti是在ApacheV2许可下发布的,可以运行在任何类型的Java程序中,例如服 务器、集群、云服务等。Activiti可以完美地与Spring集成。同时,基于简约思想的设计使Activiti非常轻量级。
官网:https://www.activiti.org/
1、画流程定义模型:
遵守BPMN的流程规范,使用BPMN的流程定义工具,通过 流程符号 把整个业务流程定义出来,可以将流程定义文件字节流保存到模型数据表中(Model)。 其实用可视化工具画出来的每一个流程图都是一个.bpmn文件,在idea中也可以随时使用插件把bpmn文件进行可视化查看。
2、部署流程定义:
加载画好的流程定义文件,将它转换成流程定义数据(ProcessDefifinition),保存到流程定义数据表中。
3、启动流程(提交流程申请):
生成流程实例数据(ProcessInstance),生成第1个节点的任务数据(Task);
4、处理人审批流程节点任务:
完成任务审批,生成审批结果,生成下一节点任务数据。直至满足条件,流程走到结束节点。
其实就是使用bpmn的规范来定义画流程图,最终会形成一个bpmn文件。
业务流程模型注解(Business Process Modeling Notation - BPMN)是业务流程模型的一种标准图形注解。这个 标准是由对象管理组(Object Management Group - OMG)维护的。
标准的早期版本(1.2版以及之前)仅仅限制在模型上, 目标是在所有的利益相关者之间形成通用的理解, 在文 档,讨论和实现业务流程之上。 BPMN标准证明了它自己,现在市场上许多建模工具都使用了BPMN标准中的元素和结构。 BPMN规范的2.0版本,当前已经处于最终阶段了, 允许添加精确的技术细节在BPMN的图形和元素中, 同时制定 BPMN元素的执行语法。 通过使用XML语言来指定业务流程的可执行语法, BPMN规范已经演变为业务流程的语 言, 可以执行在任何兼容BPMN2的流程引擎中, 同时依然可以使用强大的图形注解。 目BPMN2.0是最新的版本,它用于在BPM上下文中进行布局和可视化的沟通。BPMN 2.0是使用一些符号来明确 业务流程设计流程图的一整套符号规范,它能增进业务建模时的沟通效率。
先了解在流程设计中常见的符号:
事件Event
活动 Activity
活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程; 其次,你还可以为活动指定不同的类型。常见活动如下:
网关 Gateway
网关用来处理决策:
排他网关(x)
**只有一条路径会被选择。**流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true时,继
续执行当前网关的输出流; 如果多条线路计算结果都是 true,则会执行第一个值为 true 的线路。如果所有网关计算结果没有true,则引 擎会抛出异常。 排他网关需要和条件顺序流结合使用,default 属性指定默认顺序流,当所有的条件不满足时会执行默认顺序流。
并行网关(+)
所有路径会被同时选择 。
分支: 并行执行所有输出顺序流,为每一条顺序流创建一个并行执行线路。
汇聚 :所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。
包容网关(o)
可以同时执行多条线路,也可以在网关上设置条件。
分支:计算每条线路上的表达式,当表达式计算结果为true时,创建一个并行线路并继续执行
汇聚:所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。
事件网关(o+)
专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。
定时器事件
开始定时器事件:
可以设置时间,定时开始启动流程实例。
中间定时器事件:
设定延迟时间,当完成任务1后,到达延时时间,流程才会走向任务2。
边界定时器事件:
用于向某节点上添加边界定时事件。在设定时间内没有完成,流程实例则自动走向下一节点。
依赖 | 版本 |
---|---|
Spring Boot | 2.5.0 |
Activiti | 7.1.0.M6 |
JDK1.8
MySQL5.7
Activiti 通过数据库的数据表来进行控制业务流程,对应支持的数据库如下:
Activiti数据库类型 | JDBC连接示例 | 备注 |
---|---|---|
h2 | jdbc:h2:tcp://localhost/activiti | 默认配置的数据库 |
mysql | jdbc:mysql://localhost:3306/activiti?autoReconnect=true | 使用mysql-connector- java数据库驱动程序进行 测试 |
oracle | jdbc:oracle:thin:@localhost:1521:xe | |
postgres | jdbc:postgresql://localhost:5432/activiti | |
db2 | jdbc:db2://localhost:50000/activiti | |
mssql | jdbc:sqlserver://localhost:1433;databaseName=activiti | 使用Microsoft JDBC驱动程序4.0(sqljdbc4.jar)和JTDS驱动程序进行了测 |
在mysql服务器中创建一个activiti01数据库。注意配置好数据库之后,执行流程引擎是会自动创建数据库表的。
这里就不粘贴maven的配置了,网上都有各种教程。
4.0.0 com.mengxuegu activiti-demo1 1.0-SNAPSHOT org.activiti activiti-engine 7.1.0.M6 mysql mysql-connector-java 8.0.23 org.mybatis mybatis 3.4.5 org.slf4j slf4j-log4j12 1.7.26 org.slf4j jcl-over-slf4j 1.7.26 junit junit 4.12 test commons-io commons-io 2.6
Activiti流程引擎通过名为的XML文件进行配置 activiti.cfg.xml 。
在 resources 目录下创建 activiti.cfg.xml 文件
注意:防止插入中文数据乱码,要加上字符集 characterEncoding=utf8
如果报错:Table ‘activiti01.act_ge_property’ doesn’t exist
解决:jdbcUrl的后面加上 nullCatalogMeansCurrent=true
如果想在控制台打印执行的sql日志,需要配置日志输出格式等,在 resources 目录下创建 log4j.properties 文件: 注意:记得修改日志文件路径。
log4j.rootCategory=debug, CONSOLE, LOGFILE log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{HH:mm:ss.SSS} %p [%t] %C.%M(%L) | %m%n # LOGFILE is set to be a File appender using a PatternLayout. log4j.appender.LOGFILE=org.apache.log4j.FileAppender log4j.appender.LOGFILE.File=D:/03-projectCode/ideaProject/study-activiti/activiti.log log4j.appender.LOGFILE.Append=true log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
加载类路径上的 activiti.cfg.xml ,并根据该文件中的配置构造一个流程引擎,和创建数据表。
方式如下:
import org.activiti.engine.ProcessEngine; import org.activiti.engine.ProcessEngines; import org.junit.Test; public class ActivitiTest01 { /*** 创建ProcessEngine流程引擎,自动创建 activiti 数据表 */ @Test public void getProcessEngine() { // 方式一:使用activiti提供的工具类ProcessEngines, // 调用 getDefaultProcessEngine 会默认读取resource下的activiti.cfg.xml文件, // 并创建 Activiti 流程引擎 和 创建数据库表 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); System.out.println(processEngine); // 方式二:以编程方式创建ProcessEngineConfiguration对象 // 1. 等同于方式一 //ProcessEngineConfiguration configuration =ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault(); //ProcessEngine processEngine = configuration.buildProcessEngine(); // 2. 自定义配置路径和文件名, 但流程引擎bean的id要等于processEngineConfiguration //ProcessEngineConfiguration configuration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml"); // 3.自定义:配置路径、文件名和流程引擎bean的id //ProcessEngineConfiguration configuration =ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml", "processEngineConfiguration"); } }
Activiti 会自动创建25张数据表。(可能不同版本生成的表的数量也会不同。)
数据库表命名
Acitiviti数据库中表的命名都是以 ACT_ 开头的。第二部分是一个两个字符用例表的标识。此用例大体与服务API是
匹配的。
ACT_GE_ :* GE 表示 general 。通用数据,各种情况都使用的数据 ,如存放资源文件(图片,规则等)。
ACT_HI_xxx : HI 表示history。就是这些表包含着历史的相关数据,如结束的流程实例(变量,任务等)。
ACT_RE_xxx : RE 表示repository。带此前缀的表包含的是静态信息,如,流程定义,流程的资源(图片,规则 等)。 Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直 很小速度很快。
ACT_RU_xxx : RU 表示 runtime。这是运行时的表存储着流程变量,用户任务,变量,职责(job)等运行时的 数据。Activiti只存储实例执行期间的运行时数据,当流程实例结束时,将删除这些记录。这就保证了这些运行时的表小且快。
ACT_EVT_ :EVT表示EVENT,流程引擎的通用事件日志记录表*,方便管理员跟踪处理。
表分类 | 表名 | 说明 |
---|---|---|
通用数据 | ||
act_ge_bytearray | 二进制数据表(流程图) | |
act_ge_property | 属性数据表,存储整个流程引擎级别的数据,初始化表结构时,会插入版本号信息等 | |
历史信息 | ||
act_hi_actinst | 历史节点表 | |
act_hi_attachment | 历史附件表 | |
act_hi_comment | 历史意见表 | |
act_hi_detail | 历史详情表,提供历史变量的查询 | |
act_hi_identitylink | 历史流程人员表,主要存储任务节点与参与者的相关信息 | |
act_hi_procinst | 历史流程实例表 | |
act_hi_taskinst | 历史任务实例表 | |
act_hi_varinst | 历史变量表 | |
流程定义 部署表 | ||
act_re_deployment | 部署信息表 | |
act_re_model | 流程设计模型表 | |
act_re_procdef | 流程定义数据表 | |
流程运行数据表 | ||
act_ru_deadletter_job | 作业死亡信息表,如果作业失败超过重试次数,则写入到此表 | |
act_ru_event_subscr | throwEvent、catchEvent时间监听信息表 | |
act_ru_execution | 运行时流程执行实例表 | |
act_ru_identitylink | 运行时流程人员表,主要存储任务节点与参与者的相关信息 | |
act_ru_integration | 运行时积分表 | |
act_ru_job | 定时异步任务数据表 | |
act_ru_suspended_job | 运行时作业暂停表, 比如流程中有一个定时任务,如果把这个任务停止工作了,这个任务写入到此表中 | |
act_ru_task | 运行时任务节点表 | |
act_ru_timer_job | 运行时定时器作业表 | |
act_ru_variable | 运行时流程变量数据表 | |
其他表 | ||
act_procdef_info | 流程定义的动态变更信息 | |
act_evt_log | 流程引擎的通用事件日志记录表 |
完善表字段
报错: MySQLSyntaxErrorException: Unknown column ‘VERSION_’ in ‘field list’
解决:因为 ACT_RE_DEPLOYMENT 表缺少 VERSION_ 和 PROJECT_RELEASE_VERSION_ 字段,执行以下语句
添加
ALTER TABLE ACT_RE_DEPLOYMENT ADD COLUMN VERSION_ VARCHAR(255); ALTER TABLE ACT_RE_DEPLOYMENT ADD COLUMN PROJECT_RELEASE_VERSION_ VARCHAR(255);
在 Activiti7.1.0.M6已经有了这些,不用执行上面sql。
前言
Activiti
Process Engine API 和服务
引擎 API 是与 Activiti 交互的最常见方式。 您可以从ProcessEngine中获取包含工作流/ BPM方法的各种服务。 ProcessEngine和服务对象是线程安全的。因此,您可以为整个服务器保留对其中之一的引用。
Service 是工作流引擎提供用于进行工作流部署、执行、管理的服务接口,我们使用对应Service接口可以操作对应的数据表。也就是说它提供了几个基础的service,里面包含的api供我们对流程进行操作。
Activiti7的Servcie核心接口
Service接口 | 说明 |
---|---|
RuntimeService | 运行时 Service,可以处理所有正在运行状态的流程实例和任务等 |
RepositoryService | 流程仓库 Service,主要用于管理流程仓库,比如流程定义的控制管理(部 署、删除、挂起、激活…) |
DynamicBpmnService | RepositoryService可以用来部署流程定义(使用xml形式定义好的),一旦 部署到Activiti(解析后保存到DB),那么流程定义就不会再变了,除了修改xml定义文件内容;而DynamicBpmnService就允许我们在程序运行过程中去修改流程定义,例如:修改流程定义中的分配角色、优先级、流程流转的条件。 |
TaskService | 任务 Service,用于管理和查询任务,例如:签收、办理等 |
HistoryService | 历史Service,可以查询所有历史数据,例如:流程实例信息、参与者信息、完成时间… |
ManagementService | 引擎管理Service,和具体业务无关,主要用于对Activiti流程引擎的管理和维护。 |
// 会在首次调用时初始化并构建一个流程引擎,此后始终返回相同的流程引擎。 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 引擎管理类 ManagementService managementService = processEngine.getManagementService(); // 动态修改流程管理类 DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService(); // 流程运行管理类 RuntimeService runtimeService = processEngine.getRuntimeService(); // 流程仓库管理类 RepositoryService repositoryService = processEngine.getRepositoryService(); // 任务管理类 TaskService taskService = processEngine.getTaskService(); // 历史管理类 HistoryService historyService = processEngine.getHistoryService(); // activiti 7 没有IdentityService和FormService接口 //IdentityService identityService = processEngine.getIdentityService(); // FormService formService = processEngine.getFormService();
官网下载地址
在 /resources 目录下创建 processes 目录,用于存放流程图
创建BPMN文件:右击 processes 目录,点击【New】–>【BpmnFile】
输入文件名称 leave ,点击【OK】按钮
绘制请假申请流程图
将 xxxx.bpmn 文件放在 /resources/processes/ 目录下,即 /resources/processes/leave.bpmn
设置当前流程图唯一标签 ID (也称为:流程的KEY):leaveProcess ,流程名称:请假流程。
追加一行命令。 (C:\User\mengxuegu\.IntelliJIdea\config )
注意:不能有空格,不然idea重启后打不开
-Dfile.encoding=UTF-8
生成png图片
将 bpmn 文件不方便随处打开提供给用户看,为了方便将流程图转成图片格式,方便随处打开演示;
并在部署流程时也需要 png 图片。
将 leave.bpmn 文件复制一份为 leave.xml
右击 leave.xml 文件,选择【Diagrams】–>【Show BPMN 2.0 Diagrams…】
如果找不到 Diagrams ,则重启 IDEA 工具
概述
将上面在设计器中定义的流程部署到activiti数据库中,就是流程定义部署。
一般情况下,流程定义(Process Definition)是在流程设计和开发阶段完成的,它描述了工作流程的规则、流程节点、流程步骤、流程之间的转移条件等信息。流程定义通常以一种特定的格式或者规范进行定义,如 BPMN(Business Process Model and Notation)等。
而流程部署(Process Deployment)是在流程准备运行阶段完成的,它将流程定义部署到流程引擎中以便执行。在流程部署过程中,流程定义会被加载到流程引擎中,并在流程引擎中生成对应的流程实例。
通常流程部署与流程定义是一对多的关系,比如一组相关联的业务可以做成多个流程图定义,然后把他们打包成zip后统一部署。
通过调用activiti的api将流程定义的 .bpm 和 png 两个文件一个一个添加部署到activiti中,也可以将两个文件打成 zip包进行部署。
.bpmn 流程定义文件部署
package com.mengxuegu.test; import org.activiti.engine.*; import org.activiti.engine.impl.util.ReflectUtil; import org.activiti.engine.repository.Deployment; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; import org.activiti.engine.task.TaskQuery; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.ZipInputStream; public class ActivitiTest02 { /*** 部署流程: * 1. ACT_RE_DEPLOYMENT 流程部署表,每执行一次部署,会插入一条记录 * 2. ACT_RE_PROCDEF 生成流程定义信息 * 其中 ACT_RE_DEPLOYMENT 与 ACT_RE_PROCDEF 表是一对多的关系, * ACT_RE_PROCDEF 每条记录对应一个流程的定义信息(如:小梦、小谷请假申请) * 3. ACT_GE_BYTEARRAY 流程资源表,插入资源数据,当前插入两条记录(.bpmn和.png资源) */ @Test public void deployByFile() { // 1. 实例化流程引擎实例 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 2. 获取流程定义和部署对象相关的 Service RepositoryService repositoryService = processEngine.getRepositoryService(); // 3. 创建部署对象进行流程的部署,定义一个流程的名字,把 .bpmn 和 .png 部署到数据库中 Deployment deployment = repositoryService.createDeployment() .name("请假申请流程") .addClasspathResource("processes/leave.bpmn") .addClasspathResource("processes/leave.png") .deploy(); // 4. 输出部署信息 System.out.println("部署ID:" + deployment.getId() ); System.out.println("部署名称:" + deployment.getName() ); } }
解决报错找不到字段
解决: ACT_RE_DEPLOYMENT 表缺少 VERSION_ 和 PROJECT_RELEASE_VERSION_ 字段,执行下面添加表字
段
ALTER TABLE ACT_RE_DEPLOYMENT ADD COLUMN VERSION_ VARCHAR(255); ALTER TABLE ACT_RE_DEPLOYMENT ADD COLUMN PROJECT_RELEASE_VERSION_ VARCHAR(255);
.zip 流程定义压缩包部署
将 leave.bpmn和 leave.png 压缩成 leave.zip ,放在类路径下的 processes/leave.zip 。 (所以正如上面所说,部署与定义是一对多的关系)
* 通过zip压缩包部署流程定义 */ @Test public void deployByZip() { // 1. 实例化流程引擎实例 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 2. 获取流程定义和部署对象相关的Service RepositoryService repositoryService = processEngine.getRepositoryService(); // 3. 流程部署 // 读取zip资源包,构成 InputStream 输入流 InputStream inputStream = ReflectUtil.getResourceAsStream("processes/leave.zip"); // 封装 ZipInputStream 输入流进行流程部署 ZipInputStream zipInputStream = new ZipInputStream(inputStream); Deployment deployment = repositoryService.createDeployment() .addZipInputStream(zipInputStream) .name("请假申请流程-压缩包") .deploy(); // 4. 输出部署信息 System.out.println("部署ID:" + deployment.getId()); System.out.println("部署名称:" + deployment.getName() ); }
运行后,压缩包中的 .bpm 文件和 图片文件会保存在activiti数据表中。
部署涉及的数据表
流程定义部署会涉及 activiti 的3张表:
注意:表中字段 KEY 是不同流程定义的唯一标识。
注意:
act_re_deployment 和 act_re_procdef 一对多关系,一次部署在流程部署表生成一条记录,但一次部署可以部署 多个流程定义,每个流程定义在流程定义表生成一条记录。每一个流程定义在 act_ge_bytearray 会存在两个资源 记录,bpmn 和 png。
建议:
一次部署一个流程,这样部署表和流程定义表是一对一有关系,方便读取流程部署及流程定义信息。
查询部署的流程定义数据 ACT_RE_PROCDEF :
/*** 查询部署的流程定义数据 ACT_RE_PROCDEF */ @Test public void getProcessDefinitionList() { // 1. 实例化流程引擎实例 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 2. 获取 RepositoryService RepositoryService repositoryService = processEngine.getRepositoryService(); // 3. 获取 ProcessDefinitionQuery 这里的leaveProcess就是画流程图时指定的唯一的key ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); ListdefinitionList = query.processDefinitionKey("leaveProcess") .orderByProcessDefinitionVersion() // 按版本号排序 .desc() // 降序 .list(); for (ProcessDefinition pd : definitionList) { System.out.println("流程部署ID:" + pd.getDeploymentId()); System.out.println("流程定义ID:" + pd.getId()); System.out.println("流程定义Key:" + pd.getKey()); System.out.println("流程定义名称:" + pd.getName()); System.out.println("流程定义版本号:" + pd.getVersion()); } }
输出内容:
流程部署ID:1
流程定义ID:leaveProcess:1:4
流程定义Key:leaveProcess
流程定义名称:请假流程
流程定义版本号:1
启动流程实例(提交申请)
流程定义部署后,然后可以通过 activiti 工作流管理业务流程了。例如上面部署好了请假流程,可以申请请假了。 针对部署好的流程定义,每次用户发起一个新的请假申请,就对应的启动一个新的请假流程实例;
类似于 java 类与 java 对象(实例)的关系,定义好类后,使用 new 创建一个对象(实例)使用,当然可以 new 多个对象(实例)。
例如:请假流程,小梦发起一个请假申请单,对应的就启动一个针对小梦的新流程实例;小王发起一个请假申请 单,也启动针对小王的新流程实例。
/** * 启动流程实例(提交申请) */ @Test public void startProcessInstance() { // 1. 实例化流程引擎实例 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 2. 获取 RuntimeService RuntimeService runtimeService = processEngine.getRuntimeService(); // 开启流程实例 (流程设计图唯一标识key) ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leaveProcess"); System.out.println("流程定义id:" + processInstance.getProcessDefinitionId()); System.out.println("流程实例id:" + processInstance.getId()); }
输出内容:
流程定义id:leaveProcess:1:4
流程实例id:2501
流程实例涉及的数据表
启动流程实例后,每个任务的办理人就可以查询自己当前的待办任务,然后进行办理任务。
/*** 查询指定人员的待办任务 */ @Test public void taskListByAssignee() { // 1. 实例化流程引擎实例 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 2. 获取 TaskService TaskService taskService = processEngine.getTaskService(); // 3. 根据流程唯一标识 key 和 任务办理人 查询任务 Listlist = taskService.createTaskQuery().processDefinitionKey("leaveProcess") .taskAssignee("meng") .list(); for (Task task : list) { System.out.println("流程实例id:" + task.getProcessInstanceId()); System.out.println("任务id:" + task.getId()); System.out.println("任务名称:" + task.getName()); System.out.println("任务办理人:" + task.getAssignee()); } }
输出内容:
流程实例id:2501
任务id:2505
任务负责人:meng
任务名称:领导审批
任务办理人查询待办任务,将待办任务进行完成。
先查询 meng 办理人任务,然后完成任务
再查询 xue 办理人任务,然后完成任务
这样此流程实例就完成结束。
/*** 完成待办任务 */ @Test public void completeTask() { // 1. 实例化流程引擎实例 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 2. 获取 TaskService TaskService taskService = processEngine.getTaskService(); // 3. 根据流程唯一标识 key 和 任务办理人 查询任务 Task task = taskService.createTaskQuery() .processDefinitionKey("leaveProcess") // 流程 Key .taskAssignee("meng") // 查询 meng 的任务 .taskAssignee("xue") .singleResult(); // 目前只有一条任务,则可以只获取一条 // 4. 完成任务(任务id) taskService.complete(task.getId()); }
查询流程办理历史信息,通过 HistoryService 历史数据对象来获取 HistoricActivityInstanceQuery 历史节点查询 对象。
/*** 查看流程办理历史信息 */ @Test public void historyInfo(){ // 1. 实例化流程引擎实例 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 2. 获取 HistoryService HistoryService historyService = processEngine.getHistoryService(); // 3. 获取节点历史记录查询对象 ACT_HI_ACTINST 表 HistoricActivityInstanceQuery query = historyService.createHistoricActivityInstanceQuery(); // 实例 id String processInstanceId = "10001"; Listlist = query.processInstanceId(processInstanceId) .orderByHistoricActivityInstanceStartTime() // 根据开始时间排序 asc 升序 .asc() .list(); for (HistoricActivityInstance hi : list) { System.out.print("流程定义ID: " + hi.getProcessDefinitionId()); System.out.print(",流程实例ID: " + hi.getProcessInstanceId()); System.out.print(",节点ID: " + hi.getActivityId()); System.out.print(",节点名称: " + hi.getActivityName()); System.out.print(",任务办理人:" + hi.getAssignee()); System.out.print(",开始时间:" + hi.getStartTime()); System.out.println("结束时间:" + hi.getEndTime()); } }
测试完基础的功能,终于到了重头戏了,好多网上的文档斌没有教你如何把activi7如何整合到你的实际项目中。
模块名:activiti-boot
4.0.0 com.mengxuegu activiti-boot 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-parent 2.5.0 7.1.0.M6 3.3.1 org.activiti activiti-spring-boot-starter ${activiti.version} org.activiti activiti-image-generator ${activiti.version} org.activiti activiti-json-converter ${activiti.version} org.apache.xmlgraphics batik-all 1.10 org.springframework.boot spring-boot-starter-web mysql mysql-connector-java com.baomidou mybatis-plus-boot-starter ${mybatis-plus.version} org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin
server: port: 8080 servlet: context-path: /workflow spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/activiti-boot?nullCatalogMeansCurrent=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true username: root password: root # activiti配置 activiti: #自动更新数据库结构 # true:适用开发环境,默认值。activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建 # false:适用生产环境。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常 # create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表) # drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎) database-schema-update: true # activiti7与springboot整合后默认不创建历史表,需要手动开启 db-history-used: true # 记录历史等级 可配置的历史级别有none, activity, audit, full # none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。 # activity:级别高于none,保存流程实例与流程行为,其他数据不保存。 # audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。 # full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。 history-level: full # 是否自动检查resources下的processes目录的流程定义文件 check-process-definitions: false # smtp服务器地址 mail-server-host: smtp.qq.com # SSL端口号 mail-server-port: 465 # 开启ssl协议 mail-server-use-ssl: true # 默认的邮件发送地址(发送人),如果activiti流程定义中没有指定发送人,则取这个值 mail-server-default-from: w736486962@qq.com # 邮件的用户名 mail-server-user-name: w736486962@qq.com # qq的smtp服务相关的授权码 mail-server-password: xxx填写自己qq邮箱的smtp授权码 # 关闭不自动添加部署数据 SpringAutoDeployment #deployment-mode: never-fail # 日志级别是debug才能显示SQL日志 logging: level: org.activiti.engine.impl.persistence.entity: debug
只有你自己创建了数据库建立了驱动连接,才会自动生成工作流的表。这里就不讲解怎么创建数据库了。
创建安全认证配置类 com.mengxuegu.config.SpringSecurityConfig 类
注意:activiti7 任务办理人,必须是当前可查询到的用户名
package com.mengxuegu.workflow.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.UserDetailsManager; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @Configuration public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { private Logger logger = LoggerFactory.getLogger(SpringSecurityConfig.class); /** * 内存 UserDetailsManager */ @Bean public UserDetailsService myUserDetailsService() { InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(); // 初始化账号角色数据 addGroupAndRoles(inMemoryUserDetailsManager); return inMemoryUserDetailsManager; } private void addGroupAndRoles(UserDetailsManager userDetailsManager) { // 注意:后面流程办理人,必须是当前存在的用户 username,这些都是为了后面指定流程班里人做准备 String[][] usersGroupsAndRoles = { {"meng", "123456", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"xue", "123456", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"gu", "123456", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"小梦", "123456", "ROLE_ACTIVITI_ADMIN", "GROUP_otherTeam"}, {"小学", "123456", "ROLE_ACTIVITI_ADMIN", "GROUP_otherTeam"}, {"小谷", "123456", "ROLE_ACTIVITI_ADMIN", "GROUP_otherTeam"} }; for (String[] user : usersGroupsAndRoles) { ListauthoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length)); logger.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]"); userDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]), authoritiesStrings.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList()))); } } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
创建项目启动类 com.mengxuegu.workflflow.WorkFlowApplication
package com.mengxuegu.workflow; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class WorkFlowApplication { public static void main(String[] args) { SpringApplication.run(WorkFlowApplication.class, args); } }
相关的 Activiti 服务接口,都直接依赖注入即可使用。
package com.mengxuegu.workflow.test; import org.activiti.engine.ProcessEngine; import org.activiti.engine.RepositoryService; import org.activiti.engine.repository.Deployment; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class ActivitiTest01 { @Autowired ProcessEngine processEngine; @Autowired RepositoryService repositoryService; /*** 获取 ProcessEngine 流程引擎,自动创建 activiti 数据表 */ @Test public void getProcessEngine() { // 获取 Activiti 流程引擎 和 创建数据库表, // 并在 ACT_RE_DEPLOYMENT 插入一条无用的数据 SpringAutoDeployment System.out.println(processEngine); } /*** 部署流程: */ @Test public void deployByFile() { // 1. 创建部署对象进行流程的部署,定义一个流程的名字,把 .bpmn 和 .png 部署到数据库中 Deployment deployment = repositoryService.createDeployment() .name("请假申请流程") .addClasspathResource("processes/leave.bpmn") .addClasspathResource("processes/leave.png") .deploy(); // 2. 输出部署信息 System.out.println("部署ID:" + deployment.getId() ); System.out.println("部署名称:" + deployment.getName() ); } }
如果报错如下:
Cause: java.sql.SQLSyntaxErrorException: Unknown column 'VERSION_' in 'field list'
Cause: java.sql.SQLSyntaxErrorException: Unknown column 'PROJECT_RELEASE_VERSION_' in 'field
解决: ACT_RE_DEPLOYMENT 表缺少 VERSION_ 和 PROJECT_RELEASE_VERSION_ 字段,执行下面添加表字
段
ALTER TABLE ACT_RE_DEPLOYMENT ADD COLUMN VERSION_ VARCHAR(255); ALTER TABLE ACT_RE_DEPLOYMENT ADD COLUMN PROJECT_RELEASE_VERSION_ VARCHAR(255);
为什么要整合
上面我们在 idea 上设计的流程模型,而每次要设计都要打开 idea 或其他设计流程工具来进行操作,这样不太方 便。 而实际上 Activiti 官方提供了 Web 版的流程设计工具 Activiti Modeler,可以直接整合到我们项目中。
在 Activiti 5.10 版本把原本独立的 Activiti Modeler 模块整合到了 Activiti Explorer 模块中,两者相结合使用起来 很方便, 通过 Modeler 设计的流程模型可以直接部署到引擎,也可以把已经部署的流程转换为Model从而在Modeler中编 辑。 在实际应用中也有这样的需求,把 Modeler 整合到业务系统中可以供管理员使用,或者作为BPM流程管理平台的 一部分存在。
但是在 Activiti 官方没有给出如何整合Modeler的文档,要我们自己整合。
首先需要从 Github下载 Activiti 5.22 版本源码
访问:https://github.com/Activiti/Activiti/releases/tag/activiti-5.22.0
下载 zip 格式的压缩包:https://github.com/Activiti/Activiti/archive/activiti-5.22.0.zip
如果解压过程中出现文件名过程问题可以换一个解压软件,比如7Zip。
解压 Activiti-activiti-5.22.0.zip ,然后进入 Activiti-activiti-5.22.0/modules 目录,
复制 activiti-webapp-explorer2 工程中如下图画红框的文件夹和文件,
粘贴到 activiti-boot 工程的 resources/static 目录下(static 目录创建)
拷贝到 com.mengxuegu.workflflow.activiti 包下面,如下图:
上下文路径对应 application.yml 文件中配置的 server.servlet.context-path=/workflow
在前端请求接口路径配置文件可参见:static/editor-app/confifiguration/url-confifig.js,
ACTIVITI.CONFIG = { 'contextRoot' : '/workflow' // '/activiti-explorer/service', };
汉化文件为两个json文件stencilset.json和zh-CN.json
需要的自取:
链接:https://pan.baidu.com/s/1az50zQQ6U-o-owqOfgt3RQ
提取码:1111
汉化页面文字,在 resources/static/ 目录下添加 stencilset.json 文件。
需要修改 StencilsetRestResource.java 类中 stencilset.json 为 static/stencilset.json (最前面不
要有 / );
InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("static/stencilset.json");
在 resources/static/editor-app/i18n 目录下添加 zh-CN.json 文件
修改 resources/static/editor-app/app.js 文件,将第51行的 $translateProvider.preferredLanguage(‘en’); 替换为以下内容:
// $translateProvider.preferredLanguage('en'); // 多语言支持 if("zh-CN" == navigator.language){ $translateProvider.preferredLanguage('zh-CN'); }else { $translateProvider.preferredLanguage('en'); }
启动项目时报错:
Error:Kotlin: Module was compiled with an incompatible version of Kotlin. The binary version of
its metadata is 1.5.1, expected version is 1.1.16.
解决:如下图重新编译下,然后再启动项目
package com.mengxuegu.workflow.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.activiti.editor.constants.ModelDataJsonConstants; import org.activiti.engine.RepositoryService; import org.activiti.engine.repository.Model; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /*** 流程模型管理 */ @Controller @RequestMapping("/model") public class ModelController { @Autowired RepositoryService repositoryService; @Autowired ObjectMapper objectMapper; /*** 创建空模型窗口: * 创建模型对象 * 设置对象值 * 存储模型对象(表act_re_model) * 存储模型对象基础数据(表act_ge_bytearray) * 跳转到ActivitiModeler,编辑流程图,存储流程图片和流程定义等(表act_ge_bytearray) */ @GetMapping("/create") public void create(HttpServletRequest request, HttpServletResponse response) { try { String name = "请假流程模型"; String key = "leaveProcess"; String desc = "请输入描述信息~"; int version = 1; //初始化一个空模型 Model model = repositoryService.newModel(); model.setName(name); model.setKey(key); model.setVersion(version); // 封裝模型json对象 ObjectNode modelObjectNode = objectMapper.createObjectNode(); modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, name); modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 0); modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, desc); model.setMetaInfo(modelObjectNode.toString()); // 存储模型对象(表 ACT_RE_MODEL ) repositoryService.saveModel(model); // 封装模型对象基础数据json串 {"id":"canvas","resourceId":"canvas","stencilset": {"namespace":"http://b3mn.org/stencilset/bpmn2.0#"},"properties":{"process_id":"未定义"}} ObjectNode editorNode = objectMapper.createObjectNode(); //editorNode.put("resourceId", "canvas"); ObjectNode stencilSetNode = objectMapper.createObjectNode(); stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#"); editorNode.replace("stencilset", stencilSetNode); // 标识key ObjectNode propertiesNode = objectMapper.createObjectNode(); propertiesNode.put("process_id", key); editorNode.replace("properties", propertiesNode); // 存储模型对象基础数据(表 ACT_GE_BYTEARRAY ) repositoryService.addModelEditorSource(model.getId(), editorNode.toString().getBytes("utf-8")); // 编辑流程模型时,只需要直接跳转此url并传递上modelId即可 response.sendRedirect(request.getContextPath() + "/modeler.html?modelId=" + model.getId()); } catch (Exception e) { e.printStackTrace(); } } }
重启mengxuegu-activiti-boot项目,
访问 http://localhost:8080/workflflow/model/create ,
会重定向到 http://localhost:8080/workflflow/modeler.html?modelId=xxxxxxxx
如果按钮文字是英文,而不是中文,则清除缓存重新打开浏览器访问 。
绘制流程定义模型涉及表
1、绘制请假流程:
领导审批:办理人 meng
总经理审批:办理人 xue
保存请求路径:http://localhost:8080/workflflow/model/{modelId}/save
原因: ModelSaveRestResource 类中使用 @RequestBody MultiValueMap 接收参数无法接收到。
解决:将 @RequestBody MultiValueMap 改为 @RequestParam MultiValueMap ,如下图:
上面已经整合Web 端Activiti Modeler流程模型设计器,并完成创建和保存流程模型设计图。 下面对流程定义模型进行查询、删除、导出模型zip压缩包、xml 文件、部署流程定义。
package com.mengxuegu.workflow.test; import org.activiti.engine.RepositoryService; import org.activiti.engine.repository.Model; import org.activiti.engine.repository.ModelQuery; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest public class ActivitiTest02Model { @Autowired RepositoryService repositoryService; /*** 查询所有流程模型 ACT_RE_MODEL */ @Test public void modelList() { // 获取模型查询对象 ModelQuery query = repositoryService.createModelQuery(); // 按模型创建时间 降序 排列 Listlist = query.orderByCreateTime() .desc() .list(); for (Model model : list) { System.out.print("模型id:" + model.getId()); System.out.print(",模型名称:" + model.getName()); System.out.print(",模型描述:" + model.getMetaInfo()); System.out.print(",模型标识key:" + model.getKey()); System.out.print(",模型版本号:" + model.getVersion()); System.out.print(",创建时间:" + model.getCreateTime()); System.out.println("更新时间:" + model.getLastUpdateTime()); } } }
通过模型ID删除模型:
/*** 删除模型: * 涉及表:ACT_RE_MODEL、ACT_GE_BYTEARRAY */ @Test public void deleteModel(){ // 模型id String id = "f2cd384f-c826-11eb-bbf0-2abb00fc727d"; repositoryService.deleteModel(id); System.out.println("删除成功"); }
导出下载模型图zip压缩包,压缩包中有 .bpmn20.xml 流程描述和 .png 图片资源, 在流程部署时,可以使用上传流程模型图zip压缩包进行部署.
/*** 导出下载模型图zip压缩包(.bpmn20.xml流程描述和.png图片资源) */ @Test public void exportZip() throws Exception{ // 模型id String id = "1c3b7d73-c833-11eb-98fd-ee5b4f08d062"; // 查询模型信息 Model model = repositoryService.getModel(id); if(model != null) { // 获取流程图 json 字节码 byte[] bpmnJsonBytes = repositoryService.getModelEditorSource(id); // 流程图 json 字节码转 xml 字节码 byte[] xmlBytes = bpmnJsonToXmlBytes(bpmnJsonBytes); if(xmlBytes == null) { System.out.println("模型数据为空-请先设计完整流程-再导出"); }else { // 压缩包文件名 String zipName = model.getName() + "." + model.getKey() + ".zip"; // 文件输出流 File file = new File("D:/" + zipName); FileOutputStream outputStream = new FileOutputStream(file); // 实例化zip压缩对象输出流 ZipOutputStream zipos = new ZipOutputStream(outputStream); // 指定压缩包里的 name.bpmn20.xml 文件名 zipos.putNextEntry(new ZipEntry(model.getName() + ".bpmn20.xml")); // 将xml写入压缩流 zipos.write(xmlBytes); // 查询png图片, byte[] pngBytes = repositoryService.getModelEditorSourceExtra(id); if(pngBytes != null) { // 指定压缩包里的 name.key.png 文件名 zipos.putNextEntry(new ZipEntry(model.getName() + "." + model.getKey() + ".png")); // 图片文件写到压缩包中 zipos.write(pngBytes); } zipos.closeEntry(); zipos.close(); System.out.println("导出成功"); } }else { System.out.println("模型不存在"); } } @Autowired ObjectMapper objectMapper; /*** 流程图保存的时候是json串,引擎认识的却是符合bpmn2.0规范的xml, * json 字节码转 xml 字节码 * @param jsonBytes * @return */ private byte[] bpmnJsonToXmlBytes(byte[] jsonBytes) throws IOException { if(jsonBytes == null) { return null; } // json转回BpmnModel对象 JsonNode modelNode = objectMapper.readTree(jsonBytes); BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode); if(bpmnModel.getProcesses().size() == 0) { return null; } // BpmnModel对象转xml字节数组 return new BpmnXMLConverter().convertToXML(bpmnModel); }
运行后,生成 请假流程模型.leaveProcess.zip 压缩后,解压后如下:
在流程部署时,可以只上传流程模型图 xml 文件进行部署
/*** 导出下载模型 xml 文件 */ @Test public void exportXml() throws Exception{ // 模型id String id = "1c3b7d73-c833-11eb-98fd-ee5b4f08d062"; ByteArrayInputStream in = null; // 获取流程图 json 字节码 byte[] bytes = repositoryService.getModelEditorSource(id); // json转xml字节数组 String filename = null; if(bytes != null) { JsonNode modelNode = objectMapper.readTree(bytes); BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode); if(bpmnModel.getProcesses().size() != 0) { // 转xml字节数组 byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel); in = new ByteArrayInputStream(bpmnBytes); // 如果流程名称为空,则取流程定义key filename = StringUtils.isEmpty(bpmnModel.getMainProcess().getName()) ? bpmnModel.getMainProcess().getId() : bpmnModel.getMainProcess().getName(); } }if(filename == null) { filename = "模型数据为空,请先设计流程,再导出"; in = new ByteArrayInputStream(filename.getBytes("UTF-8")); } // 文件输出流 FileOutputStream out = new FileOutputStream(new File("D:/" + filename + ".bpmn20.xml")); // 输入流,输出流的转换 IOUtils.copy(in, out); // 关闭流 out.close(); in.close(); System.out.println("下载模型 xml 文件成功"); }
运行后,生成文件: 请假申请流程.bpmn20.xml
通过模型数据 部署 流程定义
注意事项:
每个流程定义模型可以多次流程定义部署,activiti 通过流程定义模型中的标识 key 来判断是否为同一流程模型, 相同标识key则视为同一流程定义模型。
相同的标识key流程定义模型,每部署一次对应的新增一条流程定义数据,对应流程定义版本号会基于之前的加1。
上面注意事项,针对下一章讲解的通过 zip 和 xml 文件进行部署流程定义也是要一样的。
/** * 通过流程模型 进行 流程定义部署 * 流程图保存的时候是json串,引擎认识的却是符合bpmn2.0规范的xml, * 所以在首次的部署的时候要将json串转换为BpmnModel, * 再将BpmnModel转换成xml保存进数据库,以后每次使用就直接将xml转换成BpmnModel, * 这套操作确实有点啰嗦,实际项目中如果不用activiti自带的设计器,可以考虑用插件,直接生成的是 xml, * 或者自己开发设计器,在后端生成节点及其属性,引擎有现成的节点实体,如:开始节点StartEvent, 线SequenceFlow等。 * 涉及表: * ACT_RE_PROCDEF 新增数据: 流程定义数据 * ACT_RE_DEPLOYMENT 新增数据: 流程部署数据 * ACT_GE_BYTEARRAY 新增数据:将当前流程图绑定到此流程定义部署数据上 * ACT_RE_MODEL 更新部署id */ @Test public void deploy() throws Exception { // 模型id String id = "1c3b7d73-c833-11eb-98fd-ee5b4f08d062"; // 获取流程图 json 字节码 byte[] jsonBytes = repositoryService.getModelEditorSource(id); if (jsonBytes == null) { System.out.println("模型数据为空,请先设计流程并成功保存,再进行发布。"); return; } // 转xml字节数组 byte[] xmlBytes = bpmnJsonToXmlBytes(jsonBytes); if(xmlBytes == null){ System.out.println("数据模型不符要求,请至少设计一条主线流程。"); return; } // 流程图片字节码 byte[] pngBytes = repositoryService.getModelEditorSourceExtra(id); // 获取模型 Model model = repositoryService.getModel(id); // 流程定义xml名称 String processName = model.getName() + ".bpmn20.xml"; // 流程定义png名称 String pngName = model.getName() +"." + model.getKey() + ".png"; // 流程部署 Deployment deployment = repositoryService.createDeployment() .name(model.getName()) .addString(processName, new String(xmlBytes, "UTF-8")) // xml文件 .addBytes(pngName, pngBytes ) // 图片 .deploy(); // 更新 部署id 到模型对象(将模型与部署数据绑定) model.setDeploymentId(deployment.getId()); repositoryService.saveModel(model); System.out.println("部署完成"); }
部署流程定义涉及表:
部署流程报错:
解决:流程唯一标识 不能以数字开头,以字母开头
解决:两个图标箭头连线,箭头来源和目标多拉一点到图标上。
(Ctrl+A全选流程设计图,移动一下哪根连线没动说明这根有问题)
概要
通过流程定义模型,进行部署流程定义,部署后会生成流程定义数据(相当于java类),此时生成的流程定义数据
主要用于生成流程实例(相当于java对象),一个流程定义 Java 类对应的可以创建无数个 java 流程实例对象。
package com.mengxuegu.workflow.test; import org.activiti.engine.RepositoryService; import org.activiti.engine.repository.DeploymentBuilder; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.io.*; import java.util.zip.ZipInputStream; @SpringBootTest public class ActivitiTest03Deployment { @Autowired RepositoryService repositoryService; /*** 通过 .zip 流程压缩包进行部署的流程定义 */ @Test public void deployByZip() throws Exception { File file = new File("D:/请假流程模型.leaveProcess.zip"); String filename = file.getName(); // 压缩包输入流 ZipInputStream zipis = new ZipInputStream(new FileInputStream(file)); // 创建部署实例 DeploymentBuilder deployment = repositoryService.createDeployment(); // 添加zip流 deployment.addZipInputStream(zipis); // 部署名称 deployment.name(filename.substring(0, filename.indexOf("."))); // 执行部署流程定义 deployment.deploy(); System.out.println("zip压缩包方式部署流程定义完成"); }
部署流程定义涉及表:
ACT_RE_PROCDEF 新增数据: 流程定义数据 ACT_RE_DEPLOYMENT 新增数据: 流程部署数据 ACT_GE_BYTEARRAY 新增数据:将当前流程图绑定到此流程定义部署数据上 ACT_RE_MODEL 更新部署id
/** * 通过 .bpmn 或 .bpmn20.xml 流程文件进行部署的流程定义 * 缺陷:没有png流程图 */ @Test public void deployByBpmnFile() throws Exception { // .bpmn 文件 File file = new File("D:/leave.bpmn"); // .bpmn20.xml 文件 //File file = new File("D:/请假流程模型.bpmn20.xml"); String filename = file.getName(); // 输入流 FileInputStream input = new FileInputStream(file); // 创建部署实例 DeploymentBuilder deployment = repositoryService.createDeployment(); // bpmn20.xml 或 .bpmn (activiti5.10版本以上支持) deployment.addInputStream(filename, input); // 部署名称 //deployment.name(filename.substring(0, filename.indexOf("."))); // 执行流程定义部署 deployment.deploy(); System.out.println("通过 .bpmn 或 .bpmn20.xml 部署完成"); }
/** * 根据部署ID删除流程定义部署信息: * ACT_GE_BYTEARRAY、 * ACT_RE_DEPLOYMENT、 * ACT_RU_IDENTITYLINK、 * ACT_RE_PROCDEF、 * ACT_RU_EVENT_SUBSCR */ @Test public void delete() { try { // 部署ID String deploymentId = "9a14702d-c996-11eb-8eef-02466359b284"; // 不带级联的删除:如果有正在执行的流程,则删除失败抛出异常;不会删除 ACT_HI_和 历史表数据 repositoryService.deleteDeployment(deploymentId); // 级联删除:不管流程是否启动,都能可以删除;并删除历史表数据。 //repositoryService.deleteDeployment(deploymentId, true); System.out.println("删除流程定义部署信息成功"); } catch (Exception e) { e.printStackTrace(); if(e.getMessage().indexOf("a foreign key constraint fails") > 0) { System.out.println("有正在执行的流程,不允许删除"); }else { System.out.println("删除失败,原因:" + e.getMessage()); } } }
概要
部署好流程定义后,则可以进行查询、激活(启动)、挂起(暂停)、删除流程定义数据(上面讲的删除流程定义 部署信息就是),下载流程定义对应的 xml文件和 png 文件。
package com.mengxuegu.workflow.test; import org.activiti.engine.RepositoryService; import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.repository.ProcessDefinitionQuery; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest public class ActivitiTest04ProcessDefinition { @Autowired RepositoryService repositoryService; /** * 查询部署的流程定义数据 ACT_RE_PROCDEF * 需求:如果有多个相同流程定义标识key的流程时,只查询其最新版本 */ @Test public void getProcessDefinitionList() { // 1. 获取 ProcessDefinitionQuery ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); // 条件查询 query.processDefinitionNameLike("%请假%"); // 有多个相同标识key的流程时,只查询其最新版本 query.latestVersion(); // 按流程定义key升序排列 query.orderByProcessDefinitionKey() .asc(); // 当前查询第几页 int current = 1; // 每页显示多少条数据 int size = 5; // 当前页第1条数据下标 int firstResult = (current-1) * size; // 开始分页查询 ListdefinitionList = query.listPage(firstResult, size); for (ProcessDefinition pd : definitionList) { System.out.print("流程部署ID:" + pd.getDeploymentId()); System.out.print(",流程定义ID:" + pd.getId()); System.out.print(",流程定义Key:" + pd.getKey()); System.out.print(",流程定义名称:" + pd.getName()); System.out.print(",流程定义版本号:" + pd.getVersion()); System.out.println(",状态:" + (pd.isSuspended() ? "挂起(暂停)": "激活(开启)") ); } // 用于前端显示页面,总记录数 long total = query.count(); System.out.println("满足条件的流程定义总记录数:" + total); } }
流程定义被挂起:此流程定义下的所有流程实例不允许继续往后流转了,就被停止了。
流程定义被激活:此流程定义下的所有流程实例允许继续往后流转。
为什么会被挂起?
可能当前公司的请假流程发现了一些不合理的地方,然后就把此流程定义挂起。
流程不合理解决办法:
/** * 通过流程定义id,挂起或激活流程定义 */ @Test public void updateProcessDefinitionState() { // 流程定义ID String definitionId = "leaveProcess:2:b9227310-c8f4-11eb-acbf-aa2e2e85fc05"; // 流程定义对象 ProcessDefinition processDefinition =repositoryService.createProcessDefinitionQuery() .processDefinitionId(definitionId) .singleResult(); // 获取当前状态是否为:挂起 boolean suspended = processDefinition.isSuspended(); if (suspended) { // 如果状态是:挂起,将状态更新为:激活, // 参数1: 流程定义id;参数2:是否级联激活该流程定义下的流程实例;参考3:设置什么时间激活这 个流程定义,如果 null 则立即激活) repositoryService.activateProcessDefinitionById(definitionId, true, null); } else { // 如果状态是:激活,将状态更新为:挂起 // 参数 (流程定义id,是否挂起,激活时间) repositoryService.suspendProcessDefinitionById(definitionId, true,null); } }
对应 act_re_procdef 表中的 SUSPENSION_STATE_ 字段,1是激活,2是挂起
下载流程定义的 xml 和 png 文件,方便用户浏览当前流程定义是怎样的。
/** * 导出下载流程定义想着文件(.bpmn20.xml流程描述或.png图片资源) */ @Test public void exportProcessDefinitionFile() throws Exception{ // 流程定义ID String definitionId = "leaveProcess:2:b9227310-c8f4-11eb-acbf-aa2e2e85fc05"; // 查询流程定义数据 ProcessDefinition processDefinition = repositoryService.getProcessDefinition(definitionId); // xml 文件名 //String filename = processDefinition.getResourceName(); // png 图片名 String filename = processDefinition.getDiagramResourceName(); // 获取对应文件输入流 InputStream input = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), filename); // 创建输出流 File file = new File("/Users/mengxuegu/" + filename); FileOutputStream output = new FileOutputStream(file); // 流拷贝 IOUtils.copy(input, output); // 关闭流 input.close(); output.close(); System.out.println("流程定义文件导出成功:" + filename); }
由于文章字数限制,后续会有继续的更新,请关注订阅专栏~
上一篇:什么是网络爬虫?认识网络爬虫