activiti7关于SpringBoot前后端分离项目的详细教程
文章目录
本篇文章是从网上购买的教程然后整理出来的笔记,供大家一起学习参考。
一、Activiti工作流概述
这一章节就是对工作流的基础概述,对此比较熟悉的可以直接跳过。
1.1 什么是工作流
工作流的简单概念就是用于流程审批的。
如:请假审批流程、报销审批流程、出差审批流程、合同审批流程等。
工作流(Workflflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者 之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。 工作流引擎,主要是为了帮忙我们实现流程自动化控制,对应的Activiti引擎就可以实现自动化控制。 工作流管理系统是一个软件系统,它完成工作量的定义和管理,并按照在系统中预先定义好的工作流规则进行工作
1.2 工作流应用场景
工作流应用场景:
- 业务类:合同审批流程、订单处理流程、出入库审批流程等。
- 行政类:请假流程、出差流程、用车流程、办公用品申请流程等。
- 财务类:报销流程、支付流程等。
- 客户服务类:售后跟踪、客户投诉等。
1.3 什么是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.4 Activiti开发流程
1、画流程定义模型:
遵守BPMN的流程规范,使用BPMN的流程定义工具,通过 流程符号 把整个业务流程定义出来,可以将流程定义文件字节流保存到模型数据表中(Model)。 其实用可视化工具画出来的每一个流程图都是一个.bpmn文件,在idea中也可以随时使用插件把bpmn文件进行可视化查看。
2、部署流程定义:
加载画好的流程定义文件,将它转换成流程定义数据(ProcessDefifinition),保存到流程定义数据表中。
3、启动流程(提交流程申请):
生成流程实例数据(ProcessInstance),生成第1个节点的任务数据(Task);
4、处理人审批流程节点任务:
完成任务审批,生成审批结果,生成下一节点任务数据。直至满足条件,流程走到结束节点。
1.5 BPMN 2.0规范是什么
其实就是使用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是使用一些符号来明确 业务流程设计流程图的一整套符号规范,它能增进业务建模时的沟通效率。
1.6 BPMN 2.0 基本流程符号
先了解在流程设计中常见的符号:
事件Event
活动 Activity
活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程; 其次,你还可以为活动指定不同的类型。常见活动如下:
网关 Gateway
网关用来处理决策:
排他网关(x)
**只有一条路径会被选择。**流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true时,继
续执行当前网关的输出流; 如果多条线路计算结果都是 true,则会执行第一个值为 true 的线路。如果所有网关计算结果没有true,则引 擎会抛出异常。 排他网关需要和条件顺序流结合使用,default 属性指定默认顺序流,当所有的条件不满足时会执行默认顺序流。
并行网关(+)
所有路径会被同时选择 。
分支: 并行执行所有输出顺序流,为每一条顺序流创建一个并行执行线路。
汇聚 :所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。
包容网关(o)
可以同时执行多条线路,也可以在网关上设置条件。
分支:计算每条线路上的表达式,当表达式计算结果为true时,创建一个并行线路并继续执行
汇聚:所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。
事件网关(o+)
专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。
定时器事件
-
开始定时器事件:
可以设置时间,定时开始启动流程实例。
-
中间定时器事件:
设定延迟时间,当完成任务1后,到达延时时间,流程才会走向任务2。
-
边界定时器事件:
用于向某节点上添加边界定时事件。在设定时间内没有完成,流程实例则自动走向下一节点。
二、Activiti环境搭建和流程基础入门
2.1 框架版本号
依赖 | 版本 |
---|---|
Spring Boot | 2.5.0 |
Activiti | 7.1.0.M6 |
2.2 JDK
JDK1.8
2.2 MySQL
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数据库。注意配置好数据库之后,执行流程引擎是会自动创建数据库表的。
2.3 Maven 3.9配置
这里就不粘贴maven的配置了,网上都有各种教程。
2.4 基于 mave 构建项目
- 使用 idea 创建一个空工程 study-activiti
- 创建基于maven的项目,项目名: activiti-demo1
2.7 pom.xml添加依赖坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mengxuegu</groupId>
<artifactId>activiti-demo1</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--activiti核心依赖-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>7.1.0.M6</version>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.26</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.26</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
</project>
2.8 activiti.cfg.xml核心配置
Activiti流程引擎通过名为的XML文件进行配置 activiti.cfg.xml
。
在 resources 目录下创建 activiti.cfg.xml 文件
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<!--默认方式下,bean的id必须是processEngineConfiguration -->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!-- 配置数据源 -->
<property name="jdbcUrl"
value="jdbc:mysql://127.0.0.1:3306/activiti01?characterEncoding=utf8&nullCatalogMeansCurrent=true" />
<property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="root" />
<!-- activiti 数据库表生成策略 -->
<!--
自动更新数据库结构
true:适用开发环境,默认值。activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
false:适用生产环境。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
-->
<property name="databaseSchemaUpdate" value="true" />
</bean>
</beans>
注意:防止插入中文数据乱码,要加上字符集 characterEncoding=utf8
如果报错:Table ‘activiti01.act_ge_property’ doesn’t exist
解决:jdbcUrl的后面加上 nullCatalogMeansCurrent=true
2.9 log4j.properties配置日志
如果想在控制台打印执行的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
2.10 创建ProcessEngine流程引擎实例和数据表
加载类路径上的 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"); } }
2.11 Activiti 25张数据表分析
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 | 流程引擎的通用事件日志记录表 |
完善表字段
- Activiti 7的M4以上版本,部署流程定义时,报错如下:
报错: 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。
2.12 Activiti API服务接口
前言
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流程引擎的管理和维护。 |
- 核心 Service 接口实例获取方式如下:
// 会在首次调用时初始化并构建一个流程引擎,此后始终返回相同的流程引擎。
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();
三、Activiti流程实操入门
3.1 IDEA安装actiBPM插件
3.2 绘制流程定义模型
-
在 /resources 目录下创建 processes 目录,用于存放流程图
-
创建BPMN文件:右击 processes 目录,点击【New】–>【BpmnFile】
-
输入文件名称 leave ,点击【OK】按钮
绘制请假申请流程图
将 xxxx.bpmn 文件放在 /resources/processes/ 目录下,即 /resources/processes/leave.bpmn
- 鼠标左键拖拽右侧 开始事件 符号,将其拖下左侧界面上,同样的方式再拖拽其他图标
- 添加人工任务符号,修改 Name 节点名称 领导审批,和 Assignee 节点处理人:meng
- 添加人工任务符号:修改 Name 节点名称:总经理审批,和 Assignee 节点处理人:xue
- 添加结束事件符号
- 流程连线:点击图标的中心,会变成黑白色扇形,拖拽到另一图标,即可连接
- 点击流程图的空白处(不是点击流程符号):
设置当前流程图唯一标签 ID (也称为:流程的KEY):leaveProcess ,流程名称:请假流程。
- 最后检查,每个人工任务节点的 Name 和 Assignee 是否也上面一致。
3.3 解决中文乱码问题
- 打开 Settings 窗口,点击 Editor > File Encodings > 将字符编码均设置为 UTF-8
- 打开idea安装目录,在 idea.exe.vmoptions (32位系统)和 idea64.exe.vmoptions (64位系统)文件最后
追加一行命令。 (C:\User\mengxuegu\.IntelliJIdea\config
)
注意:不能有空格,不然idea重启后打不开
-Dfile.encoding=UTF-8
- 如果上面修改后,重启idea重新打开bpmn文件,还是乱码,则查找:
C:\Users\Administrator\AppData\Roaming\JetBrains\IntelliJIdea2020.
生成png图片
将 bpmn 文件不方便随处打开提供给用户看,为了方便将流程图转成图片格式,方便随处打开演示;
并在部署流程时也需要 png 图片。
-
将 leave.bpmn 文件复制一份为 leave.xml
-
右击 leave.xml 文件,选择【Diagrams】–>【Show BPMN 2.0 Diagrams…】
-
如果找不到 Diagrams ,则重启 IDEA 工具
- 打开后,点击上方导出按钮,保存即可。
- 将生成的 leave.png 图片拷贝到 resources/processes 目录下
- 可以把 leave.xml 文件删除
3.4 部署流程定义
概述
将上面在设计器中定义的流程部署到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张表:
- act_re_deployment 流程定义部署表,每部署一次增加一条记录
- act_re_procdef 流程定义表,部署每个新的流程定义都会在这张表中增加一条记录
注意:表中字段 KEY 是不同流程定义的唯一标识。
- act_ge_bytearray 流程资源表,bpmn的 xml 串和 流程图片,每次增加对应资源数据
注意:
act_re_deployment 和 act_re_procdef 一对多关系,一次部署在流程部署表生成一条记录,但一次部署可以部署 多个流程定义,每个流程定义在流程定义表生成一条记录。每一个流程定义在 act_ge_bytearray 会存在两个资源 记录,bpmn 和 png。
建议:
一次部署一个流程,这样部署表和流程定义表是一对一有关系,方便读取流程部署及流程定义信息。
3.5 查询流程定义
查询部署的流程定义数据 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(); List<ProcessDefinition> definitionList = 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 多个对象(实例)。
例如:请假流程,小梦发起一个请假申请单,对应的就启动一个针对小梦的新流程实例;小王发起一个请假申请 单,也启动针对小王的新流程实例。
3.6 启动流程实例代码
/** * 启动流程实例(提交申请) */
@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
流程实例涉及的数据表
- act_hi_actinst 流程实例执行的节点历史信息
- act_hi_identitylink 流程的参与用户历史信息
- act_hi_procinst 流程实例历史信息
- act_hi_taskinst 流程实例的任务历史信息
- act_ru_execution 流程运行中执行信息
- act_ru_identitylink 流程运行中参与用户信息
- act_ru_task 流程运行中任务信息
3.7 查询办理人待办任务
启动流程实例后,每个任务的办理人就可以查询自己当前的待办任务,然后进行办理任务。
/*** 查询指定人员的待办任务 */
@Test public void taskListByAssignee() {
// 1. 实例化流程引擎实例
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2. 获取 TaskService
TaskService taskService = processEngine.getTaskService();
// 3. 根据流程唯一标识 key 和 任务办理人 查询任务
List<Task> list = 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
任务名称:领导审批
3.8 完成待办任务
任务办理人查询待办任务,将待办任务进行完成。
-
先查询 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());
}
3.9 查询流程实例历史节点信息
查询流程办理历史信息,通过 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";
List<HistoricActivityInstance> list = 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());
}
}
四、SpringBoot 与 Activiti7 整合
测试完基础的功能,终于到了重头戏了,好多网上的文档斌没有教你如何把activi7如何整合到你的实际项目中。
4.1 创建模块 activiti-boot
模块名:activiti-boot
4.2 pom.xml 依赖坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mengxuegu</groupId>
<artifactId>activiti-boot</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/>
</parent>
<properties>
<activiti.version>7.1.0.M6</activiti.version>
<mybatis-plus.version>3.3.1</mybatis-plus.version>
</properties>
<dependencies>
<!-- Activiti -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- java绘制activiti流程图 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-image-generator</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- activiti json转换器-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- svg转png图片工具-->
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-all</artifactId>
<version>1.10</version>
</dependency>
<!-- web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-plus启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- SpringSecurity -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.3 创建application.yml配置文件
- 创建 application.yml 文件,activiti 7 相关配置如下:
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
4.4 创建数据库activiti-boot
只有你自己创建了数据库建立了驱动连接,才会自动生成工作流的表。这里就不讲解怎么创建数据库了。
4.5 整合SpringSecurity
- activiti7 与 SpringSecurity 强耦合,
创建安全认证配置类 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) {
List<String> authoritiesStrings = 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();
}
}
4.6 创建启动类
创建项目启动类 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); }
}
4.7 测试Activiti流程
相关的 Activiti 服务接口,都直接依赖注入即可使用。
- 将上面的 activiti-demo1 工程中绘制的bpmn流程图文件,拷贝如下工程的 resources 目录下
- 在 src/test 目录下创建测试类 com.mengxuegu.workflflow.test.ActivitiTest01
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);
五、整合流程模型设计器Activiti Modeler
为什么要整合
上面我们在 idea 上设计的流程模型,而每次要设计都要打开 idea 或其他设计流程工具来进行操作,这样不太方 便。 而实际上 Activiti 官方提供了 Web 版的流程设计工具 Activiti Modeler,可以直接整合到我们项目中。
在 Activiti 5.10 版本把原本独立的 Activiti Modeler 模块整合到了 Activiti Explorer 模块中,两者相结合使用起来 很方便, 通过 Modeler 设计的流程模型可以直接部署到引擎,也可以把已经部署的流程转换为Model从而在Modeler中编 辑。 在实际应用中也有这样的需求,把 Modeler 整合到业务系统中可以供管理员使用,或者作为BPM流程管理平台的 一部分存在。
但是在 Activiti 官方没有给出如何整合Modeler的文档,要我们自己整合。
5.1 下载源码
首先需要从 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。
5.2 拷贝目标源码
-
解压 Activiti-activiti-5.22.0.zip ,然后进入 Activiti-activiti-5.22.0/modules 目录,
-
复制 activiti-webapp-explorer2 工程中如下图画红框的文件夹和文件,
粘贴到 activiti-boot 工程的 resources/static 目录下(static 目录创建)
- 找到 activiti-modeler 工程下的 3个类,如下:
拷贝到 com.mengxuegu.workflflow.activiti 包下面,如下图:
5.3 修改上下文路径
- 找到 static/editor-app/confifiguration/url-confifig.js ,修改文件中的项目上下文路径,这样才能请求到上面的3 个接口
上下文路径对应 application.yml 文件中配置的 server.servlet.context-path=/workflow
在前端请求接口路径配置文件可参见:static/editor-app/confifiguration/url-confifig.js,
ACTIVITI.CONFIG = {
'contextRoot' : '/workflow' // '/activiti-explorer/service',
};
5.4 汉化Activiti Modeler
汉化文件为两个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");
- 汉化按钮文字,添加
zh-CN.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.
解决:如下图重新编译下,然后再启动项目
5.5 创建空的流程模型
- 创建一个 ModelController 模型控制器,用于创建空流程模型和跳转到模型设计页面 modeler.html
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
如果按钮文字是英文,而不是中文,则清除缓存重新打开浏览器访问 。
5.6 绘制请假流程定义模型
绘制流程定义模型涉及表
- ACT_RE_MODEL 流程模型基本信息表
- ACT_GE_BYTEARRAY 流程模型描述 json 串(注意不是xml串)和 流程图字节码。
1、绘制请假流程:
领导审批:办理人 meng
总经理审批:办理人 xue
- 点击 保存 按钮,会将绘制的 流程图 提交给后台进行保存操作(当前会报400错误,提交不成功)
保存请求路径:http://localhost:8080/workflflow/model/{modelId}/save
- 上面点击保存,会报400错误,
原因: ModelSaveRestResource 类中使用 @RequestBody MultiValueMap 接收参数无法接收到。
解决:将 @RequestBody MultiValueMap 改为 @RequestParam MultiValueMap ,如下图:
- 重启项目,再次保存测试 ok
六、流程定义模型管理Model
上面已经整合Web 端Activiti Modeler流程模型设计器,并完成创建和保存流程模型设计图。 下面对流程定义模型进行查询、删除、导出模型zip压缩包、xml 文件、部署流程定义。
6.1 查询已绘制的所有流程模型数据:
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();
// 按模型创建时间 降序 排列
List<Model> list = 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());
}
}
}
6.2 删除流程模型
通过模型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("删除成功");
}
6.3 导出下载模型图zip压缩包
导出下载模型图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 压缩后,解压后如下:
6.4 导出下载模型 xml 文件
在流程部署时,可以只上传流程模型图 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("部署完成");
}
部署流程定义涉及表:
- ACT_RE_PROCDEF 新增数据: 流程定义数据
- ACT_RE_DEPLOYMENT 新增数据: 流程部署数据
- ACT_GE_BYTEARRAY 新增数据:流程定义 xml 和 png 保存下来,对应绑定到此流程定义数据上
- ACT_RE_MODEL 更新部署id
部署流程报错:
- 报错:元素 ‘sequenceFlow’ 中必须包含属性 ‘targetRef’
解决:流程唯一标识 不能以数字开头,以字母开头
- 报错:元素’sequenceFlow’ 中必须包含属性’sourceRef’
解决:两个图标箭头连线,箭头来源和目标多拉一点到图标上。
(Ctrl+A全选流程设计图,移动一下哪根连线没动说明这根有问题)
七、流程定义部署管理 Deployment
概要
通过流程定义模型,进行部署流程定义,部署后会生成流程定义数据(相当于java类),此时生成的流程定义数据
主要用于生成流程实例(相当于java对象),一个流程定义 Java 类对应的可以创建无数个 java 流程实例对象。
7.1 通过 .zip 压缩包 部署 流程定义
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
7.2 通过 .bpmn 或 .bpmn.xml 文件 部署 流程定义
/**
* 通过 .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 部署完成");
}
7.3 删除流程定义部署信息
/**
* 根据部署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());
}
}
}
八、流程定义管理 ProcessDefifinition
概要
部署好流程定义后,则可以进行查询、激活(启动)、挂起(暂停)、删除流程定义数据(上面讲的删除流程定义 部署信息就是),下载流程定义对应的 xml文件和 png 文件。
8.1 分页条件查询流程定义
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;
// 开始分页查询
List<ProcessDefinition> definitionList = 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);
}
}
8.2 激活或挂起流程定义
-
流程定义被挂起:此流程定义下的所有流程实例不允许继续往后流转了,就被停止了。
-
流程定义被激活:此流程定义下的所有流程实例允许继续往后流转。
-
为什么会被挂起?
可能当前公司的请假流程发现了一些不合理的地方,然后就把此流程定义挂起。
流程不合理解决办法:
- **方式一:**可以先挂起流程定义,然后更新流程定义,然后激活流程定义。
- **方式二:**挂起了就不激活了,重新创建一个新的请假流程定义。
/**
* 通过流程定义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是挂起
8.3 下载流程定义的 xml 和 png 文件
下载流程定义的 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);
}
由于文章字数限制,后续会有继续的更新,请关注订阅专栏~