一、简介
1.1。执照
Activiti在 Apache V2 许可下分发。
1.3. 来源
该发行版包含大部分源代码作为 jar 文件。Activiti 的源代码可以在 https://github.com/Activiti/Activiti
1.4. 所需软件
1.4.1。JDK 7+
Activiti 在高于或等于版本 7 的 JDK 上运行。转到Oracle Java SE 下载并单击“下载 JDK”按钮。该页面上也有安装说明。要验证您的安装是否成功,请java -version
在命令行上运行。那应该打印您的JDK的安装版本。
1.4.2. IDE
Activiti 开发可以使用您选择的 IDE 来完成。如果您想使用 Activiti Designer,那么您需要 Eclipse Kepler 或 Luna。从Eclipse 下载页面下载您选择的 Eclipse 发行版。解压缩下载的文件,然后您应该可以使用目录中的 eclipse 文件启动它eclipse
。在本用户指南中,还有一个关于安装我们的 eclipse 设计器插件的部分。
1.5。报告问题
每个有自尊的开发人员都应该阅读如何以聪明的方式提问。
完成后,您可以在用户论坛上发布问题和评论,并在我们的 JIRA 问题跟踪器中创建问题。
即使 Activiti 托管在 GitHub 上,也不应该使用 GitHub 的问题系统报告问题。如果您想报告问题,请不要创建 GitHub 问题,而是使用我们的 JIRA。 |
1.6. 实验功能
标有[EXPERIMENTAL]的部分不应被认为是稳定的。
包名中的所有类.impl.
都是内部实现类,不能认为是稳定的。但是,如果用户指南将这些类作为配置值提及,则它们是受支持的并且可以被认为是稳定的。
1.7. 内部实现类
在 jar 文件中,包中包含.impl.
(例如org.activiti.engine.impl.db
)的所有类都是实现类,应视为内部类。对实现类中的类或接口不提供稳定性保证。
2. 入门
2.1。一分钟版本
从Activiti 网站下载 Activiti UI WAR 文件后,按照以下步骤以默认设置运行演示设置。您将需要一个有效的Java 运行时和Apache Tomcat安装(实际上,任何 Web 容器都可以工作,因为我们只依赖 servlet 功能。但我们主要在 Tomcat 上进行测试)。
-
将下载的activiti-app.war复制到Tomcat的webapps目录下。
-
通过运行Tomcat的bin文件夹中的startup.bat或startup.sh脚本启动Tomcat
-
当 Tomcat 启动时,打开浏览器并转到 http://localhost:8080/activiti-app。使用管理员和密码测试登录。
而已!Activiti UI 应用程序默认使用内存中的 H2 数据库,如果您想使用其他数据库配置,请阅读更长的版本。
2.2. 活动设置
要安装 Activiti,您需要一个有效的Java 运行时和Apache Tomcat安装。还要确保正确设置了JAVA_HOME系统变量。执行此操作的方法取决于您的操作系统。
要运行 Activiti UI 和 REST Web 应用程序,只需将从 Activiti 下载页面下载的 WAR 复制到webapps
Tomcat 安装目录中的文件夹中。默认情况下,UI 应用程序使用内存数据库运行。
演示用户:
用户身份 | 密码 | 安全角色 |
---|---|---|
行政 |
测试 |
行政 |
现在您可以访问以下 Web 应用程序:
网络应用名称 | 网址 | 描述 |
---|---|---|
活动界面 |
流程引擎用户控制台。使用此工具启动新流程、分配任务、查看和声明任务等。 |
请注意,Activiti UI 应用程序演示设置是一种尽可能轻松快速地展示 Activiti 能力和功能的方式。然而,这并不意味着它是使用 Activiti 的唯一方式。由于 Activiti只是一个 jar,它可以嵌入到任何 Java 环境中:使用 swing 或在 Tomcat、JBoss、WebSphere 等上。或者您可以选择将 Activiti 作为典型的独立 BPM 服务器运行。如果在 Java 中是可能的,那么在 Activiti 中也是可能的!
2.3. Activiti 数据库设置
正如在一分钟的演示设置中所说,Activiti UI 应用程序默认运行内存中的 H2 数据库。要使用独立的 H2 或其他数据库运行 Activiti UI 应用程序,应更改 Activiti UI Web 应用程序的 WEB-INF/classes/META-INF/activiti-app 中的 activiti-app.properties。
2.4. 包含 Activiti jar 及其依赖项
要包含 Activiti jar 及其依赖库,我们建议使用Maven(或Ivy),因为它大大简化了我们和您双方的依赖管理。按照http://www.activiti.org/community.html#maven.repository上的说明在您的环境中包含必要的 jar。
或者,如果您不想使用 Maven,您可以自己在项目中包含这些 jar。Activiti 下载 zip 包含一个文件夹libs
,其中包含所有 Activiti jar(和源 jar)。依赖项不是以这种方式交付的。Activiti 引擎所需的依赖项是(使用生成的mvn dependency:tree
):
org.activiti:activiti-engine:jar:6.x +- org.activiti:activiti-bpmn-converter:jar:6.x:compile | \- org.activiti:activiti-bpmn-model:jar:6.x:compile | +- com.fasterxml.jackson.core:jackson-core:jar:2.2.3:compile | \- com.fasterxml.jackson.core:jackson-databind:jar:2.2.3:compile | \- com.fasterxml.jackson.core:jackson-annotations:jar:2.2.3:compile +- org.activiti:activiti-process-validation:jar:6.x:compile +- org.activiti:activiti-image-generator:jar:6.x:compile +- org.apache.commons:commons-email:jar:1.2:compile | +- javax.mail:mail:jar:1.4.1:compile | \- javax.activation:activation:jar:1.1:compile +- org.apache.commons:commons-lang3:jar:3.3.2:compile +- org.mybatis:mybatis:jar:3.3.0:compile +- org.springframework:spring-beans:jar:4.1.6.RELEASE:compile | \- org.springframework:spring-core:jar:4.1.6.RELEASE:compile +- joda-time:joda-time:jar:2.6:compile +- org.slf4j:slf4j-api:jar:1.7.6:compile +- org.slf4j:jcl-over-slf4j:jar:1.7.6:compile
注意:只有在使用邮件服务任务时才需要邮件罐子。
使用Activiti 源代码mvn dependency:copy-dependencies
的一个模块可以轻松下载所有依赖项。
2.5. 下一步
玩转Activiti UI Web 应用程序是熟悉 Activiti 概念和功能的好方法。但是,Activiti 的主要目的当然是在您自己的应用程序中启用强大的 BPM 和工作流功能。以下章节将帮助您熟悉如何在您的环境中以编程方式使用 Activiti:
-
配置章节将教你如何设置 Activiti 以及如何获取
ProcessEngine
类的实例,这是你对 Activiti 的所有引擎功能的中心访问点。* API 章节将引导您了解构成 Activiti API 的服务。这些服务以方便而强大的方式提供了 Activiti 引擎功能,并且可以在任何 Java 环境中使用。*有兴趣深入了解 BPMN 2.0,即 Activiti 引擎的流程编写格式吗?然后继续阅读 BPMN 2.0 部分。
3.配置
3.1。创建流程引擎
Activiti 流程引擎是通过一个名为activiti.cfg.xml
. 请注意,如果您使用Spring 样式构建流程引擎,则这不适用。
获得 , 的最简单方法ProcessEngine
是使用org.activiti.engine.ProcessEngines
该类:
1 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine()
这将activiti.cfg.xml
在类路径中查找一个文件,并根据该文件中的配置构造一个引擎。以下代码段显示了一个示例配置。以下部分将详细概述配置属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 <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" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
<property name="jdbcDriver" value="org.h2.Driver" />
<property name="jdbcUsername" value="sa" />
<property name="jdbcPassword" value="" />
<property name="databaseSchemaUpdate" value="true" />
<property name="asyncExecutorActivate" value="false" />
<property name="mailServerHost" value="mail.my-corp.com" />
<property name="mailServerPort" value="5025" />
</bean>
</beans>
请注意,配置 XML 实际上是一个 Spring 配置。这并不意味着 Activiti 只能在 Spring 环境中使用!我们只是在内部利用 Spring 的解析和依赖注入功能来构建引擎。
ProcessEngineConfiguration 对象也可以使用配置文件以编程方式创建。也可以使用不同的 bean id(例如,见第 3 行)。
1
2
3
4
5 ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault();
ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(String resource);
ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(String resource, String beanName);
ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(InputStream inputStream);
ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(InputStream inputStream, String beanName);
也可以不使用配置文件,并基于默认值创建配置(有关更多信息,请参阅不同支持的类)。
1
2 ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration();
所有这些ProcessEngineConfiguration.createXXX()
方法都返回 a ProcessEngineConfiguration
,如果需要可以进一步调整。调用buildProcessEngine()
操作后,ProcessEngine
创建了一个:
1
2
3
4
5 ProcessEngine processEngine = ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration()
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE)
.setJdbcUrl("jdbc:h2:mem:my-own-db;DB_CLOSE_DELAY=1000")
.setAsyncExecutorActivate(false)
.buildProcessEngine();
3.2. ProcessEngineConfiguration bean
activiti.cfg.xml
必须包含一个具有 id 的bean 'processEngineConfiguration'
。
1 <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
然后使用这个 bean 来构造ProcessEngine
. 有多个类可用于定义processEngineConfiguration
. 这些类代表不同的环境,并相应地设置默认值。最佳实践是选择与您的环境(最匹配)的类,以尽量减少配置引擎所需的属性数量。目前提供以下类(未来版本中将提供更多类):
-
org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration:流程引擎以独立的方式使用。Activiti 将处理交易。默认情况下,只有在引擎启动时才会检查数据库(如果没有 Activiti 架构或架构版本不正确,则会引发异常)。
-
org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration:这是一个用于单元测试的便利类。Activiti 将处理交易。默认使用 H2 内存数据库。当引擎启动和关闭时,将创建和删除数据库。使用它时,可能不需要额外的配置(例如使用作业执行器或邮件功能时除外)。
-
org.activiti.spring.SpringProcessEngineConfiguration:在 Spring 环境中使用流程引擎时使用。有关更多信息,请参阅Spring 集成部分。
-
org.activiti.engine.impl.cfg.JtaProcessEngineConfiguration:当引擎以独立模式运行时使用,带有 JTA 事务。
3.3. 数据库配置
有两种方法可以配置 Activiti 引擎将使用的数据库。第一个选项是定义数据库的 JDBC 属性:
-
jdbcUrl:数据库的 JDBC URL。
-
jdbcDriver:特定数据库类型的驱动程序的实现。
-
jdbcUsername:连接数据库的用户名。
-
jdbcPassword:连接数据库的密码。
基于提供的 JDBC 属性构建的数据源将具有默认的MyBatis连接池设置。可以选择设置以下属性来调整该连接池(取自 MyBatis 文档):
-
jdbcMaxActiveConnections:连接池在任何时候最多可以包含的活动连接数。默认值为 10。
-
jdbcMaxIdleConnections:连接池在任何时候最多可以包含的空闲连接数。
-
jdbcMaxCheckoutTime :在强制返回连接之前,可以从连接池中签出连接的时间量(以毫秒为单位)。默认值为 20000(20 秒)。
-
jdbcMaxWaitTime:这是一个低级别设置,它使池有机会打印日志状态并在连接异常长时间的情况下重新尝试获取连接(以避免在池配置错误时永远静默失败)默认值是 20000(20 秒)。
示例数据库配置:
1
2
3
4 <property name="jdbcUrl" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
<property name="jdbcDriver" value="org.h2.Driver" />
<property name="jdbcUsername" value="sa" />
<property name="jdbcPassword" value="" />
我们的基准测试表明,在处理大量并发请求时,MyBatis 连接池并不是最有效或最有弹性的。因此,建议给我们一个javax.sql.DataSource
实现并将其注入到流程引擎配置中(例如 DBCP、C3P0、Hikari、Tomcat 连接池等):
1
2
3
4
5
6
7
8
9
10
11
12 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" >
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/activiti" />
<property name="username" value="activiti" />
<property name="password" value="activiti" />
<property name="defaultAutoCommit" value="false" />
</bean>
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="dataSource" ref="dataSource" />
...
请注意,Activiti 不附带允许定义此类数据源的库。所以你必须确保这些库在你的类路径上。
无论您使用的是 JDBC 还是数据源方法,都可以设置以下属性:
-
databaseType:通常不需要指定此属性,因为它是从数据库连接元数据中自动分析的。仅应在自动检测失败的情况下指定。可能的值:{h2、mysql、oracle、postgres、mssql、db2}。此设置将确定将使用哪些创建/删除脚本和查询。有关受支持的类型的概述,请参阅受支持的数据库部分。
-
databaseSchemaUpdate:允许设置策略以在流程引擎启动和关闭时处理数据库模式。
-
false
(默认):在创建流程引擎时根据库检查数据库模式的版本,如果版本不匹配则抛出异常。 -
true
:在构建流程引擎时,将执行检查并在必要时执行架构更新。如果架构不存在,则会创建它。 -
create-drop
:在创建流程引擎时创建架构,并在关闭流程引擎时删除架构。
-
3.4. JNDI 数据源配置
默认情况下,Activiti 的数据库配置包含在每个 Web 应用程序的 WEB-INF/classes 中的 db.properties 文件中。这并不总是理想的,因为它需要用户修改 Activiti 源中的 db.properties 并重新编译 war 文件,或者在每次部署时分解 war 并修改 db.properties。
通过使用JNDI(Java Naming and Directory Interface)获取数据库连接,连接完全由Servlet Container管理,配置可以在war部署之外进行管理。这也允许对连接参数的控制比 db.properties 文件提供的更多。
3.4.1。配置
JNDI 数据源的配置将根据您使用的 servlet 容器应用程序而有所不同。以下说明适用于 Tomcat,但对于其他容器应用程序,请参阅容器应用程序的文档。
如果使用 Tomcat,JNDI 资源在 $CATALINA_BASE/conf/[enginename]/[hostname]/[warname].xml 中配置(对于 Activiti UI,这通常是 $CATALINA_BASE/conf/Catalina/localhost/activiti-app.xml) xml)。默认上下文是在首次部署应用程序时从 Activiti war 文件中复制的,因此如果它已经存在,则需要替换它。例如,要更改 JNDI 资源以使应用程序连接到 MySQL 而不是 H2,请将文件更改为以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 <?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/activiti-app">
<Resource auth="Container"
name="jdbc/activitiDB"
type="javax.sql.DataSource"
description="JDBC DataSource"
url="jdbc:mysql://localhost:3306/activiti"
driverClassName="com.mysql.jdbc.Driver"
username="sa"
password=""
defaultAutoCommit="false"
initialSize="5"
maxWait="5000"
maxActive="120"
maxIdle="5"/>
</Context>
3.4.2. JNDI 属性
要配置 JNDI 数据源,请在 Activiti UI 的属性文件中使用以下属性:
-
datasource.jndi.name:数据源的 JNDI 名称。
-
datasource.jndi.resourceRef:设置查找是否发生在 J2EE 容器中,即如果 JNDI 名称尚未包含前缀“java:comp/env/”,是否需要添加它。默认为“真”。
3.5. 支持的数据库
下面列出了 Activiti 用来引用数据库的类型(区分大小写!)。
Activiti 数据库类型 | 示例 JDBC URL | 笔记 |
---|---|---|
h2 |
jdbc:h2:tcp://localhost/activiti |
默认配置的数据库 |
mysql |
jdbc:mysql://localhost:3306/activiti?autoReconnect=true |
使用 mysql-connector-java 数据库驱动测试 |
甲骨文 |
jdbc:oracle:thin:@localhost:1521:xe |
|
postgres |
jdbc:postgresql://localhost:5432/activiti |
|
数据库2 |
jdbc:db2://localhost:50000/activiti |
|
mssql |
jdbc:sqlserver://localhost:1433;databaseName=activiti (jdbc.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver)或jdbc:jtds:sqlserver://localhost:1433/activiti (jdbc.driver=net.sourceforge .jtds.jdbc.Driver) |
使用 Microsoft JDBC Driver 4.0 (sqljdbc4.jar) 和 JTDS Driver 测试 |
3.6. 创建数据库表
为您的数据库创建数据库表的最简单方法是:
-
将 activiti-engine jar 添加到您的类路径中
-
添加合适的数据库驱动程序
-
添加一个 Activiti 配置文件(activiti.cfg.xml)到你的类路径,指向你的数据库(见数据库配置部分)
-
执行DbSchemaCreate类的 main 方法
但是,通常只有数据库管理员可以对数据库执行 DDL 语句。在生产系统上,这也是最明智的选择。SQL DDL 语句可以在 Activiti 下载页面或 Activiti 分发文件夹中的database
子目录中找到。这些脚本也在引擎 jar ( activiti-engine-x.jar ) 中,在包org/activiti/db/create中(drop文件夹包含 drop 语句)。SQL 文件的格式为
activiti.{db}.{create|drop}.{type}.sql
其中db是任何受支持的数据库,type是
-
引擎:引擎执行所需的表。必需的。
-
身份:包含用户、组和用户对组的成员资格的表。这些表是可选的,应在使用引擎附带的默认身份管理时使用。
-
历史:包含历史和审计信息的表。可选:当历史级别设置为none时不需要。请注意,这也会禁用将数据存储在历史数据库中的某些功能(例如评论任务)。
MySQL 用户注意事项:低于 5.6.4 的 MySQL 版本不支持毫秒精度的时间戳或日期。更糟糕的是,某些版本在尝试创建这样的列时会抛出异常,但其他版本不会。在进行自动创建/升级时,引擎将在执行时更改 DDL。使用 DDL 文件方法时,可以使用常规版本和包含mysql55的特殊文件(这适用于低于 5.6.4 的任何版本)。后一个文件将包含没有毫秒精度的列类型。
具体来说,以下适用于 MySQL 版本
-
<5.6:没有可用的毫秒精度。可用的 DDL 文件(查找包含mysql55的文件)。自动创建/更新将开箱即用。
-
5.6.0 - 5.6.3:没有可用的毫秒精度。自动创建/更新将不起作用。无论如何,建议升级到更新的数据库版本。如果确实需要,可以使用mysql 5.5的 DDL 文件。
-
5.6.4+:毫秒精度可用。可用的 DDL 文件(包含mysql的默认文件)。自动创建/更新开箱即用。
请注意,如果稍后升级 MySQL 数据库并且 Activiti 表已经创建/升级,则必须手动更改列类型!
3.7. 数据库表名解释
Activiti 的数据库名称都以ACT_ 开头。第二部分是表用例的双字符标识。这个用例也将大致匹配服务 API。
-
ACT_RE_ *:RE代表
repository
. 带有此前缀的表包含静态信息,例如流程定义和流程资源(图像、规则等)。 -
ACT_RU_ *:RU代表
runtime
. 这些是运行时表,包含流程实例、用户任务、变量、作业等的运行时数据。Activiti 只存储流程实例执行期间的运行时数据,并在流程实例结束时删除记录。这使运行时表保持小而快。 -
ACT_ID_ *:ID代表
identity
. 这些表包含身份信息,例如用户、组等。 -
ACT_HI_ *:HI代表
history
. 这些是包含历史数据的表,例如过去的流程实例、变量、任务等。 -
ACT_GE_ *:
general
数据,用于各种用例。
3.8. 数据库升级
确保在运行升级之前备份数据库(使用数据库备份功能)。
默认情况下,每次创建流程引擎时都会执行版本检查。这通常在您的应用程序或 Activiti webapps 启动时发生一次。如果 Activiti 库注意到库版本和 Activiti 数据库表的版本之间存在差异,则抛出异常。
要升级,您必须首先将以下配置属性放入您的 activiti.cfg.xml 配置文件中:
1
2
3
4
5
6
7
8
9 <beans >
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!-- ... -->
<property name="databaseSchemaUpdate" value="true" />
<!-- ... -->
</bean>
</beans>
此外,在类路径中包含适合您的数据库的数据库驱动程序。升级应用程序中的 Activiti 库。或者启动新版本的 Activiti 并将其指向包含旧版本的数据库。databaseSchemaUpdate
设置为,当true
Activiti 第一次发现库和 DB 模式不同步时,它会自动将 DB 模式升级到较新的版本。
作为替代方案,您还可以运行升级 DDL 语句。也可以在 Activiti 下载页面上运行升级数据库脚本。
3.9. 作业执行器(从 6.0.0 版开始)
Activiti 5 的异步执行器是 Activiti 6 中唯一可用的作业执行器,因为它是一种在 Activiti 引擎中执行异步作业的性能更高且对数据库更友好的方式。Activiti 5 的旧作业执行器被移除。更多信息可以在用户指南的高级部分中找到。
ManagedAsyncJobExecutor
此外,如果在 Java EE 7 下运行,则可以使用兼容 JSR-236的方式让容器管理线程。为了启用它们,应该在配置中传递线程工厂,如下所示:
1
2
3
4
5
6
7
8
9 <bean id="threadFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:jboss/ee/concurrency/factory/default" />
</bean>
<bean id="customJobExecutor" class="org.activiti.engine.impl.jobexecutor.ManagedAsyncJobExecutor">
<!-- ... -->
<property name="threadFactory" ref="threadFactory" />
<!-- ... -->
</bean>
如果未指定线程工厂,则托管实现会回退到其默认对应项。
3.10。作业执行器激活
这AsyncExecutor
是一个管理线程池以触发计时器和其他异步任务的组件。其他实现是可能的(例如使用消息队列,请参阅用户指南的高级部分)。
默认情况下,AsyncExecutor
未激活且未启动。通过以下配置,异步执行器可以与 Activiti 引擎一起启动。
1 <property name="asyncExecutorActivate" value="true" />
asyncExecutorActivate 属性指示 Activiti Engine 在启动时启动 Async 执行器。
3.11。邮件服务器配置
配置邮件服务器是可选的。Activiti 支持在业务流程中发送电子邮件。要实际发送电子邮件,需要有效的 SMTP 邮件服务器配置。有关配置选项,请参阅电子邮件任务。
3.12。历史配置
1 <property name="history" value="audit" />
3.13。在表达式和脚本中公开配置 bean
默认情况下,您在activiti.cfg.xml
配置或您自己的 Spring 配置文件中指定的所有 bean 都可用于表达式和脚本。如果要限制配置文件中 bean 的可见性,可以配置beans
在流程引擎配置中调用的属性。中的 beans 属性ProcessEngineConfiguration
是一个映射。当您指定该属性时,只有在该映射中指定的 bean 对表达式和脚本可见。公开的 bean 将使用您在该映射中指定的名称公开。
3.14。部署缓存配置
所有流程定义都被缓存(在它们被解析之后)以避免每次需要流程定义时都访问数据库并且因为流程定义数据不会更改。默认情况下,此缓存没有限制。要限制流程定义缓存,请添加以下属性
1 <property name="processDefinitionCacheLimit" value="10" />
设置此属性会将默认 hashmap 缓存与具有提供的硬限制的 LRU 缓存交换。当然,该属性的最佳值取决于存储的流程定义的总量以及所有运行时流程实例在运行时实际使用的流程定义的数量。
您还可以注入自己的缓存实现。这必须是一个实现 org.activiti.engine.impl.persistence.deploy.DeploymentCache 接口的 bean:
1
2
3 <property name="processDefinitionCache">
<bean class="org.activiti.MyCache" />
</property>
有一个类似的属性叫做knowledgeBaseCacheLimit
andknowledgeBaseCache
用于配置规则缓存。仅当您在流程中使用规则任务时才需要这样做。
3.15。日志记录
所有的日志记录(activiti、spring、mybatis……)都通过 SLF4J 路由,并允许选择您选择的日志记录实现。
默认情况下,activiti-engine 依赖项中不存在 SFL4J 绑定 jar,应将其添加到您的项目中,以便使用您选择的日志框架。如果没有添加实现 jar,SLF4J 将使用 NOP 记录器,根本不记录任何内容,除了不会记录任何内容的警告。有关这些绑定的更多信息http://www.slf4j.org/codes.html#StaticLoggerBinder。
使用 Maven,例如添加这样的依赖项(此处使用 log4j),请注意您仍然需要添加版本:
1
2
3
4 <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
activiti-ui 和 activiti-rest webapps 被配置为使用 Log4j 绑定。对所有 activiti-* 模块运行测试时也会使用 Log4j。
在类路径中使用带有 commons-logging 的容器时的重要注意事项:为了通过 SLF4J 路由 spring-logging,使用了一个桥接器(参见http://www.slf4j.org/legacy.html#jclOverSLF4J)。如果您的容器提供公共日志记录实现,请按照此页面上的说明进行操作:http://www.slf4j.org/codes.html#release以确保稳定性。
使用 Maven 时的示例(版本省略):
1
2
3
4 <dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
3.16。映射的诊断上下文
Activiti 支持 SLF4j 的映射诊断上下文功能。这些基本信息与将要记录的内容一起传递给底层记录器:
-
流程定义 ID 作为 mdcProcessDefinitionID
-
进程实例 ID 作为 mdcProcessInstanceID
-
执行 ID 作为 mdcExecutionId
默认情况下不会记录这些信息。记录器可以配置为以所需的格式显示它们,除了通常记录的消息。例如,在 Log4j 中,以下示例布局定义导致记录器显示上述信息:
1
2 log4j.appender.consoleAppender.layout.ConversionPattern=ProcessDefinitionId=%X{mdcProcessDefinitionID}
executionId=%X{mdcExecutionId} mdcProcessInstanceID=%X{mdcProcessInstanceID} mdcBusinessKey=%X{mdcBusinessKey} %m%n
当日志包含需要实时检查的信息时,这很有用,例如通过日志分析器。
3.17。事件处理程序
Activiti 引擎中的事件机制允许您在引擎内发生各种事件时得到通知。查看所有支持的事件类型,了解可用事件的概览。
可以为某些类型的事件注册侦听器,而不是在分派任何类型的事件时收到通知。您可以通过配置添加引擎范围的事件侦听器,使用 API 在运行时添加引擎范围的事件侦听器,或者将事件侦听器添加到BPMN XML 中的特定流程定义。
调度的所有事件都是org.activiti.engine.delegate.event.ActivitiEvent
. 该事件公开(如果可用)type
、executionId
和。某些事件包含与发生的事件相关的附加上下文,有关附加负载的附加信息可以在所有支持的事件类型列表中找到。processInstanceId
processDefinitionId
3.17.1。事件监听器实现
事件监听器的唯一要求是实现org.activiti.engine.delegate.event.ActivitiEventListener
. 下面是一个监听器的示例实现,它将接收到的所有事件输出到标准输出,与作业执行相关的事件除外:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 public class MyEventListener implements ActivitiEventListener {
@Override
public void onEvent(ActivitiEvent event) {
switch (event.getType()) {
case JOB_EXECUTION_SUCCESS:
System.out.println("A job well done!");
break;
case JOB_EXECUTION_FAILURE:
System.out.println("A job has failed...");
break;
default:
System.out.println("Event received: " + event.getType());
}
}
@Override
public boolean isFailOnException() {
// The logic in the onEvent method of this listener is not critical, exceptions
// can be ignored if logging fails...
return false;
}
}
该isFailOnException()
方法确定在onEvent(..)
调度事件时该方法抛出异常时的行为。如果false
返回,则忽略异常。返回时true
,异常不会被忽略并冒泡,实际上使当前正在进行的命令失败。如果事件是 API 调用(或任何其他事务操作,例如作业执行)的一部分,则事务将回滚。如果事件监听器中的行为不是关键业务,建议返回false
。
Activiti 提供了一些基本实现来促进事件监听器的常见用例。这些可以用作基类或作为示例侦听器实现:
-
org.activiti.engine.delegate.event.BaseEntityEventListener:一个事件监听器基类,可用于监听特定类型实体或所有实体的实体相关事件。它隐藏了类型检查并提供了 4 个应该被覆盖的方法:以及创建、更新或删除实体的时间
onCreate(..)
。对于所有其他与实体相关的事件,调用 。onUpdate(..)
onDelete(..)
onEntityEvent(..)
3.17.2. 配置和设置
如果在流程引擎配置中配置了事件侦听器,它将在流程引擎启动时处于活动状态,并在引擎随后重新启动后保持活动状态。
该属性eventListeners
需要一个org.activiti.engine.delegate.event.ActivitiEventListener
实例列表。像往常一样,您可以声明一个内联 bean 定义或使用ref
现有的 bean 来代替。下面的代码片段向配置中添加了一个事件侦听器,当任何事件被调度时,该事件侦听器会被通知,无论其类型如何:
1
2
3
4
5
6
7
8 <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
...
<property name="eventListeners">
<list>
<bean class="org.activiti.engine.example.MyEventListener" />
</list>
</property>
</bean>
要在某些类型的事件被调度时得到通知,请使用typedEventListeners
需要映射的属性。映射条目的键是逗号分隔的事件名称列表(或单个事件名称)。map-entry 的值是org.activiti.engine.delegate.event.ActivitiEventListener
实例列表。下面的代码片段在配置中添加了一个事件监听器,当作业执行成功或失败时会收到通知:
1
2
3
4
5
6
7
8
9
10
11
12 <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
...
<property name="typedEventListeners">
<map>
<entry key="JOB_EXECUTION_SUCCESS,JOB_EXECUTION_FAILURE" >
<list>
<bean class="org.activiti.engine.example.MyJobEventListener" />
</list>
</entry>
</map>
</property>
</bean>
调度事件的顺序取决于添加侦听器的顺序。首先,所有正常的事件侦听器都eventListeners
按照它们在list
. 之后typedEventListeners
,如果调度了正确类型的事件,则调用所有类型化的事件侦听器(属性)。
3.17.3. 在运行时添加监听器
可以使用 API ( RuntimeService
) 向引擎添加和删除额外的事件侦听器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 /**
* Adds an event-listener which will be notified of ALL events by the dispatcher.
* @param listenerToAdd the listener to add
*/
void addEventListener(ActivitiEventListener listenerToAdd);
/**
* Adds an event-listener which will only be notified when an event occurs, which type is in the given types.
* @param listenerToAdd the listener to add
* @param types types of events the listener should be notified for
*/
void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType... types);
/**
* Removes the given listener from this dispatcher. The listener will no longer be notified,
* regardless of the type(s) it was registered for in the first place.
* @param listenerToRemove listener to remove
*/
void removeEventListener(ActivitiEventListener listenerToRemove);
请注意,重新启动引擎时不会保留运行时添加的侦听器。
3.17.4。将侦听器添加到流程定义
可以将侦听器添加到特定的流程定义。只有与流程定义相关的事件以及与以该特定流程定义启动的流程实例相关的所有事件才会调用侦听器。侦听器实现可以使用完全限定的类名、解析为实现侦听器接口的 bean 的表达式来定义,或者可以配置为抛出消息/信号/错误 BPMN 事件。
执行用户定义逻辑的监听器
下面的代码片段将 2 个侦听器添加到流程定义中。第一个侦听器将接收任何类型的事件,侦听器实现基于完全限定的类名。仅当作业成功执行或失败时才通知第二个侦听器,使用已在beans
流程引擎配置的属性中定义的侦听器。
1
2
3
4
5
6
7
8
9 <process id="testEventListeners">
<extensionElements>
<activiti:eventListener class="org.activiti.engine.test.MyEventListener" />
<activiti:eventListener delegateExpression="${testEventListener}" events="JOB_EXECUTION_SUCCESS,JOB_EXECUTION_FAILURE" />
</extensionElements>
...
</process>
对于与实体相关的事件,还可以将侦听器添加到流程定义中,仅当特定实体类型的实体事件发生时才会收到通知。下面的片段显示了如何实现这一点。它可以用于所有实体事件(第一个示例)或仅用于特定事件类型(第二个示例)。
1
2
3
4
5
6
7
8
9 <process id="testEventListeners">
<extensionElements>
<activiti:eventListener class="org.activiti.engine.test.MyEventListener" entityType="task" />
<activiti:eventListener delegateExpression="${testEventListener}" events="ENTITY_CREATED" entityType="task" />
</extensionElements>
...
</process>
支持的值为entityType
:attachment
, comment
, execution
, identity-link
, job
, process-instance
, process-definition
, task
.
监听器抛出 BPMN 事件
另一种处理被分派的事件的方法是抛出一个 BPMN 事件。请记住,只有在某些类型的 Activiti 事件类型中抛出 BPMN 事件才有意义。例如,当流程实例被删除时抛出一个 BPMN 事件将导致错误。下面的代码片段展示了如何在流程实例中抛出信号、向外部进程(全局)抛出信号、在流程实例中抛出消息事件以及在流程实例中抛出错误事件。不使用class
or delegateExpression
,而是使用该属性throwEvent
以及一个附加属性,该属性特定于被抛出的事件类型。
1
2
3
4
5 <process id="testEventListeners">
<extensionElements>
<activiti:eventListener throwEvent="signal" signalName="My signal" events="TASK_ASSIGNED" />
</extensionElements>
</process>
1
2
3
4
5 <process id="testEventListeners">
<extensionElements>
<activiti:eventListener throwEvent="globalSignal" signalName="My signal" events="TASK_ASSIGNED" />
</extensionElements>
</process>
1
2
3
4
5 <process id="testEventListeners">
<extensionElements>
<activiti:eventListener throwEvent="message" messageName="My message" events="TASK_ASSIGNED" />
</extensionElements>
</process>
1
2
3
4
5 <process id="testEventListeners">
<extensionElements>
<activiti:eventListener throwEvent="error" errorCode="123" events="TASK_ASSIGNED" />
</extensionElements>
</process>
如果需要额外的逻辑来决定是否抛出 BPMN 事件,可以扩展 Activiti 提供的监听器类。通过覆盖isValidEvent(ActivitiEvent event) in your subclass, the BPMN-event throwing can be prevented. The classes involved are +org.activiti.engine.test.api.event.SignalThrowingEventListenerTest
,org.activiti.engine.impl.bpmn.helper.MessageThrowingEventListener
和org.activiti.engine.impl.bpmn.helper.ErrorThrowingEventListener
.
关于进程定义的监听器的注释
-
事件监听器只能在
process
元素上声明,作为extensionElements
. 无法在流程中的单个活动上定义侦听器。 -
中使用的表达式
delegateExpression
无法访问执行上下文,因为其他表达式(例如在网关中)具有。它们只能引用beans
在流程引擎配置的属性中定义的bean,或者在使用spring(并且bean 属性不存在)时将bean 引用到任何实现侦听器接口的spring-bean。 -
使用
class
侦听器的属性时,只会创建该类的一个实例。确保侦听器实现不依赖于成员字段或确保从多个线程/上下文安全使用。 -
当
events
属性中使用了非法事件类型或使用了非法throwEvent
值时,部署流程定义时会抛出异常(实际上是部署失败)。当为class
ordelegateExecution
提供非法值时(不存在的类、不存在的 bean 引用或未实现侦听器接口的委托),将在进程启动时引发异常(或当该进程定义的第一个有效事件是发送给监听器)。确保引用的类在类路径上,并且表达式解析为有效实例。
3.17.5。通过 API 调度事件
我们通过 API 开放了事件分派机制,允许您将自定义事件分派给在引擎中注册的任何侦听器。建议(尽管不强制)仅ActivitiEvents
使用 type进行调度CUSTOM
。调度事件可以使用RuntimeService
:
1
2
3
4
5
6
7
8
9 /**
* Dispatches the given event to any listeners that are registered.
* @param event event to dispatch.
*
* @throws ActivitiException if an exception occurs when dispatching the event or when the {@link ActivitiEventDispatcher}
* is disabled.
* @throws ActivitiIllegalArgumentException when the given event is not suitable for dispatching.
*/
void dispatchEvent(ActivitiEvent event);
3.17.6。支持的事件类型
下面列出了引擎中可能发生的所有事件类型。每种类型对应于org.activiti.engine.delegate.event.ActivitiEventType
.
活动名称 | 描述 | 事件类 |
---|---|---|
ENGINE_CREATED |
此侦听器附加到的流程引擎已创建并准备好进行 API 调用。 |
|
ENGINE_CLOSED |
此侦听器附加到的流程引擎已关闭。对引擎的 API 调用不再可能。 |
|
ENTITY_CREATED |
创建了一个新实体。新实体包含在事件中。 |
|
ENTITY_INITIALIZED |
已创建一个新实体并已完全初始化。如果在创建实体的过程中创建了任何子实体,则该事件将在子实体的创建/初始化之后触发,而不是 |
|
ENTITY_UPDATED |
现有的被更新。更新的实体包含在事件中。 |
|
ENTITY_DELETED |
现有实体被删除。删除的实体包含在事件中。 |
|
ENTITY_SUSPENDED |
现有实体被暂停。被暂停的实体包含在事件中。将为 ProcessDefinitions、ProcessInstances 和 Tasks 分派。 |
|
ENTITY_ACTIVATED |
现有实体被激活。激活的实体包含在事件中。将为 ProcessDefinitions、ProcessInstances 和 Tasks 分派。 |
|
JOB_EXECUTION_SUCCESS |
作业已成功执行。该事件包含已执行的作业。 |
|
JOB_EXECUTION_FAILURE |
作业执行失败。该事件包含已执行的作业和异常。 |
|
JOB_RETRIES_DECREMENTED |
由于作业失败,作业重试次数已减少。该事件包含已更新的作业。 |
|
TIMER_FIRED |
计时器已被触发。事件包含已执行的作业? |
|
JOB_CANCELED |
已取消作业。该事件包含已取消的作业。在新的流程定义部署中,可以通过 API 调用取消作业、完成任务并取消关联的边界计时器。 |
|
ACTIVITY_STARTED |
一个活动开始执行 |
|
ACTIVITY_COMPLETED |
一个活动成功完成 |
|
ACTIVITY_CANCELLED |
一项活动将被取消。活动取消可能有三个原因(MessageEventSubscriptionEntity、SignalEventSubscriptionEntity、TimerEntity)。 |
|
ACTIVITY_SIGNALED |
活动收到信号 |
|
ACTIVITY_MESSAGE_RECEIVED |
一个活动收到一条消息。在活动接收到消息之前调度。收到后,将为此活动分派一个 |
|
ACTIVITY_ERROR_RECEIVED |
活动收到错误事件。在活动处理实际错误之前调度。该事件 |
|
UNCAUGHT_BPMN_ERROR |
引发了未捕获的 BPMN 错误。该进程没有针对该特定错误的任何处理程序。该事件 |
|
ACTIVITY_COMPENSATE |
一项活动即将得到补偿。该事件包含将执行以进行补偿的活动的 ID。 |
|
VARIABLE_CREATED |
已创建变量。该事件包含变量名称、值以及相关的执行和任务(如果有)。 |
|
VARIABLE_UPDATED |
现有变量已更新。该事件包含变量名称、更新值以及相关的执行和任务(如果有)。 |
|
VARIABLE_DELETED |
现有变量已被删除。该事件包含变量名称、最后一个已知值以及相关的执行和任务(如果有)。 |
|
TASK_ASSIGNED |
任务已分配给用户。事件包含任务 |
|
TASK_CREATED |
已创建任务。这是在 |
|
TASK_COMPLETED |
一项任务已完成。这是在 |
|
PROCESS_COMPLETED |
一个过程已经完成。在最后一个活动事件之后调度 |
|
PROCESS_CANCELLED |
一个进程已被取消。在从运行时删除流程实例之前调度。流程实例被 API 调用取消 |
|
MEMBERSHIP_CREATED |
已将用户添加到组中。该事件包含所涉及的用户和组的 ID。 |
|
MEMBERSHIP_DELETED |
用户已从组中删除。该事件包含所涉及的用户和组的 ID。 |
|
MEMBERSHIPS_DELETED |
所有成员都将从组中删除。该事件在成员被删除之前被抛出,因此它们仍然可以访问。 |
|
所有ENTITY_\*
事件都与引擎内部的实体相关。下面的列表显示了为哪些实体分派了哪些实体事件的概述:
-
ENTITY_CREATED, ENTITY_INITIALIZED, ENTITY_DELETED
:附件、评论、部署、执行、组、IdentityLink、作业、模型、ProcessDefinition、ProcessInstance、任务、用户。 -
ENTITY_UPDATED
:附件、部署、执行、组、IdentityLink、作业、模型、ProcessDefinition、ProcessInstance、任务、用户。 -
ENTITY_SUSPENDED, ENTITY_ACTIVATED
:流程定义、流程实例/执行、任务。
3.17.7。附加说明
只有侦听器会在引擎中被通知事件被分派。因此,如果您有不同的引擎 - 针对同一个数据库运行 - 只有源自侦听器注册的引擎的事件才会被分派给该侦听器。发生在其他引擎中的事件不会分派给侦听器,无论它们是否在同一个 JVM 中运行。
某些事件类型(与实体相关)暴露了目标实体。根据类型或事件,这些实体不能再更新(例如,当实体被删除时)。如果可能,请使用EngineServices
事件公开的内容以安全的方式与引擎进行交互。即使这样,您也需要谨慎对待分派事件中涉及的实体的更新/操作。
没有与历史相关的实体事件被分派,因为它们都有一个运行时对应物,其事件被分派。
4. Activiti API
4.1。流程引擎 API 和服务
引擎 API 是与 Activiti 交互的最常见方式。中心起点是ProcessEngine
,可以按照 配置部分中所述的多种方式创建。您可以从 ProcessEngine 获取包含工作流/BPM 方法的各种服务。ProcessEngine 和服务对象是线程安全的。因此,您可以为整个服务器保留对其中 1 个的引用。
1
2
3
4
5
6
7
8
9
10 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
ManagementService managementService = processEngine.getManagementService();
IdentityService identityService = processEngine.getIdentityService();
HistoryService historyService = processEngine.getHistoryService();
FormService formService = processEngine.getFormService();
DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();
ProcessEngines.getDefaultProcessEngine()
将在第一次调用时初始化并构建流程引擎,然后总是返回相同的流程引擎。可以使用ProcessEngines.init()
和正确创建和关闭所有流程引擎ProcessEngines.destroy()
。
activiti.cfg.xml
ProcessEngines类将扫描所有activiti-context.xml
文件。对于所有activiti.cfg.xml
文件,流程引擎将以典型的 Activiti 方式构建:ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(inputStream).buildProcessEngine()
. 对于所有activiti-context.xml
文件,流程引擎将以 Spring 方式构建:首先创建 Spring 应用程序上下文,然后从该应用程序上下文中获取流程引擎。
所有服务都是无状态的。这意味着您可以轻松地在集群中的多个节点上运行 Activiti,每个节点都访问同一个数据库,而不必担心哪台机器实际执行了先前的调用。无论在何处执行,对任何服务的任何调用都是幂等的。
RepositoryService可能是使用 Activiti 引擎时需要的第一个服务。该服务提供用于管理和操作的操作deployments
以及process definitions
. 在此不赘述,流程定义是 BPMN 2.0 流程的 Java 对应物。它是过程中每个步骤的结构和行为的表示。Adeployment
是 Activiti 引擎中的封装单位。一个部署可以包含多个 BPMN 2.0 xml 文件和任何其他资源。一个部署中包含的内容的选择取决于开发人员。它的范围可以从单个流程 BPMN 2.0 xml 文件到整个流程包和相关资源(例如部署hr-processes可以包含与 hr 流程相关的所有内容)。RepositoryService
允许这样的deploy
包。部署部署意味着它被上传到引擎,在存储到数据库之前,所有流程都经过检查和解析。从那时起,系统就知道部署了,现在可以启动部署中包含的任何进程。
此外,该服务还允许
-
查询引擎已知的部署和流程定义。
-
暂停和激活作为一个整体或特定流程定义的部署。挂起意味着不能对它们进行进一步的操作,而激活是相反的操作。
-
检索各种资源,例如引擎自动生成的部署或流程图中包含的文件。
-
检索流程定义的 POJO 版本,该版本可用于使用 Java 而不是 xml 内省流程。
虽然它RepositoryService
是关于静态信息(即不会改变,或者至少不会改变很多的数据),但RuntimeService恰恰相反。它处理启动流程定义的新流程实例。如上所述,aprocess definition
定义了流程中不同步骤的结构和行为。流程实例是此类流程定义的一次执行。对于每个流程定义,通常有许多实例同时运行。RuntimeService
同样是用于检索和存储的服务process variables
。这是特定于给定流程实例的数据,并且可以被流程中的各种结构使用(例如,独占网关经常使用流程变量来确定选择哪个路径来继续流程)。这Runtimeservice
还允许查询流程实例和执行。执行是'token'
BPMN 2.0 概念的一种表示。基本上,执行是指向流程实例当前所在位置的指针。最后,RuntimeService
当流程实例等待外部触发器并且流程需要继续时使用。流程实例可以有多种wait states
,并且该服务包含各种操作以向实例发出信号,表明接收到外部触发器并且流程实例可以继续。
需要由系统的实际人类用户执行的任务是 BPM 引擎(如 Activiti)的核心。围绕任务的所有内容都分组在TaskService中,例如
-
查询分配给用户或组的任务
-
创建新的独立任务。这些是与流程实例无关的任务。
-
操纵任务被分配给哪个用户或哪些用户以某种方式参与该任务。
-
要求并完成一项任务。声明意味着某人决定成为该任务的受让人,这意味着该用户将完成该任务。完成意味着完成任务的工作。通常这是填写某种形式。
IdentityService非常简单。它允许管理(创建、更新、删除、查询……)组和用户。重要的是要了解 Activiti 实际上不会在运行时对用户进行任何检查。例如,可以将任务分配给任何用户,但引擎不会验证系统是否知道该用户。这是因为 Activiti 引擎还可以与 LDAP、Active Directory 等服务结合使用。
FormService是一项可选服务。这意味着 Activiti 可以在没有它的情况下完美使用,而不会牺牲任何功能。该服务引入了启动表单和任务表单的概念。开始表单是在流程实例启动之前向用户显示的表单,而任务表单是在用户想要完成表单时显示的表单。Activiti 允许在 BPMN 2.0 流程定义中定义这些表单。该服务以一种易于使用的方式公开这些数据。但同样,这是可选的,因为表单不需要嵌入到流程定义中。
HistoryService暴露了 Activiti 引擎收集的所有历史数据。执行流程时,引擎可以保留很多数据(这是可配置的),例如流程实例的启动时间、谁执行了哪些任务、完成任务需要多长时间、每个流程实例中遵循的路径等. 该服务主要暴露查询能力来访问这些数据。
使用Activiti 编写自定义应用程序时,通常不需要ManagementService 。它允许检索有关数据库表和表元数据的信息。此外,它还公开了作业的查询功能和管理操作。作业在 Activiti 中用于各种事情,例如计时器、异步延续、延迟暂停/激活等。稍后,将更详细地讨论这些主题。
DynamicBpmnService可用于更改流程定义的一部分,而无需重新部署它。例如,您可以更改流程定义中用户任务的受理人定义,或更改服务任务的类名。
有关服务操作和引擎 API 的更多详细信息,请参阅javadocs。
4.2. 异常策略
Activiti 中的基本异常是org.activiti.engine.ActivitiException
,一个未经检查的异常。API 可以随时抛出此异常,但在特定方法中发生的预期异常记录在javadocs中。例如,摘录自TaskService
:
1
2
3
4
5
6 /**
* Called when the task is successfully executed.
* @param taskId the id of the task to complete, cannot be null.
* @throws ActivitiObjectNotFoundException when no task exists with the given id.
*/
void complete(String taskId);
在上面的示例中,当传递一个不存在任务的 id 时,将引发异常。此外,由于 javadoc明确指出 taskId 不能为 null,ActivitiIllegalArgumentException
因此将在null
传递时抛出 an 。
即使我们想避免大的异常层次结构,也添加了以下子类,这些子类在特定情况下被抛出。在流程执行或 API 调用期间发生的所有其他错误,不符合下面可能的异常,将作为常规ActivitiExceptions
s 抛出。
-
ActivitiWrongDbException
: 当 Activiti 引擎发现数据库模式版本和引擎版本不匹配时抛出。 -
ActivitiOptimisticLockingException
:当并发访问同一数据条目导致数据存储发生乐观锁定时抛出。 -
ActivitiClassLoadingException
:当未找到请求加载的类或加载时发生错误(例如 JavaDelegates、TaskListeners ......)时抛出。 -
ActivitiObjectNotFoundException
:当请求或操作的对象不存在时抛出。 -
ActivitiIllegalArgumentException
: 异常表示在 Activiti API 调用中提供了非法参数,在引擎配置中配置了非法值,或者提供了非法值,或者在流程定义中使用了非法值。 -
ActivitiTaskAlreadyClaimedException
:当一个任务已经被声明,当taskService.claim(…)
被调用时抛出。
4.3. 使用 Activiti 服务
如上所述,与 Activiti 引擎交互的方式是通过org.activiti.engine.ProcessEngine
类实例暴露的服务。下面的代码片段假设你有一个工作的 Activiti 环境,即你可以访问一个有效的org.activiti.engine.ProcessEngine
. 如果你只是想试试下面的代码,你可以下载或克隆Activiti 单元测试模板,将它导入你的 IDE 并添加一个testUserguideCode()
方法到org.activiti.MyUnitTest
单元测试中。
这个小教程的最终目标是建立一个可以模仿公司简单休假申请流程的工作业务流程:
4.3.1。部署流程
与静态数据相关的所有内容(例如流程定义)都可以通过RepositoryService访问。从概念上讲,每条这样的静态数据都是Activiti 引擎存储库的内容。
VacationRequest.bpmn20.xml
在src/test/resources/org/activiti/test
资源文件夹(或其他任何地方,如果您不使用单元测试模板)中创建一个新的 xml 文件,其中包含以下内容。请注意,本节不会解释上面示例中使用的 xml 结构。如果需要,请先阅读BPMN 2.0 章节以熟悉这些结构。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85 <?xml version="1.0" encoding="UTF-8" ?>
<definitions id="definitions"
targetNamespace="http://activiti.org/bpmn20"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:activiti="http://activiti.org/bpmn">
<process id="vacationRequest" name="Vacation request">
<startEvent id="request" activiti:initiator="employeeName">
<extensionElements>
<activiti:formProperty id="numberOfDays" name="Number of days" type="long" value="1" required="true"/>
<activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" datePattern="dd-MM-yyyy hh:mm" type="date" required="true" />
<activiti:formProperty id="vacationMotivation" name="Motivation" type="string" />
</extensionElements>
</startEvent>
<sequenceFlow id="flow1" sourceRef="request" targetRef="handleRequest" />
<userTask id="handleRequest" name="Handle vacation request" >
<documentation>
${employeeName} would like to take ${numberOfDays} day(s) of vacation (Motivation: ${vacationMotivation}).
</documentation>
<extensionElements>
<activiti:formProperty id="vacationApproved" name="Do you approve this vacation" type="enum" required="true">
<activiti:value id="true" name="Approve" />
<activiti:value id="false" name="Reject" />
</activiti:formProperty>
<activiti:formProperty id="managerMotivation" name="Motivation" type="string" />
</extensionElements>
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>management</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<sequenceFlow id="flow2" sourceRef="handleRequest" targetRef="requestApprovedDecision" />
<exclusiveGateway id="requestApprovedDecision" name="Request approved?" />
<sequenceFlow id="flow3" sourceRef="requestApprovedDecision" targetRef="sendApprovalMail">
<conditionExpression xsi:type="tFormalExpression">${vacationApproved == 'true'}</conditionExpression>
</sequenceFlow>
<task id="sendApprovalMail" name="Send confirmation e-mail" />
<sequenceFlow id="flow4" sourceRef="sendApprovalMail" targetRef="theEnd1" />
<endEvent id="theEnd1" />
<sequenceFlow id="flow5" sourceRef="requestApprovedDecision" targetRef="adjustVacationRequestTask">
<conditionExpression xsi:type="tFormalExpression">${vacationApproved == 'false'}</conditionExpression>
</sequenceFlow>
<userTask id="adjustVacationRequestTask" name="Adjust vacation request">
<documentation>
Your manager has disapproved your vacation request for ${numberOfDays} days.
Reason: ${managerMotivation}
</documentation>
<extensionElements>
<activiti:formProperty id="numberOfDays" name="Number of days" value="${numberOfDays}" type="long" required="true"/>
<activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" value="${startDate}" datePattern="dd-MM-yyyy hh:mm" type="date" required="true" />
<activiti:formProperty id="vacationMotivation" name="Motivation" value="${vacationMotivation}" type="string" />
<activiti:formProperty id="resendRequest" name="Resend vacation request to manager?" type="enum" required="true">
<activiti:value id="true" name="Yes" />
<activiti:value id="false" name="No" />
</activiti:formProperty>
</extensionElements>
<humanPerformer>
<resourceAssignmentExpression>
<formalExpression>${employeeName}</formalExpression>
</resourceAssignmentExpression>
</humanPerformer>
</userTask>
<sequenceFlow id="flow6" sourceRef="adjustVacationRequestTask" targetRef="resendRequestDecision" />
<exclusiveGateway id="resendRequestDecision" name="Resend request?" />
<sequenceFlow id="flow7" sourceRef="resendRequestDecision" targetRef="handleRequest">
<conditionExpression xsi:type="tFormalExpression">${resendRequest == 'true'}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow8" sourceRef="resendRequestDecision" targetRef="theEnd2">
<conditionExpression xsi:type="tFormalExpression">${resendRequest == 'false'}</conditionExpression>
</sequenceFlow>
<endEvent id="theEnd2" />
</process>
</definitions>
为了让 Activiti 引擎知道这个过程,我们必须首先部署它。部署意味着引擎会将 BPMN 2.0 xml 解析为可执行文件,并且将为部署中包含的每个流程定义添加新的数据库记录。这样,当引擎重新启动时,它仍然会知道所有已部署的进程:
1
2
3
4
5
6
7 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
repositoryService.createDeployment()
.addClasspathResource("org/activiti/test/VacationRequest.bpmn20.xml")
.deploy();
Log.info("Number of process definitions: " + repositoryService.createProcessDefinitionQuery().count());
在部署章节中阅读有关部署的更多信息。
4.3.2. 启动流程实例
将流程定义部署到 Activiti 引擎后,我们可以从中启动新的流程实例。对于每个流程定义,通常有许多流程实例。流程定义是蓝图,而流程实例是它的运行时执行。
与进程运行时状态相关的所有内容都可以在RuntimeService中找到。有多种方法可以启动一个新的流程实例。在下面的代码片段中,我们使用我们在流程定义 xml 中定义的键来启动流程实例。我们还在流程实例启动时提供了一些流程变量,因为第一个用户任务的描述将在其表达式中使用这些变量。流程变量是常用的,因为它们为特定流程定义的流程实例赋予意义。通常,流程变量是使流程实例彼此不同的原因。
1
2
3
4
5
6
7
8
9
10 Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employeeName", "Kermit");
variables.put("numberOfDays", new Integer(4));
variables.put("vacationMotivation", "I'm really tired!");
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("vacationRequest", variables);
// Verify that we started a new process instance
Log.info("Number of process instances: " + runtimeService.createProcessInstanceQuery().count());
4.3.3. 完成任务
当流程开始时,第一步将是用户任务。这是必须由系统用户执行的步骤。通常,此类用户将拥有一个任务收件箱,其中列出了该用户需要完成的所有任务。以下代码片段显示了如何执行此类查询:
1
2
3
4
5
6 // Fetch all tasks for the management group
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
for (Task task : tasks) {
Log.info("Task available: " + task.getName());
}
要继续流程实例,我们需要完成此任务。对于 Activiti 引擎,这意味着您需要complete
完成任务。以下片段显示了这是如何完成的:
1
2
3
4
5
6 Task task = tasks.get(0);
Map<String, Object> taskVariables = new HashMap<String, Object>();
taskVariables.put("vacationApproved", "false");
taskVariables.put("managerMotivation", "We have a tight deadline!");
taskService.complete(task.getId(), taskVariables);
流程实例现在将继续进行下一步。在此示例中,下一步允许员工填写表格以调整其原始休假请求。员工可以重新提交休假请求,这将导致流程循环回到开始任务。
4.3.4. 暂停和激活进程
可以暂停流程定义。当流程定义被挂起时,无法创建新的流程实例(将引发异常)。暂停流程定义是通过以下方式完成的RepositoryService
:
1
2
3
4
5
6 repositoryService.suspendProcessDefinitionByKey("vacationRequest");
try {
runtimeService.startProcessInstanceByKey("vacationRequest");
} catch (ActivitiException e) {
e.printStackTrace();
}
要重新激活流程定义,只需调用其中一种repositoryService.activateProcessDefinitionXXX
方法。
也可以暂停流程实例。暂停时,进程无法继续(例如完成任务会引发异常),并且不会执行任何作业(例如计时器)。可以通过调用该runtimeService.suspendProcessInstance
方法来暂停流程实例。通过调用runtimeService.activateProcessInstanceXXX
方法再次激活流程实例。
4.3.5。进一步阅读
在前面关于 Activiti 功能的部分中,我们几乎没有触及表面。我们将在未来进一步扩展这些部分,增加对 Activiti API 的覆盖。当然,与任何开源项目一样,最好的学习方法是检查代码并阅读 Javadocs!
4.4. 查询接口
从引擎查询数据有两种方式:查询 API 和原生查询。Query API 允许使用 fluent API 编写完全类型安全的查询。您可以在查询中添加各种条件(所有这些条件都作为逻辑 AND 一起应用)并且恰好是一个排序。以下代码显示了一个示例:
1
2
3
4
5 List<Task> tasks = taskService.createTaskQuery()
.taskAssignee("kermit")
.processVariableValueEquals("orderId", "0815")
.orderByDueDate().asc()
.list();
有时您需要更强大的查询,例如使用 OR 运算符的查询或您无法使用 Query API 表达的限制。对于这些情况,我们引入了本机查询,允许您编写自己的 SQL 查询。返回类型由您使用的 Query 对象定义,并且数据被映射到正确的对象中,例如 Task、ProcessInstance、Execution 等……。由于查询将在数据库中触发,因此您必须使用在数据库中定义的表名和列名;这需要有关内部数据结构的一些知识,建议谨慎使用本机查询。可以通过 API 检索表名,以使依赖关系尽可能小。
1
2
3
4
5
6
7
8
9 List<Task> tasks = taskService.createNativeTaskQuery()
.sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T WHERE T.NAME_ = #{taskName}")
.parameter("taskName", "gonzoTask")
.list();
long count = taskService.createNativeTaskQuery()
.sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T1, "
+ managementService.getTableName(VariableInstanceEntity.class) + " V1 WHERE V1.TASK_ID_ = T1.ID_")
.count();
4.5. 变量
每个流程实例都需要并使用数据来执行它存在的步骤。在 Activiti 中,这些数据称为变量,存储在数据库中。变量可以用在表达式中(例如在独占网关中选择正确的传出序列流),在调用外部服务时的java服务任务中(例如提供输入或存储服务调用的结果)等。
流程实例可以有变量(称为流程变量),也可以有执行(指向流程处于活动状态的特定指针)和用户任务可以有变量。一个流程实例可以有任意数量的变量。每个变量都存储在ACT_RU_VARIABLE数据库表中的一行中。
任何startProcessInstanceXXX方法都有一个可选参数,用于在创建和启动流程实例时提供变量。例如,从RuntimeService:
1 ProcessInstance startProcessInstanceByKey(String processDefinitionKey, Map<String, Object> variables);
可以在流程执行期间添加变量。例如(RuntimeService):
1
2
3
4 void setVariable(String executionId, String variableName, Object value);
void setVariableLocal(String executionId, String variableName, Object value);
void setVariables(String executionId, Map<String, ? extends Object> variables);
void setVariablesLocal(String executionId, Map<String, ? extends Object> variables);
请注意,可以为给定的执行设置本地变量(请记住,流程实例由执行树组成)。该变量仅在该执行中可见,在执行树中不可见。如果不应该将数据传播到流程实例级别,或者变量对于流程实例中的某个路径(例如使用并行路径时)具有新值,这可能很有用。
也可以再次获取变量,如下所示。请注意,TaskService上存在类似的方法。这意味着任务(如执行)可以具有仅在任务期间处于活动状态的局部变量。
1
2
3
4
5
6 Map<String, Object> getVariables(String executionId);
Map<String, Object> getVariablesLocal(String executionId);
Map<String, Object> getVariables(String executionId, Collection<String> variableNames);
Map<String, Object> getVariablesLocal(String executionId, Collection<String> variableNames);
Object getVariable(String executionId, String variableName);
<T> T getVariable(String executionId, String variableName, Class<T> variableClass);
1
2
3
4
5
6 execution.getVariables();
execution.getVariables(Collection<String> variableNames);
execution.getVariable(String variableName);
execution.setVariables(Map<String, object> variables);
execution.setVariable(String variableName, Object value);
请注意,带有本地的变体也可用于上述所有内容。
由于历史原因(和向后兼容的原因),在执行上述任何调用时,实际上所有变量都将从数据库中获取。这意味着,如果您有 10 个变量,并且仅通过getVariable("myVariable")获得一个,那么其他 9 个将在幕后被获取并缓存。这还不错,因为后续调用不会再次访问数据库。例如,当您的流程定义具有三个连续的服务任务(因此一个数据库事务)时,使用一次调用来获取第一个服务任务中的所有变量可能比单独获取每个服务任务中所需的变量更好。请注意,这适用于获取和设置变量。
当然,当使用大量变量时,或者只是想严格控制数据库查询和流量时,这是不合适的。自 Activiti 5.17 以来,引入了新方法来对此进行更严格的控制,通过添加具有可选参数的新方法,该参数告诉引擎是否需要在后台获取和缓存所有变量:
1
2
3 Map<String, Object> getVariables(Collection<String> variableNames, boolean fetchAllVariables);
Object getVariable(String variableName, boolean fetchAllVariables);
void setVariable(String variableName, Object value, boolean fetchAllVariables);
当参数fetchAllVariables使用true时,行为将与上面描述的完全一样:获取或设置变量时,将获取并缓存所有其他变量。
但是,当使用false作为值时,将使用特定查询,并且不会获取或缓存其他变量。只有这里有问题的变量的值会被缓存以供后续使用。
4.6. 瞬态变量
瞬态变量是行为类似于常规变量但不持久的变量。通常,瞬态变量用于高级用例(即当有疑问时,使用常规过程变量)。
以下适用于瞬态变量:
-
对于瞬态变量,根本没有存储历史记录。
-
与常规变量一样,瞬态变量在设置时放在最高父级。这意味着在执行时设置变量时,瞬态变量实际上存储在流程实例执行中。与常规变量一样,如果应在特定执行或任务上设置变量,则存在方法的局部变体。
-
只能在流程定义中的下一个等待状态之前访问瞬态变量。在那之后,他们就走了。等待状态在这里表示流程实例中将其持久保存到数据存储的点。请注意,在此定义中,异步活动也是等待状态!
-
瞬态变量只能由setTransientVariable(name, value)设置,但调用getVariable(name)时也会返回瞬态变量(也存在一个getTransientVariable(name),它只检查瞬态变量)。这样做的原因是为了简化表达式的编写,并且使用变量的现有逻辑适用于这两种类型。
-
瞬态变量会隐藏同名的持久变量。这意味着当在流程实例上同时设置持久变量和瞬态变量并使用getVariable("someVariable")时,将返回瞬态变量值。
在暴露常规变量的大多数地方都可以获取和/或设置瞬态变量:
-
关于JavaDelegate实现中的DelegateExecution
-
ExecutionListener实现中的DelegateExecution和TaskListener实现中的DelegateTask
-
通过执行对象在脚本任务中
-
通过运行时服务启动流程实例时
-
完成任务时
-
调用runtimeService.trigger方法时
这些方法遵循常规流程变量的命名约定:
1
2
3
4
5
6
7
8
9
10
11
12
13 void setTransientVariable(String variableName, Object variableValue);
void setTransientVariableLocal(String variableName, Object variableValue);
void setTransientVariables(Map<String, Object> transientVariables);
void setTransientVariablesLocal(Map<String, Object> transientVariables);
Object getTransientVariable(String variableName);
Object getTransientVariableLocal(String variableName);
Map<String, Object> getTransientVariables();
Map<String, Object> getTransientVariablesLocal();
void removeTransientVariable(String variableName);
void removeTransientVariableLocal(String variableName);
下面的 BPMN 图显示了一个典型的例子:
假设Fetch Data服务任务调用某个远程服务(例如使用 REST)。我们还假设需要一些配置参数,并且需要在启动流程实例时提供。此外,这些配置参数对于历史审计目的并不重要,因此我们将它们作为瞬态变量传递:
1
2
3
4
5
6 ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder()
.processDefinitionKey("someKey")
.transientVariable("configParam01", "A")
.transientVariable("configParam02", "B")
.transientVariable("configParam03", "C")
.start();
请注意,在到达用户任务并将其持久化到数据库之前,这些变量都是可用的。例如,在Additional Work用户任务中,它们不再可用。另请注意,如果Fetch Data本来是异步的,那么在该步骤之后它们也将不可用。
获取数据(简化)可能类似于
1
2
3
4
5
6
7
8
9
10 public static class FetchDataServiceTask implements JavaDelegate {
public void execute(DelegateExecution execution) {
String configParam01 = (String) execution.getVariable(configParam01);
// ...
RestReponse restResponse = executeRestCall();
execution.setTransientVariable("response", restResponse.getBody());
execution.setTransientVariable("status", restResponse.getStatus());
}
}
过程数据将获取响应瞬态变量,对其进行解析并将相关数据存储在实际过程变量中,以备我们稍后需要时使用。
离开独占网关的序列流的条件不知道使用的是持久变量还是瞬态变量(在本例中为状态瞬态变量):
1 <conditionExpression xsi:type="tFormalExpression">${status == 200}</conditionExpression>
4.7. 表达式
Activiti 使用 UEL 进行表达式解析。UEL 代表统一表达式语言,是 EE6 规范的一部分(有关详细信息,请参阅EE6 规范)。为了在所有环境中支持最新 UEL 规范的所有功能,我们使用了 JUEL 的修改版本。
表达式可用于例如Java 服务任务、执行侦听器、任务侦听器和条件序列流。虽然有两种类型的表达式,值表达式和方法表达式,但 Activiti 对此进行了抽象,因此它们都可以在expression
需要的地方使用。
-
值表达式:解析为一个值。默认情况下,所有流程变量都可以使用。此外,所有 spring-beans(如果使用 Spring)都可以在表达式中使用。一些例子:
${myVar} ${myBean.myProperty}
-
方法表达式:调用方法,带或不带参数。调用不带参数的方法时,请务必在方法名称后添加空括号(因为这将表达式与值表达式区分开来)。传递的参数可以是文字值或自行解析的表达式。例子:
${printer.print()} ${myBean.addNewOrder('orderName')} ${myBean.doSomething(myVar, 执行)}
请注意,这些表达式支持解析原语(包括比较它们)、bean、列表、数组和映射。
除了所有流程变量之外,还有一些可用于表达式的默认对象:
-
execution
:DelegateExecution
包含有关正在进行的执行的附加信息。 -
task
:DelegateTask
包含有关当前任务的附加信息。注意:仅适用于从任务侦听器评估的表达式。 -
authenticatedUserId
:当前认证的用户的id。如果没有用户通过身份验证,则该变量不可用。
有关更具体的用法和示例,请查看Spring 中的表达式、Java 服务任务、执行侦听器、 任务侦听器或条件序列流。
4.8. 单元测试
业务流程是软件项目不可分割的一部分,它们应该以与测试普通应用程序逻辑相同的方式进行测试:单元测试。由于 Activiti 是一个可嵌入的 Java 引擎,因此为业务流程编写单元测试就像编写常规单元测试一样简单。
Activiti 支持 JUnit 版本 3 和 4 的单元测试风格。在 JUnit 3 样式中,org.activiti.engine.test.ActivitiTestCase
必须扩展 。这将使 ProcessEngine 和服务通过受保护的成员字段可用。在setup()
测试中,processEngine 将默认使用activiti.cfg.xml
类路径上的资源进行初始化。要指定不同的配置文件,请覆盖getConfigurationResource()方法。当配置资源相同时,流程引擎在多个单元测试中被静态缓存。
通过扩展ActivitiTestCase
,您可以使用org.activiti.engine.test.Deployment
. 在运行测试之前testClassName.testMethod.bpmn20.xml
,将部署一个与测试类在同一个包中的表单资源文件。测试结束时会删除部署,包括所有相关的流程实例、任务等。Deployment
注解还支持显式设置资源位置。有关更多信息,请参阅课程本身。
考虑到所有这些,JUnit 3 风格的测试如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13 public class MyBusinessProcessTest extends ActivitiTestCase {
@Deployment
public void testSimpleProcess() {
runtimeService.startProcessInstanceByKey("simpleProcess");
Task task = taskService.createTaskQuery().singleResult();
assertEquals("My Task", task.getName());
taskService.complete(task.getId());
assertEquals(0, runtimeService.createProcessInstanceQuery().count());
}
}
要在使用 JUnit 4 编写单元测试的风格时获得相同的功能,org.activiti.engine.test.ActivitiRule
必须使用规则。通过此规则,流程引擎和服务可通过 getter 获得。与ActivitiTestCase
(见上文)一样,包括这Rule
将启用org.activiti.engine.test.Deployment
注解的使用(见上文关于其使用和配置的说明),它将在类路径中查找默认配置文件。当使用相同的配置资源时,流程引擎在多个单元测试中被静态缓存。
以下代码片段显示了使用 JUnit 4 测试风格的示例以及ActivitiRule
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 public class MyBusinessProcessTest {
@Rule
public ActivitiRule activitiRule = new ActivitiRule();
@Test
@Deployment
public void ruleUsageExample() {
RuntimeService runtimeService = activitiRule.getRuntimeService();
runtimeService.startProcessInstanceByKey("ruleUsage");
TaskService taskService = activitiRule.getTaskService();
Task task = taskService.createTaskQuery().singleResult();
assertEquals("My Task", task.getName());
taskService.complete(task.getId());
assertEquals(0, runtimeService.createProcessInstanceQuery().count());
}
}
4.9. 调试单元测试
当使用内存 H2 数据库进行单元测试时,以下说明允许在调试会话期间轻松检查 Activiti 数据库中的数据。这里的屏幕截图是在 Eclipse 中截取的,但其他 IDE 的机制应该类似。
假设我们在单元测试的某处放置了一个断点。在 Eclipse 中,这是通过双击代码旁边的左边框来完成的:
如果我们现在在调试模式下运行单元测试(在测试类中右键单击,选择Run as,然后选择JUnit test),测试执行将在我们的断点处停止,我们现在可以检查测试的变量,如右图所示上面板。
要检查 Activiti 数据,请打开“显示”窗口(如果此窗口不存在,请打开 Window→Show View→Other 并选择Display。)并输入(代码完成可用)org.h2.tools.Server.createWebServer("-web").start()
选择您刚刚键入的行并右键单击它。现在选择显示(或执行快捷方式而不是右键单击)
现在打开浏览器并转到http://localhost:8082,并填写内存数据库的 JDBC URL(默认为jdbc:h2:mem:activiti
),然后点击连接按钮。
您现在可以查看 Activiti 数据并使用它来了解您的单元测试如何以及为什么以某种方式执行您的流程。
4.10。Web 应用程序中的流程引擎
这ProcessEngine
是一个线程安全的类,可以很容易地在多个线程之间共享。在 Web 应用程序中,这意味着可以在容器启动时创建一次流程引擎,并在容器关闭时关闭引擎。
下面的代码片段展示了如何ServletContextListener
在一个普通的 Servlet 环境中编写一个简单的初始化和销毁流程引擎:
1
2
3
4
5
6
7
8
9
10
11 public class ProcessEnginesServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
ProcessEngines.init();
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ProcessEngines.destroy();
}
}
该contextInitialized
方法将委托给ProcessEngines.init()
. 这将activiti.cfg.xml
在类路径上查找资源文件,并ProcessEngine
为给定的配置创建一个(例如,带有配置文件的多个 jar)。如果类路径上有多个此类资源文件,请确保它们都有不同的名称。当需要流程引擎时,可以使用
1 ProcessEngines.getDefaultProcessEngine()
或者
1 ProcessEngines.getProcessEngine("myName");
当然,也可以使用任何创建流程引擎的变体,如配置部分所述。
上下文侦听器的contextDestroyed
方法委托给ProcessEngines.destroy()
. 这将正确关闭所有已初始化的流程引擎。
5. 弹簧集成
虽然你绝对可以在没有 Spring 的情况下使用 Activiti,但我们已经提供了一些非常好的集成特性,这些特性将在本章中进行说明。
5.1。ProcessEngineFactoryBean
可以将ProcessEngine
其配置为常规 Spring bean。集成的起点是类org.activiti.spring.ProcessEngineFactoryBean
。该 bean 采用流程引擎配置并创建流程引擎。这意味着 Spring 属性的创建和配置与配置部分中记录的相同。对于 Spring 集成,配置和引擎 bean 将如下所示:
1
2
3
4
5
6
7 <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
...
</bean>
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>
请注意,processEngineConfiguration
bean 现在使用org.activiti.spring.SpringProcessEngineConfiguration
该类。
5.2. 交易
我们将SpringTransactionIntegrationTest
逐步解释在 Spring 示例中找到的分布。下面是我们在本例中使用的 Spring 配置文件(您可以在 SpringTransactionIntegrationTest-context.xml 中找到它)。下面显示的部分包含 dataSource、transactionManager、processEngine 和 Activiti Engine 服务。
当将 DataSource 传递给SpringProcessEngineConfiguration
(使用属性“dataSource”)时,Activiti 在org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
内部使用一个,它包装了传递的 DataSource。这样做是为了确保从 DataSource 检索到的 SQL 连接和 Spring 事务可以很好地协同工作。这意味着不再需要在 Spring 配置中自己代理数据源,尽管它仍然允许将 a 传递TransactionAwareDataSourceProxy
到SpringProcessEngineConfiguration
. 在这种情况下,不会发生额外的包装。
确保TransactionAwareDataSourceProxy
自己在 Spring 配置中声明时,不要将它用于已经知道 Spring 事务的资源(例如 DataSourceTransactionManager 和 JPATransactionManager 需要未代理的数据源)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 <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="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="driverClass" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseSchemaUpdate" value="true" />
<property name="asyncExecutorActivate" value="false" />
</bean>
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>
<bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService" />
<bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService" />
<bean id="taskService" factory-bean="processEngine" factory-method="getTaskService" />
<bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService" />
<bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />
...
该 Spring 配置文件的其余部分包含我们将在此特定示例中使用的 bean 和配置:
1
2
3
4
5
6
7
8
9
10
11 <beans>
...
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="userBean" class="org.activiti.spring.test.UserBean">
<property name="runtimeService" ref="runtimeService" />
</bean>
<bean id="printer" class="org.activiti.spring.test.Printer" />
</beans>
首先,应用程序上下文是使用任何 Spring 方法创建的。在本例中,您可以使用类路径 XML 资源来配置我们的 Spring 应用程序上下文:
1
2 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"org/activiti/examples/spring/SpringTransactionIntegrationTest-context.xml");
或者因为它是一个测试:
1 @ContextConfiguration("classpath:org/activiti/spring/test/transaction/SpringTransactionIntegrationTest-context.xml")
然后我们可以获取服务 bean 并在它们上调用方法。ProcessEngineFactoryBean 将向服务添加一个额外的拦截器,在 Activiti 服务方法上应用 Propagation.REQUIRED 事务语义。因此,例如,我们可以使用 repositoryService 来部署这样的流程:
1
2
3
4
5
6
7 RepositoryService repositoryService =
(RepositoryService) applicationContext.getBean("repositoryService");
String deploymentId = repositoryService
.createDeployment()
.addClasspathResource("org/activiti/spring/test/hello.bpmn20.xml")
.deploy()
.getId();
反之亦然。在这种情况下,Spring 事务将围绕 userBean.hello() 方法,而 Activiti 服务方法调用将加入同一个事务。
1
2 UserBean userBean = (UserBean) applicationContext.getBean("userBean");
userBean.hello();
UserBean 看起来像这样。请记住,在上面的 Spring bean 配置中,我们将 repositoryService 注入到 userBean 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 public class UserBean {
/** injected by Spring */
private RuntimeService runtimeService;
@Transactional
public void hello() {
// here you can do transactional stuff in your domain model
// and it will be combined in the same transaction as
// the startProcessInstanceByKey to the Activiti RuntimeService
runtimeService.startProcessInstanceByKey("helloProcess");
}
public void setRuntimeService(RuntimeService runtimeService) {
this.runtimeService = runtimeService;
}
}
5.3. 表达式
使用 ProcessEngineFactoryBean 时,默认情况下,BPMN 流程中的所有表达式也将看到所有 Spring bean。可以使用可以配置的映射来限制要在表达式中公开的 bean,甚至根本不公开任何 bean。下面的示例公开了一个 bean(打印机),可在“打印机”键下使用。 要完全不暴露任何 bean,只需将一个空列表作为SpringProcessEngineConfiguration 上的beans属性传递。当没有设置beans属性时,上下文中的所有 Spring beans 都将可用。
1
2
3
4
5
6
7
8
9
10 <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
...
<property name="beans">
<map>
<entry key="printer" value-ref="printer" />
</map>
</property>
</bean>
<bean id="printer" class="org.activiti.examples.spring.Printer" />
现在公开的 bean 可以在表达式中使用:例如,SpringTransactionIntegrationTesthello.bpmn20.xml
显示了如何使用 UEL 方法表达式调用 Spring bean 上的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 <definitions id="definitions">
<process id="helloProcess">
<startEvent id="start" />
<sequenceFlow id="flow1" sourceRef="start" targetRef="print" />
<serviceTask id="print" activiti:expression="#{printer.printMessage()}" />
<sequenceFlow id="flow2" sourceRef="print" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>
哪里Printer
看起来像这样:
1
2
3
4
5
6 public class Printer {
public void printMessage() {
System.out.println("hello world");
}
}
Spring bean 配置(也如上所示)如下所示:
1
2
3
4
5
6 <beans>
...
<bean id="printer" class="org.activiti.examples.spring.Printer" />
</beans>
5.4. 自动资源部署
Spring 集成还具有用于部署资源的特殊功能。在流程引擎配置中,您可以指定一组资源。创建流程引擎时,将扫描和部署所有这些资源。有适当的过滤可以防止重复部署。只有当资源实际发生变化时,才会将新的部署部署到 Activiti DB。这在很多用例中都是有意义的,在这些用例中,Spring 容器经常重新启动(例如测试)。
这是一个例子:
1
2
3
4
5
6
7
8
9 <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
...
<property name="deploymentResources"
value="classpath*:/org/activiti/spring/test/autodeployment/autodeploy.*.bpmn20.xml" />
</bean>
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>
默认情况下,上面的配置会将所有匹配过滤的资源分组到 Activiti 引擎的单个部署中。用于防止重新部署未更改资源的重复过滤适用于整个部署。在某些情况下,这可能不是您想要的。例如,如果您以这种方式部署一组流程资源并且这些资源中只有一个流程定义发生了变化,那么整个部署将被视为新的,并且该部署中的所有流程定义都将被重新部署,从而导致在每个流程定义的新版本中,即使实际上只更改了一个。
为了能够自定义确定部署的方式,您可以在 , 中指定一个附加SpringProcessEngineConfiguration
属性deploymentMode
。此属性定义从匹配过滤器的资源集确定部署的方式。此属性默认支持 3 个值:
-
default
:将所有资源分组到一个部署中,并将重复过滤应用于该部署。这是默认值,如果您不指定值,将使用它。 -
single-resource
:为每个单独的资源创建单独的部署并将重复过滤应用于该部署。这是您将用于单独部署每个流程定义的值,并且只有在它发生更改时才创建新的流程定义版本。 -
resource-parent-folder
:为共享相同父文件夹的资源创建单独的部署,并将重复过滤应用于该部署。此值可用于为大多数资源创建单独的部署,但仍然可以通过将它们放在共享文件夹中来对它们进行分组。以下是如何指定single-resource
配置的示例deploymentMode
:
1
2
3
4
5
6 <bean id="processEngineConfiguration"
class="org.activiti.spring.SpringProcessEngineConfiguration">
...
<property name="deploymentResources" value="classpath*:/activiti/*.bpmn" />
<property name="deploymentMode" value="single-resource" />
</bean>
除了使用上面列出的值之外deploymentMode
,您可能需要自定义行为来确定部署。如果是这样,您可以创建一个子类SpringProcessEngineConfiguration
并覆盖该getAutoDeploymentStrategy(String deploymentMode)
方法。此方法确定对某个deploymentMode
配置值使用哪种部署策略。
5.5. 单元测试
当与 Spring 集成时,可以使用标准的Activiti 测试工具非常轻松地测试业务流程。以下示例显示了如何在典型的基于 Spring 的单元测试中测试业务流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:org/activiti/spring/test/junit4/springTypicalUsageTest-context.xml")
public class MyBusinessProcessTest {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
@Rule
public ActivitiRule activitiSpringRule;
@Test
@Deployment
public void simpleProcessTest() {
runtimeService.startProcessInstanceByKey("simpleProcess");
Task task = taskService.createTaskQuery().singleResult();
assertEquals("My Task", task.getName());
taskService.complete(task.getId());
assertEquals(0, runtimeService.createProcessInstanceQuery().count());
}
}
请注意,要使其正常工作,您需要在 Spring 配置中定义一个org.activiti.engine.test.ActivitiRule bean(在上面的示例中通过自动装配注入)。
1
2
3 <bean id="activitiRule" class="org.activiti.engine.test.ActivitiRule">
<property name="processEngine" ref="processEngine" />
</bean>
5.6. 带有休眠 4.2.x 的 JPA
在 Activiti Engine 中的服务任务或侦听器逻辑中使用 Hibernate 4.2.x JPA 时,需要对 Spring ORM 的额外依赖。Hibernate 4.1.x 或更低版本不需要这样做。应添加以下依赖项:
1
2
3
4
5 <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
5.7. 弹簧靴
Spring Boot 是一个应用程序框架,根据其网站,可以轻松创建可以“直接运行”的独立的、生产级的基于 Spring 的应用程序。它对 Spring 平台和第三方库有一个固执的看法,因此您可以轻松上手。大多数 Spring Boot 应用程序只需要很少的 Spring 配置。
有关 Spring Boot 的更多信息,请参阅http://projects.spring.io/spring-boot/
Spring Boot - Activiti 集成目前处于试验阶段。它与 Spring 提交者一起开发,但仍处于早期阶段。我们欢迎大家试用并提供反馈。
5.7.1。兼容性
Spring Boot 需要 JDK 7 运行时。请查看 Spring Boot 文档。
5.7.2. 入门
Spring Boot 是关于约定优于配置的。要开始,只需将spring-boot-starters-basic依赖项添加到您的项目中。例如对于 Maven:
1
2
3
4
5 <dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>${activiti.version}</version>
</dependency>
这就是所有需要的。这个依赖会传递性地将正确的 Activiti 和 Spring 依赖添加到类路径中。您现在可以编写 Spring Boot 应用程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
@EnableAutoConfiguration
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Activiti 需要一个数据库来存储它的数据。如果您要运行上面的代码,它会给您一个信息异常消息,您需要将数据库驱动程序依赖项添加到类路径。现在,添加 H2 数据库依赖项:
1
2
3
4
5 <dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.183</version>
</dependency>
现在可以启动应用程序。你会看到这样的输出:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_||))))) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|===============|___/=/_/_/_/ :: Spring Boot :: (v1.1.6.RELEASE) MyApplication : 在 ... 上启动 MyApplication scaAnnotationConfigApplicationContext:刷新 org.springframework.context.annotation.AnnotationConfigApplicationContext@33cb5951:启动日期 [Wed Dec 17 15:24:34 CET 2014];上下文层次的根 asbAbstractProcessEngineConfiguration :未使用指定路径(类路径:/processes/**.bpmn20.xml)找到流程定义。 o.activiti.engine.impl.db.DbSqlSession :使用资源 org/activiti/db/create/activiti.h2.create.engine.sql 在引擎上执行创建 o.activiti.engine.impl.db.DbSqlSession :使用资源 org/activiti/db/create/activiti.h2.create.history.sql 在历史上执行创建 o.activiti.engine.impl.db.DbSqlSession :使用资源 org/activiti/db/create/activiti.h2.create.identity.sql 对身份执行创建 oaengine.impl.ProcessEngineImpl : ProcessEngine 默认创建 oaeiaDefaultAsyncJobExecutor :启动默认的异步作业执行器 [org.activiti.spring.SpringAsyncExecutor]。 oaeiaAcquireTimerJobsRunnable : {} 开始获取异步作业到期 oaeiaAcquireAsyncJobsDueRunnable : {} 开始获取异步作业到期 osjeaAnnotationMBeanExporter :注册 bean 以在启动时公开 JMX MyApplication :在 2.019 秒内启动 MyApplication(JVM 运行 2.294)
因此,只需将依赖项添加到类路径并使用@EnableAutoConfiguration注释,幕后就会发生很多事情:
-
自动创建内存数据源(因为 H2 驱动程序在类路径上)并传递给 Activiti 流程引擎配置
-
创建并公开一个 Activiti ProcessEngine bean
-
所有的 Activiti 服务都暴露为 Spring bean
-
Spring Job Executor 已创建
此外,将自动部署流程文件夹中的任何 BPMN 2.0 流程定义。创建一个文件夹processes并将一个虚拟进程定义(名为one-task-process.bpmn20.xml)添加到此文件夹。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 <?xml version="1.0" encoding="UTF-8"?>
<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<process id="oneTaskProcess" name="The One Task Process">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="theTask" />
<userTask id="theTask" name="my task" />
<sequenceFlow id="flow2" sourceRef="theTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
还添加以下代码行以测试部署是否确实有效。CommandLineRunner是一种特殊的 Spring bean,在应用程序启动时执行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 @Configuration
@ComponentScan
@EnableAutoConfiguration
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
@Bean
public CommandLineRunner init(final RepositoryService repositoryService,
final RuntimeService runtimeService,
final TaskService taskService) {
return new CommandLineRunner() {
@Override
public void run(String... strings) throws Exception {
System.out.println("Number of process definitions : "
+ repositoryService.createProcessDefinitionQuery().count());
System.out.println("Number of tasks : " + taskService.createTaskQuery().count());
runtimeService.startProcessInstanceByKey("oneTaskProcess");
System.out.println("Number of tasks after process start: " + taskService.createTaskQuery().count());
}
};
}
}
输出将如预期:
进程定义数:1 任务数:0 进程启动后的任务数:1
5.7.3. 更改数据库和连接池
如上所述,Spring Boot 是关于约定优于配置的。默认情况下,通过在类路径中只有 H2,它创建了一个内存数据源并将其传递给 Activiti 流程引擎配置。
要更改数据源,只需通过提供 Datasource bean 来覆盖默认值。我们在这里使用DataSourceBuilder类,它是 Spring Boot 的帮助类。如果 Tomcat、HikariCP 或 Commons DBCP 在类路径中,将选择其中一个(按照 Tomcat 优先的顺序)。例如,要切换到 MySQL 数据库:
1
2
3
4
5
6
7
8
9 @Bean
public DataSource database() {
return DataSourceBuilder.create()
.url("jdbc:mysql://127.0.0.1:3306/activiti-spring-boot?characterEncoding=UTF-8")
.username("alfresco")
.password("alfresco")
.driverClassName("com.mysql.jdbc.Driver")
.build();
}
从 Maven 依赖项中删除 H2 并将 MySQL 驱动程序和 Tomcat 连接池添加到类路径:
1
2
3
4
5
6
7
8
9
10 <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>8.0.15</version>
</dependency>
现在启动应用程序时,您会看到它使用 MySQL 作为数据库(以及 Tomcat 连接池框架):
org.activiti.engine.impl.db.DbSqlSession :使用资源 org/activiti/db/create/activiti.mysql.create.engine.sql 在引擎上执行创建 org.activiti.engine.impl.db.DbSqlSession :使用资源 org/activiti/db/create/activiti.mysql.create.history.sql 在历史上执行创建 org.activiti.engine.impl.db.DbSqlSession :使用资源 org/activiti/db/create/activiti.mysql.create.identity.sql 对身份执行创建
当您多次重新启动应用程序时,您会看到任务数量增加(H2 内存数据库无法在关闭后幸存下来,而 MySQL 会)。
5.7.4。REST 支持
通常,在嵌入式 Activiti 引擎之上需要一个 REST API(与公司中的不同服务交互)。Spring Boot 使这变得非常容易。将以下依赖项添加到类路径:
1
2
3
4
5 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
创建一个新类,一个 Spring 服务,并创建两种方法:一种用于启动我们的流程,另一种用于获取给定受让人的任务列表。我们在这里简单地包装了 Activiti 调用,但在现实生活场景中,这显然会更加复杂。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 @Service
public class MyService {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Transactional
public void startProcess() {
runtimeService.startProcessInstanceByKey("oneTaskProcess");
}
@Transactional
public List<Task> getTasks(String assignee) {
return taskService.createTaskQuery().taskAssignee(assignee).list();
}
}
我们现在可以通过使用@RestController注释类来创建 REST 端点。在这里,我们只是简单地委托给上面定义的服务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 @RestController
public class MyRestController {
@Autowired
private MyService myService;
@RequestMapping(value="/process", method= RequestMethod.POST)
public void startProcessInstance() {
myService.startProcess();
}
@RequestMapping(value="/tasks", method= RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)
public List<TaskRepresentation> getTasks(@RequestParam String assignee) {
List<Task> tasks = myService.getTasks(assignee);
List<TaskRepresentation> dtos = new ArrayList<TaskRepresentation>();
for (Task task : tasks) {
dtos.add(new TaskRepresentation(task.getId(), task.getName()));
}
return dtos;
}
static class TaskRepresentation {
private String id;
private String name;
public TaskRepresentation(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
@Service和@RestController都可以通过我们添加到应用程序类的自动组件扫描 ( @ComponentScan ) 找到。再次运行应用程序类。我们现在可以使用例如 cURL 与 REST API 交互:
curl http://localhost:8080/tasks?assignee=kermit [] curl -X POST http://localhost:8080/process curl http://localhost:8080/tasks?assignee=kermit [{"id":"10004","name":"我的任务"}]
5.7.5。JPA 支持
要在 Spring Boot 中添加对 Activiti 的 JPA 支持,请添加以下依赖项:
1
2
3
4
5 <dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-jpa</artifactId>
<version>${activiti.version}</version>
</dependency>
这将添加用于使用 JPA 的 Spring 配置和 bean。默认情况下,JPA 提供者将是 Hibernate。
让我们创建一个简单的实体类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 @Entity
class Person {
@Id
@GeneratedValue
private Long id;
private String username;
private String firstName;
private String lastName;
private Date birthDate;
public Person() {
}
public Person(String username, String firstName, String lastName, Date birthDate) {
this.username = username;
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = birthDate;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
}
默认情况下,当不使用内存数据库时,不会自动创建表。在类路径上创建文件application.properties并添加以下属性:
spring.jpa.hibernate.ddl-auto=更新
添加以下类:
1
2
3
4
5 public interface PersonRepository extends JpaRepository<Person, Long> {
Person findByUsername(String username);
}
这是一个 Spring 存储库,它提供了开箱即用的 CRUD。我们添加了通过用户名查找人员的方法。Spring 将根据约定(即使用的属性名称)自动实现这一点。
我们现在进一步加强我们的服务:
-
通过将@Transactional添加到类。请注意,通过添加上面的 JPA 依赖项,我们之前使用的 DataSourceTransactionManager 现在由 JpaTransactionManager 自动换出。
-
startProcess现在获得了一个受理人用户名,用于查找 Person,并将 Person JPA 对象作为流程实例中的流程变量。
-
添加了创建虚拟用户的方法。这在 CommandLineRunner 中用于填充数据库。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 @Service
@Transactional
public class MyService {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private PersonRepository personRepository;
public void startProcess(String assignee) {
Person person = personRepository.findByUsername(assignee);
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("person", person);
runtimeService.startProcessInstanceByKey("oneTaskProcess", variables);
}
public List<Task> getTasks(String assignee) {
return taskService.createTaskQuery().taskAssignee(assignee).list();
}
public void createDemoUsers() {
if (personRepository.findAll().size() == 0) {
personRepository.save(new Person("jbarrez", "Joram", "Barrez", new Date()));
personRepository.save(new Person("trademakers", "Tijs", "Rademakers", new Date()));
}
}
}
CommandLineRunner 现在看起来像:
1
2
3
4
5
6
7
8
9
10 @Bean
public CommandLineRunner init(final MyService myService) {
return new CommandLineRunner() {
public void run(String... strings) throws Exception {
myService.createDemoUsers();
}
};
}
RestController 也稍作更改以合并上述更改(仅显示新方法),并且 HTTP POST 现在有一个包含受让人用户名的正文:
@RestController 公共类 MyRestController { @自动连线 私人的我的服务我的服务; @RequestMapping(value="/process", method= RequestMethod.POST) 公共无效 startProcessInstance(@RequestBody StartProcessRepresentation startProcessRepresentation) { myService.startProcess(startProcessRepresentation.getAssignee()); } ... 静态类 StartProcessRepresentation { 私有字符串受让人; 公共字符串 getAssignee() { 返回受让人; } 公共无效 setAssignee(字符串受让人){ this.assignee = 受让人; } }
最后,为了尝试 Spring-JPA-Activiti 集成,我们使用流程定义中的 Person JPA 对象的 id 分配任务:
1 <userTask id="theTask" name="my task" activiti:assignee="${person.id}"/>
我们现在可以启动一个新的流程实例,在 POST 正文中提供用户名:
curl -H "Content-Type: application/json" -d '{"assignee" : "jbarrez"}' http://localhost:8080/process
现在使用人员 ID 获取任务列表:
curl http://localhost:8080/tasks?assignee=1 [{"id":"12505","name":"我的任务"}]
5.7.6。延伸阅读
显然,还有很多关于 Spring Boot 的内容尚未涉及,例如非常简单的 JTA 集成或构建可以在主要应用程序服务器上运行的 war 文件。Spring Boot 集成还有更多内容:
-
执行器支持
-
弹簧集成支持
-
Rest API 集成:启动嵌入在 Spring 应用程序中的 Activiti Rest API
-
弹簧安全支持
所有这些领域目前都是第一个版本,但它们将在未来进一步发展。
6. 部署
6.1。业务档案
要部署流程,必须将它们包装在业务档案中。业务档案是部署到 Activiti 引擎的单元。业务档案相当于一个 zip 文件。它可以包含 BPMN 2.0 流程、任务表单、规则和任何其他类型的文件。通常,业务档案包含命名资源的集合。
部署业务档案时,会扫描它以查找带有.bpmn20.xml
或.bpmn
扩展名的 BPMN 文件。每一个都将被解析并且可能包含多个流程定义。
业务档案中的 Java 类不会被添加到类路径中。业务档案中流程定义中使用的所有自定义类(例如 Java 服务任务或事件侦听器实现)都应该存在于 Activiti Engine 类路径中,以便运行流程。 |
6.1.1. 以编程方式部署
从 zip 文件部署业务档案可以这样完成:
1
2
3
4
5
6
7 String barFileName = "path/to/process-one.bar";
ZipInputStream inputStream = new ZipInputStream(new FileInputStream(barFileName));
repositoryService.createDeployment()
.name("process-one.bar")
.addZipInputStream(inputStream)
.deploy();
也可以从单个资源构建部署。有关更多详细信息,请参阅 javadocs。
6.2. 外部资源
流程定义存在于 Activiti 数据库中。当使用 Activiti 配置文件中的服务任务或执行侦听器或 Spring bean 时,这些流程定义可以引用委托类。这些类和 Spring 配置文件必须可供所有可能执行流程定义的流程引擎使用。
6.2.1. Java 类
当流程实例启动时,流程中使用的所有自定义类(例如,服务任务或事件侦听器、TaskListeners 中使用的 JavaDelegate 等)都应该出现在引擎的类路径中。
然而,在部署业务档案期间,这些类不必出现在类路径中。这意味着当使用 Ant 部署新的业务档案时,您的委托类不必位于类路径中。
当您使用演示设置并且想要添加自定义类时,您应该将包含您的类的 jar 添加到 activiti-explorer 或 activiti-rest webapp lib。不要忘记包括自定义类的依赖项(如果有的话)。或者,您可以将依赖项包含在 Tomcat 安装的库目录中,${tomcat.home}/lib
.
6.2.2. 从进程中使用 Spring bean
当表达式或脚本使用 Spring bean 时,这些 bean 在执行流程定义时必须对引擎可用。如果您正在构建自己的 web 应用程序并按照 spring 集成部分中的描述在上下文中配置流程引擎,那很简单。但是请记住,如果您使用它,您还应该使用该上下文更新 Activiti rest webapp。您可以通过将JAR 文件activiti.cfg.xml
中的 替换为包含 Spring 上下文配置的文件来做到这一点。activiti-rest/lib/activiti-cfg.jar
activiti-context.xml
6.2.3. 创建单个应用程序
您可以考虑将 Activiti rest webapp 包含在您自己的 webapp 中,这样只有一个ProcessEngine
.
6.3. 流程定义的版本控制
BPMN 没有版本控制的概念。这实际上很好,因为可执行的 BPMN 流程文件可能会作为您的开发项目的一部分存在于版本控制系统存储库(例如 Subversion、Git 或 Mercurial)中。流程定义的版本是在部署期间创建的。ProcessDefinition
在部署过程中,Activiti 会在存储到 Activiti DB 之前为其分配一个版本。
对于业务档案中的每个流程定义,执行以下步骤来初始化属性key
、version
和:name
id
-
XML 文件中的流程定义属性
id
用作流程定义key
属性。 -
XML 文件中的流程定义属性
name
用作流程定义name
属性。如果未指定 name 属性,则使用 id 属性作为名称。 -
第一次部署具有特定密钥的进程时,会分配版本 1。对于具有相同密钥的流程定义的所有后续部署,版本将设置为比当前部署的最大版本高 1。key 属性用于区分流程定义。
-
id 属性设置为 {processDefinitionKey}:{processDefinitionVersion}:{generated-id},其中
generated-id
是添加的唯一编号,以保证集群环境中进程定义缓存的进程 ID 的唯一性。
以下面的流程为例
1
2
3 <definitions id="myDefinitions" >
<process id="myProcess" name="My important process" >
...
部署此流程定义时,数据库中的流程定义将如下所示:
ID | 钥匙 | 名称 | 版本 |
---|---|---|---|
我的进程:1:676 |
我的进程 |
我的重要过程 |
1 |
假设我们现在部署相同流程的更新版本(例如更改一些用户任务),但id
流程定义保持不变。流程定义表现在将包含以下条目:
ID | 钥匙 | 名称 | 版本 |
---|---|---|---|
我的进程:1:676 |
我的进程 |
我的重要过程 |
1 |
我的进程:2:870 |
我的进程 |
我的重要过程 |
2 |
调用时runtimeService.startProcessInstanceByKey("myProcess")
,它现在将使用带有 version 的流程定义2
,因为这是流程定义的最新版本。
如果我们创建如下定义的第二个流程并将其部署到 Activiti,则第三行将添加到表中。
1
2
3 <definitions id="myNewDefinitions" >
<process id="myNewProcess" name="My important process" >
...
该表将如下所示:
ID | 钥匙 | 名称 | 版本 |
---|---|---|---|
我的进程:1:676 |
我的进程 |
我的重要过程 |
1 |
我的进程:2:870 |
我的进程 |
我的重要过程 |
2 |
我的新进程:1:1033 |
我的新进程 |
我的重要过程 |
1 |
请注意新流程的密钥与我们的第一个流程有何不同。即使名称相同(我们可能也应该更改它),Activitiid
在区分进程时只考虑属性。因此,新流程使用版本 1 进行部署。
6.4. 提供流程图
可以将流程图图像添加到部署中。此图像将存储在 Activiti 存储库中,并可通过 API 访问。此图像还用于可视化 Activiti Explorer 中的过程。
假设我们的类路径上有一个进程,org/activiti/expenseProcess.bpmn20.xml
它有一个进程密钥费用。流程图图像的以下命名约定适用(按此特定顺序):
-
如果部署中存在一个图像资源,其名称为 BPMN 2.0 XML 文件名,并与流程密钥和图像后缀连接,则使用此图像。在我们的示例中,这将是
org/activiti/expenseProcess.expense.png
(或 .jpg/gif)。如果您在一个 BPMN 2.0 XML 文件中定义了多个图像,那么这种方法最有意义。然后,每个图表图像将在其文件名中包含流程密钥。 -
如果不存在这样的图像,则搜索部署中与 BPMN 2.0 XML 文件的名称匹配的图像资源。在我们的示例中,这将是
org/activiti/expenseProcess.png
. 请注意,这意味着在同一个 BPMN 2.0 文件中定义的每个流程定义都具有相同的流程图图像。如果每个 BPMN 2.0 XML 文件中只有一个流程定义,这显然不是问题。
以编程方式部署时的示例:
1
2
3
4
5 repositoryService.createDeployment()
.name("expense-process.bar")
.addClasspathResource("org/activiti/expenseProcess.bpmn20.xml")
.addClasspathResource("org/activiti/expenseProcess.png")
.deploy();
之后可以通过API获取图片资源:
1
2
3
4
5
6
7 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("expense")
.singleResult();
String diagramResourceName = processDefinition.getDiagramResourceName();
InputStream imageStream = repositoryService.getResourceAsStream(
processDefinition.getDeploymentId(), diagramResourceName);
6.5。生成流程图
如果部署中没有提供图像,如上一节所述,如果流程定义包含必要的图表交换信息,Activiti 引擎将生成图表图像。
可以以与在部署中提供图像时完全相同的方式检索资源。
如果由于某种原因,在部署期间不需要或不想生成图表,则isCreateDiagramOnDeploy
可以在流程引擎配置上设置属性:
1 <property name="createDiagramOnDeploy" value="false" />
现在不会生成图表。
6.6. 类别
部署和流程定义都有用户定义的类别。流程定义类别是BPMN文件中属性的初始化值:<definitions … targetNamespace="yourCategory" …
可以在 API 中指定部署类别,如下所示:
1
2
3
4
5 repositoryService
.createDeployment()
.category("yourCategory")
...
.deploy();
7. BPMN 2.0 简介
7.1。什么是 BPMN?
请参阅我们关于 BPMN 2.0 的常见问题解答条目。
7.2. 定义流程
本简介是在您使用Eclipse IDE创建和编辑文件的假设下编写的。然而,其中很少是特定于 Eclipse 的。您可以使用您喜欢的任何其他工具来创建包含 BPMN 2.0 的 XML 文件。 |
创建一个新的 XML 文件(右键单击任何项目并选择 New→Other→XML-XML File)并为其命名。确保文件以 .bpmn20.xml 或 .bpmn 结尾,否则引擎将不会选择此文件进行部署。
BPMN 2.0 模式的根元素是definitions
元素。在这个元素中,可以定义多个流程定义(尽管我们建议每个文件中只有一个流程定义,因为这样可以简化开发流程后期的维护)。一个空的流程定义如下所示。请注意,最小definitions
元素只需要xmlns
andtargetNamespace
声明。targetNamespace
可以是任何东西,并且对于对流程定义进行分类很有用。
1
2
3
4
5
6
7
8
9
10 <definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<process id="myProcess" name="My First Process">
..
</process>
</definitions>
您还可以选择添加 BPMN 2.0 XML 模式的在线模式位置,作为 Eclipse 中 XML 目录配置的替代方案。
1
2
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd
该process
元素有两个属性:
-
id:这个属性是必需的并且映射到一个 Activiti对象的key属性。
ProcessDefinition
然后id
可以startProcessInstanceByKey
通过RuntimeService
. 此方法将始终采用流程定义的最新部署版本。
1 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess");
-
这里需要注意的重要一点是,这与调用
startProcessInstanceById
方法不同。该方法需要 Activiti 引擎在部署时生成的 String id,并且可以通过调用该processDefinition.getId()
方法来检索。生成的 id 格式为key:version,长度限制为64 个字符。如果你得到一个ActivitiException
说明生成的 id 太长,限制在进程的key字段中的文本。 -
name:此属性是可选的,并映射到 a 的name属性
ProcessDefinition
。引擎本身不使用此属性,因此它可用于在用户界面中显示更人性化的名称,例如。
[[10分钟教程]]
7.3. 入门:10 分钟教程
在本节中,我们将介绍一个(非常简单的)业务流程,我们将使用它来介绍一些基本的 Activiti 概念和 Activiti API。
7.3.1. 先决条件
本教程假设您正在运行 Activiti 演示设置,并且您使用的是独立的 H2 服务器。编辑db.properties
并设置jdbc.url=jdbc:h2:tcp://localhost/activiti
,然后根据H2 的文档运行独立服务器。
7.3.2. 目标
本教程的目标是了解 Activiti 和一些基本的 BPMN 2.0 概念。最终结果将是一个简单的 Java SE 程序,它部署了一个流程定义,并通过 Activiti 引擎 API 与该流程进行交互。我们还将接触一些围绕 Activiti 的工具。当然,您在本教程中学到的内容也可以用于围绕业务流程构建自己的 Web 应用程序。
7.3.3. 用例
用例很简单:我们有一家公司,我们称之为 BPMCorp。在 BPMCorp,每个月都需要为公司股东编写一份财务报告。这是会计部门的职责。报告完成后,需要一名高级管理人员批准该文件,然后才能发送给所有股东。
7.3.4. 流程图
如上所述的业务流程可以使用 Activiti Designer以图形方式可视化。但是,对于本教程,我们将自己键入 XML,因为此时我们以这种方式学习得最多。我们流程的图形化 BPMN 2.0 符号如下所示:
7.3.5。XML 表示
此业务流程 ( FinancialReportProcess.bpmn20.xml ) 的 XML 版本如下所示。很容易识别我们流程的主要元素(单击链接转到该 BPMN 2.0 构造的详细部分):
-
( none) start 事件告诉我们进程的入口点是什么。
-
用户任务声明是我们流程的人工任务的表示。请注意,第一个任务分配给会计组,而第二个任务分配给管理组。有关如何将用户和组分配给用户任务的更多信息,请参阅用户任务分配部分。
-
当到达无结束事件时,该过程结束。
-
元素通过序列流相互连接。这些序列流有一个
source
和target
,定义了序列流的方向。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 <definitions id="definitions"
targetNamespace="http://activiti.org/bpmn20"
xmlns:activiti="http://activiti.org/bpmn"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
<process id="financialReport" name="Monthly financial report reminder process">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="writeReportTask" />
<userTask id="writeReportTask" name="Write monthly financial report" >
<documentation>
Write monthly financial report for publication to shareholders.
</documentation>
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>accountancy</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<sequenceFlow id="flow2" sourceRef="writeReportTask" targetRef="verifyReportTask" />
<userTask id="verifyReportTask" name="Verify monthly financial report" >
<documentation>
Verify monthly financial report composed by the accountancy department.
This financial report is going to be sent to all the company shareholders.
</documentation>
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>management</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<sequenceFlow id="flow3" sourceRef="verifyReportTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
7.3.6。启动流程实例
我们现在已经创建了业务流程的流程定义。从这样的流程定义中,我们可以创建流程实例。在这种情况下,一个流程实例将与特定月份的单个财务报告的创建和验证相匹配。所有流程实例共享相同的流程定义。
为了能够从给定的流程定义创建流程实例,我们必须首先部署此流程定义。部署流程定义意味着两件事:
-
流程定义将存储在为您的 Activiti 引擎配置的持久数据存储中。因此,通过部署我们的业务流程,我们确保引擎将在引擎重启后找到流程定义。
-
BPMN 2.0 流程文件将被解析为内存中的对象模型,可以通过 Activiti API 进行操作。
更多关于部署的信息可以在关于部署的专门章节中找到。
如该部分所述,部署可以通过多种方式进行。一种方法是通过 API,如下所示。请注意,与 Activiti 引擎的所有交互都是通过其服务发生的。
1
2
3 Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();
id
现在我们可以使用我们在流程定义中定义的启动一个新的流程实例(参见 XML 文件中的流程元素)。请注意,这id
在 Activiti 术语中称为key。
1 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("financialReport");
这将创建一个流程实例,该实例将首先通过启动事件。在开始事件之后,它遵循所有传出的序列流(在这种情况下只有一个),并达到第一个任务(编写月度财务报告)。Activiti 引擎现在将在持久数据库中存储一个任务。此时,附加到任务的用户或组分配被解析并存储在数据库中。需要注意的是,Activiti 引擎会继续流程执行步骤,直到它达到等待状态,例如用户任务。在这种等待状态下,流程实例的当前状态存储在数据库中。它会一直保持这种状态,直到用户决定完成他们的任务。此时,引擎将继续运行,直到达到新的等待状态或进程结束。当引擎重新启动或同时崩溃时,进程的状态在数据库中是安全且良好的。
创建任务后,该startProcessInstanceByKey
方法将返回,因为用户任务活动处于等待状态。在这种情况下,任务被分配给一个组,这意味着该组的每个成员都是执行任务的候选人。
我们现在可以将所有这些放在一起并创建一个简单的 Java 程序。创建一个新的 Eclipse 项目并将 Activiti JAR 和依赖项添加到它的类路径中(这些可以在Activiti 发行版的libs文件夹中找到)。在我们可以调用 Activiti 服务之前,我们必须首先构造一个ProcessEngine
允许我们访问服务的方法。在这里,我们使用“独立”配置,它构造了一个ProcessEngine
使用演示设置中也使用的数据库的数据库。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 public static void main(String[] args) {
// Create Activiti process engine
ProcessEngine processEngine = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration()
.buildProcessEngine();
// Get Activiti services
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
// Deploy the process definition
repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();
// Start a process instance
runtimeService.startProcessInstanceByKey("financialReport");
}
7.3.7. 任务清单
我们现在可以TaskService
通过添加以下逻辑来检索此任务:
1 List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit").list();
请注意,我们传递给此操作的用户需要是会计组的成员,因为这是在流程定义中声明的:
1
2
3
4
5 <potentialOwner>
<resourceAssignmentExpression>
<formalExpression>accountancy</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
我们还可以使用任务查询 API 来使用组名来获得相同的结果。我们现在可以将以下逻辑添加到我们的代码中:
1
2 TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();
由于我们已经将我们配置ProcessEngine
为使用与演示设置相同的数据库,我们现在可以登录到Activiti Explorer。默认情况下,会计组中没有用户。使用 kermit/kermit 登录,单击 Groups,然后单击“Create group”。然后单击用户并将组添加到 fozzie。现在用fozzie/fozzie登录,我们会发现选择Processes页面,点击“Monthly Financial Report”流程对应的“Actions”栏中的“Start Process”链接,就可以启动我们的业务流程了。
如上所述,该过程将执行到第一个用户任务。由于我们以 kermit 身份登录,因此我们可以看到在我们启动流程实例后有一个新的候选任务可供他使用。选择“任务”页面以查看此新任务。请注意,即使该流程是由其他人启动的,该任务仍将作为候选任务对会计组中的每个人可见。
7.3.8. 领取任务
会计师现在需要申请这项任务。通过认领任务,特定用户将成为任务的受让人,并且该任务将从会计组其他成员的每个任务列表中消失。声明任务以编程方式完成,如下所示:
1 taskService.claim(task.getId(), "fozzie");
该任务现在位于声称该任务的人的个人任务列表中。
1 List<Task> tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();
在 Activiti UI App 中,点击认领按钮会调用同样的操作。该任务现在将移至已登录用户的个人任务列表。您还可以看到,任务的受理人已更改为当前登录用户。
7.3.9。完成任务
会计师现在可以开始处理财务报告。一旦报告完成,他就可以完成任务,这意味着该任务的所有工作都完成了。
1 taskService.complete(task.getId());
对于 Activiti 引擎,这是必须继续执行流程实例的外部信号。任务本身已从运行时数据中删除。跟随任务的单个传出转换,将执行移至第二个任务(“验证报告”)。现在将使用与第一个任务相同的机制来分配第二个任务,只是任务将分配给 管理组的细微差别。
在演示设置中,通过单击任务列表中的完成按钮来完成任务。由于 Fozzie 不是会计,我们需要退出 Activiti Explorer 并以kermit(他是经理)的身份登录。第二个任务现在在未分配的任务列表中可见。
7.3.10。结束进程
验证任务可以按照与以前完全相同的方式进行检索和声明。完成第二个任务会将流程执行移至结束事件,该事件完成流程实例。流程实例和所有相关的运行时执行数据将从数据存储中删除。
当您登录 Activiti Explorer 时,您可以验证这一点,因为不会在存储流程执行的表中找到任何记录。
以编程方式,您还可以使用historyService
1
2
3
4 HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());
7.3.11。代码概述
结合前面部分的所有片段,你应该有这样的东西(这段代码考虑到你可能已经通过 Activiti Explorer UI 启动了一些流程实例。因此,它总是检索任务列表而不是一个任务,所以它总是有效的):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 public class TenMinuteTutorial {
public static void main(String[] args) {
// Create Activiti process engine
ProcessEngine processEngine = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration()
.buildProcessEngine();
// Get Activiti services
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
// Deploy the process definition
repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();
// Start a process instance
String procId = runtimeService.startProcessInstanceByKey("financialReport").getId();
// Get the first task
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();
for (Task task : tasks) {
System.out.println("Following task is available for accountancy group: " + task.getName());
// claim it
taskService.claim(task.getId(), "fozzie");
}
// Verify Fozzie can now retrieve the task
tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();
for (Task task : tasks) {
System.out.println("Task for fozzie: " + task.getName());
// Complete the task
taskService.complete(task.getId());
}
System.out.println("Number of tasks for fozzie: "
+ taskService.createTaskQuery().taskAssignee("fozzie").count());
// Retrieve and claim the second task
tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
for (Task task : tasks) {
System.out.println("Following task is available for management group: " + task.getName());
taskService.claim(task.getId(), "kermit");
}
// Completing the second task ends the process
for (Task task : tasks) {
taskService.complete(task.getId());
}
// verify that the process is actually finished
HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());
}
}
7.3.12。未来的增强
很容易看出,这个业务流程过于简单,无法在现实中使用。但是,当您在 Activiti 中使用 BPMN 2.0 结构时,您将能够通过以下方式增强业务流程:
-
定义充当决策的网关。这样,经理可以拒绝财务报告,这将为会计师重新创建任务。
-
声明和使用变量,以便我们可以存储或引用报表,以便它可以在表单中可视化。
-
在流程结束时定义服务任务,将报告发送给每个股东。
-
等等。
8. BPMN 2.0 构造
本章涵盖了 Activiti 支持的 BPMN 20 结构以及对 BPMN 标准的自定义扩展。
8.1。自定义扩展
BPMN 2.0 标准对所有相关方来说都是一件好事。最终用户不会因依赖专有解决方案而受到供应商锁定的困扰。框架,尤其是诸如 Activiti 之类的开源框架,可以实现与大型供应商具有相同(并且通常实现更好;-)功能的解决方案。由于 BPMN 2.0 标准,从如此大的供应商解决方案向 Activiti 的过渡是一条简单而顺利的道路。
然而,标准的不利之处在于,它始终是不同公司(通常是愿景)之间多次讨论和妥协的结果。作为一名阅读流程定义的 BPMN 2.0 XML 的开发人员,有时会觉得某些结构或做事方式过于繁琐。由于 Activiti 将易于开发作为首要任务,我们引入了称为Activiti BPMN 扩展的东西。这些扩展是新的结构或方法来简化不在 BPMN 2.0 规范中的某些结构。
尽管 BPMN 2.0 规范明确指出它是为自定义扩展而设计的,但我们确保:
-
这种自定义扩展的先决条件是必须始终对标准的处理方式进行简单的转换。因此,当您决定使用自定义扩展时,您不必担心没有退路。
-
使用自定义扩展时,总是通过为新的 XML 元素、属性等提供activiti:命名空间前缀来清楚地表明这一点。
因此,是否要使用自定义扩展完全取决于您。有几个因素会影响这个决定(图形编辑器的使用、公司政策等)。我们只提供它们是因为我们相信标准中的某些点可以更简单或更有效地完成。随意给我们(正面和/或负面)关于我们的扩展的反馈,或发布自定义扩展的新想法。谁知道,有一天你的想法可能会出现在规范中!
8.2. 活动
事件用于对生命周期过程中发生的事情进行建模。事件总是被可视化为一个圆圈。在 BPMN 2.0 中,存在两个主要的事件类别:捕捉事件或投掷事件。
-
捕获:当流程执行到达事件时,它会等待触发发生。触发器的类型由内部图标或 XML 中的类型声明定义。捕捉事件通过未填充的内部图标(即白色)在视觉上与投掷事件区分开来。
-
抛出:当流程执行到达事件时,触发触发器。触发器的类型由内部图标或 XML 中的类型声明定义。用黑色填充的内部图标在视觉上将投掷事件与捕捉事件区分开来。
8.2.1。事件定义
事件定义定义了事件的语义。没有事件定义,事件“没有什么特别的”。例如,没有事件定义的启动事件并没有指定确切启动流程的原因。如果我们将事件定义添加到启动事件(例如计时器事件定义),我们声明事件的“类型”启动流程(在计时器事件定义的情况下,到达某个时间点的事实) .
8.2.2. 定时器事件定义
1
2
3 <timerEventDefinition activiti:businessCalendarName="custom">
...
</timerEventDefinition>
其中 businessCalendarName 指向流程引擎配置中的业务日历。如果省略业务日历,则使用默认业务日历。
计时器定义必须具有以下元素中的一个:
-
时间日期。此格式以ISO 8601格式指定固定日期,触发触发器的时间。例子:
1
2
3 <timerEventDefinition>
<timeDate>2011-03-11T12:13:14</timeDate>
</timerEventDefinition>
-
持续时间。要指定计时器在触发之前应该运行多长时间,可以将timeDuration指定为timerEventDefinition的子元素。使用的格式是ISO 8601格式(根据 BPMN 2.0 规范的要求)。示例(间隔持续 10 天):
1
2
3 <timerEventDefinition>
<timeDuration>P10D</timeDuration>
</timerEventDefinition>
-
时间周期。指定重复间隔,这对于定期启动进程或为过期用户任务发送多个提醒很有用。时间周期元素可以有两种格式。首先是重复持续时间的格式,由ISO 8601标准指定。示例(3 个重复间隔,每个持续 10 小时):
也可以将endDate指定为timeCycle上的可选属性,或者在 time 表达式的末尾指定,如下所示R3/PT10H/${EndDate}
:当达到 endDate 时,应用程序将停止为此任务创建其他作业。它接受静态值ISO 8601标准作为值,例如“2015-02-25T16:42:11+00:00”或变量${EndDate}
1
2
3 <timerEventDefinition>
<timeCycle activiti:endDate="2015-02-25T16:42:11+00:00">R3/PT10H</timeCycle>
</timerEventDefinition>
1
2
3 <timerEventDefinition>
<timeCycle>R3/PT10H/${EndDate}</timeCycle>
</timerEventDefinition>
如果两者都指定,则系统将使用指定为属性的 endDate。
目前只有BoundaryTimerEvents和CatchTimerEvent支持EndDate功能。
此外,您可以使用 cron 表达式指定时间周期,下面的示例显示触发器每 5 分钟触发一次,从整点开始:
0 0/5 * * * ?
请参阅本教程以了解如何使用 cron 表达式。
注意:第一个符号表示秒,而不是正常 Unix cron 中的分钟。
循环持续时间更适合处理相对计时器,这些计时器是根据某个特定时间点(例如用户任务开始的时间)计算的,而 cron 表达式可以处理绝对计时器 - 这对于计时器启动事件特别有用。
您可以将表达式用于计时器事件定义,这样您就可以根据流程变量影响计时器定义。对于适当的计时器类型,流程变量必须包含 ISO 8601(或循环类型的 cron)字符串。
1
2
3
4
5 <boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="firstLineSupport">
<timerEventDefinition>
<timeDuration>${duration}</timeDuration>
</timerEventDefinition>
</boundaryEvent>
注意:计时器仅在启用作业或异步执行器时触发(即需要将 jobExecutorActivate 或 asyncExecutorActivate 设置为,因为作业true
和activiti.cfg.xml
异步执行器默认禁用)。
8.2.3. 错误事件定义
重要提示: BPMN 错误与 Java 异常不同。事实上,两者并没有什么共同之处。BPMN 错误事件是对业务异常进行建模的一种方式。Java 异常以它们自己的特定方式处理。
1
2
3 <endEvent id="myErrorEndEvent">
<errorEventDefinition errorRef="myError" />
</endEvent>
8.2.4. 信号事件定义
信号事件是引用命名信号的事件。信号是全局范围的事件(广播语义),并被传递给所有活动的处理程序(等待流程实例/捕获信号事件)。
使用signalEventDefinition
元素声明信号事件定义。该属性signalRef
引用signal
声明为definitions
根元素的子元素的元素。下面是一个信号事件被中间事件抛出和捕获的过程的摘录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 <definitions... >
<!-- declaration of the signal -->
<signal id="alertSignal" name="alert" />
<process id="catchSignal">
<intermediateThrowEvent id="throwSignalEvent" name="Alert">
<!-- signal event definition -->
<signalEventDefinition signalRef="alertSignal" />
</intermediateThrowEvent>
...
<intermediateCatchEvent id="catchSignalEvent" name="On Alert">
<!-- signal event definition -->
<signalEventDefinition signalRef="alertSignal" />
</intermediateCatchEvent>
...
</process>
</definitions>
signalEventDefinition
s 引用相同的元素signal
。
抛出信号事件
信号可以由流程实例使用 BPMN 构造或以编程方式使用 java API 抛出。可以使用以下方法org.activiti.engine.RuntimeService
以编程方式抛出信号:
1
2 RuntimeService.signalEventReceived(String signalName);
RuntimeService.signalEventReceived(String signalName, String executionId);
signalEventReceived(String signalName);
和之间的区别在于signalEventReceived(String signalName, String executionId);
,第一种方法将信号全局抛出给所有订阅的处理程序(广播语义),而第二种方法仅将信号传递给特定的执行。
捕捉信号事件
信号事件可以被中间捕获信号事件或信号边界事件捕获。
查询信号事件订阅
可以查询订阅了特定信号事件的所有执行:
1
2
3 List<Execution> executions = runtimeService.createExecutionQuery()
.signalEventSubscriptionName("alert")
.list();
然后我们可以使用该signalEventReceived(String signalName, String executionId)
方法将信号传递给这些执行。
信号事件范围
默认情况下,信号是广播过程引擎范围内的。这意味着您可以在一个流程实例中抛出一个信号事件,其他具有不同流程定义的流程实例可以对该事件的发生做出反应。
但是,有时只希望在同一流程实例中对信号事件做出反应。例如,如果两个或多个活动是互斥的,则用例是流程实例中的同步机制。
要限制信号事件的范围,请将(非 BPMN 2.0 标准!)范围属性添加到信号事件定义中:
1 <signal id="alertSignal" name="alert" activiti:scope="processInstance"/>
此属性的默认值为"global"。
信号事件示例
以下是使用信号进行通信的两个独立进程的示例。如果保险单被更新或更改,则开始第一个过程。在人工参与者审查更改后,会引发信号事件,表明策略已更改:
这个事件现在可以被所有感兴趣的流程实例捕获。以下是订阅事件的流程示例。
注意:重要的是要了解信号事件会广播到所有活动的处理程序。这意味着在上面给出的示例中,所有捕获信号的进程实例都将接收到事件。在这种情况下,这就是我们想要的。但是,也有广播行为是无意的情况。考虑以下过程:
BPMN 不支持上述流程中描述的模式。这个想法是在执行“做某事”任务时抛出的错误被边界错误事件捕获,并将使用信号抛出事件传播到并行执行路径,然后中断“并行做某事”任务。到目前为止,Activiti 将按预期执行。信号将被传播到捕获边界事件并中断任务。但是,由于信号的广播语义,它也会传播到所有其他订阅了信号事件的流程实例。在这种情况下,这可能不是我们想要的。
注意:信号事件不会与特定流程实例执行任何类型的关联。相反,它被广播到所有流程实例。如果您只需要向特定流程实例传递信号,请手动执行关联并使用 signalEventReceived(String signalName, String executionId)
适当的查询机制。
Activiti 确实有办法解决这个问题,通过将scope属性添加到信号事件并将其设置为processInstance。
8.2.5。消息事件定义
消息事件是引用命名消息的事件。消息具有名称和有效负载。与信号不同,消息事件总是针对单个接收者。
使用messageEventDefinition
元素声明消息事件定义。该属性messageRef
引用message
声明为definitions
根元素的子元素的元素。下面是一个过程的摘录,其中两个消息事件由一个开始事件和一个中间捕获消息事件声明和引用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 <definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples"
xmlns:tns="Examples">
<message id="newInvoice" name="newInvoiceMessage" />
<message id="payment" name="paymentMessage" />
<process id="invoiceProcess">
<startEvent id="messageStart" >
<messageEventDefinition messageRef="newInvoice" />
</startEvent>
...
<intermediateCatchEvent id="paymentEvt" >
<messageEventDefinition messageRef="payment" />
</intermediateCatchEvent>
...
</process>
</definitions>
抛出消息事件
作为一个可嵌入的流程引擎,Activiti 并不关心实际接收消息。这将取决于环境并需要特定于平台的活动,例如连接到 JMS(Java 消息服务)队列/主题或处理 Web 服务或 REST 请求。因此,您必须将消息接收作为嵌入流程引擎的应用程序或基础架构的一部分来实现。
在您的应用程序中收到一条消息后,您必须决定如何处理它。如果消息应该触发新流程实例的启动,请在运行时服务提供的以下方法中进行选择:
1
2
3 ProcessInstance startProcessInstanceByMessage(String messageName);
ProcessInstance startProcessInstanceByMessage(String messageName, Map<String, Object> processVariables);
ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey, Map<String, Object> processVariables);
这些方法允许使用引用的消息启动流程实例。
如果消息需要由现有流程实例接收,您首先必须将消息与特定流程实例相关联(参见下一节),然后触发等待执行的继续。运行时服务提供以下方法来触发基于消息事件订阅的执行:
1
2 void messageEventReceived(String messageName, String executionId);
void messageEventReceived(String messageName, String executionId, HashMap<String, Object> processVariables);
查询消息事件订阅
-
在消息启动事件的情况下,消息事件订阅与特定的流程定义相关联。可以使用以下方式查询此类消息订阅
ProcessDefinitionQuery
:
1
2
3 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.messageEventSubscription("newCallCenterBooking")
.singleResult();
由于特定消息订阅只能有一个流程定义,因此查询总是返回零个或一个结果。如果更新了流程定义,则只有最新版本的流程定义订阅了消息事件。
-
在中间捕获消息事件的情况下,消息事件订阅与特定执行相关联。可以使用以下方式查询此类消息事件订阅
ExecutionQuery
:
1
2
3
4 Execution execution = runtimeService.createExecutionQuery()
.messageEventSubscriptionName("paymentReceived")
.variableValueEquals("orderId", message.getOrderId())
.singleResult();
此类查询称为相关查询,通常需要有关流程的知识(在这种情况下,给定的 orderId 最多有一个流程实例)。
消息事件示例
以下是可以使用两种不同消息启动的进程示例:
如果流程需要替代方式来对不同的开始事件做出反应但最终以统一的方式继续,这将很有用。
8.2.6。开始活动
开始事件指示流程从哪里开始。启动事件的类型(进程在消息到达时启动,在特定时间间隔等),定义进程如何启动,在事件的可视化表示中显示为一个小图标。在 XML 表示中,类型由子元素的声明给出。
开始事件总是在捕捉:从概念上讲,事件(在任何时候)一直等到某个触发器发生。
在开始事件中,可以指定以下 Activiti 特定的属性:
-
发起者:标识进程启动时将存储经过身份验证的用户标识的变量名称。例子:
1 <startEvent id="request" activiti:initiator="initiator" />
必须使用IdentityService.setAuthenticatedUserId(String)
try-finally 块中的方法设置经过身份验证的用户,如下所示:
1
2
3
4
5
6 try {
identityService.setAuthenticatedUserId("bono");
runtimeService.startProcessInstanceByKey("someProcessKey");
} finally {
identityService.setAuthenticatedUserId(null);
}
此代码被烘焙到 Activiti Explorer 应用程序中。所以它与Forms结合使用。
8.2.7. 无 开始事件
描述
从技术上讲,无启动事件意味着启动流程实例的触发器是未指定的。这意味着引擎无法预测何时必须启动流程实例。当流程实例通过 API 调用startProcessInstanceByXXX方法之一启动时,将使用 none start 事件。
1 ProcessInstance processInstance = runtimeService.startProcessInstanceByXXX();
注意:子流程总是有一个无启动事件。
图形符号
无启动事件被可视化为一个没有内部图标的圆圈(即没有触发类型)。
XML 表示
无启动事件的 XML 表示是正常的启动事件声明,没有任何子元素(其他启动事件类型都有一个声明类型的子元素)。
1 <startEvent id="start" name="my start event" />
无开始事件的自定义扩展
formKey:对用户在启动新流程实例时必须填写的表单模板的引用。更多信息可以在表格部分中找到示例:
1 <startEvent id="request" activiti:formKey="org/activiti/examples/taskforms/request.form" />
8.2.8. 定时器启动事件
描述
计时器启动事件用于在给定时间创建流程实例。它既可用于应仅启动一次的进程,也可用于应以特定时间间隔启动的进程。
注意:子流程不能有计时器启动事件。
注意:一旦部署流程,就会安排启动计时器事件。无需调用startProcessInstanceByXXX,但调用startProcessInstance方法不受限制,在startProcessInstanceByXXX调用时会导致进程再启动一次。
注意:当一个带有启动定时器事件的流程的新版本被部署时,与之前的定时器对应的作业将被移除。原因是通常不希望自动启动旧版本流程的新流程实例。
图形符号
无开始事件被可视化为带有时钟内部图标的圆圈。
XML 表示
定时器启动事件的 XML 表示是正常的启动事件声明,带有定时器定义子元素。有关配置详细信息,请参阅定时器定义。
示例:进程将启动 4 次,间隔 5 分钟,从 2011 年 3 月 11 日 12:13 开始
1
2
3
4
5 <startEvent id="theStart">
<timerEventDefinition>
<timeCycle>R4/2011-03-11T12:13/PT5M</timeCycle>
</timerEventDefinition>
</startEvent>
示例:流程将在选定日期开始一次
1
2
3
4
5 <startEvent id="theStart">
<timerEventDefinition>
<timeDate>2011-03-11T12:13:14</timeDate>
</timerEventDefinition>
</startEvent>
8.2.9。消息启动事件
描述
消息启动事件可用于使用命名消息启动流程实例。这有效地允许我们使用消息名称从一组替代启动事件中选择正确的启动事件。
部署具有一个或多个消息启动事件的流程定义时,需要考虑以下注意事项:
-
消息启动事件的名称在给定的流程定义中必须是唯一的。一个流程定义不能有多个同名的消息启动事件。Activiti 在部署流程定义时抛出异常,如果两个或多个消息启动事件引用具有相同消息名称的消息,则两个或多个消息启动事件引用相同的消息。
-
消息启动事件的名称在所有部署的流程定义中必须是唯一的。Activiti 在部署流程定义时会抛出异常,这样一个或多个消息启动事件会引用与已由不同流程定义部署的消息启动事件同名的消息。
-
流程版本控制:在部署流程定义的新版本时,取消之前版本的消息订阅。对于新版本中不存在的消息事件也是如此。
启动流程实例时,可以使用以下方法触发消息启动事件RuntimeService
:
1
2
3 ProcessInstance startProcessInstanceByMessage(String messageName);
ProcessInstance startProcessInstanceByMessage(String messageName, Map<String, Object> processVariables);
ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey, Map<String, Object< processVariables);
messageName
是由 的属性引用的元素的属性name
中message
给出的名称。启动流程实例时适用以下注意事项:messageRef
messageEventDefinition
-
消息启动事件仅在顶级进程上受支持。嵌入式子进程不支持消息启动事件。
-
如果一个流程定义有多个消息启动事件,
runtimeService.startProcessInstanceByMessage(…)
允许选择适当的启动事件。 -
如果一个流程定义有多个消息启动事件和一个无启动事件,
runtimeService.startProcessInstanceByKey(…)
并且runtimeService.startProcessInstanceById(…)
使用无启动事件启动一个流程实例。 -
如果一个流程定义有多个消息启动事件且没有无启动事件,
runtimeService.startProcessInstanceByKey(…)
则runtimeService.startProcessInstanceById(…)
抛出异常。 -
如果流程定义有单个消息启动事件,则
runtimeService.startProcessInstanceByKey(…)
使用runtimeService.startProcessInstanceById(…)
消息启动事件启动一个新流程实例。 -
如果进程是从调用活动启动的,则仅在以下情况下才支持消息启动事件
-
除了消息启动事件之外,流程还有一个无启动事件
-
该流程只有一个消息启动事件,没有其他启动事件。
-
图形符号
消息开始事件被可视化为带有消息事件符号的圆圈。该符号未填充,以可视化捕获(接收)行为。
XML 表示
消息启动事件的 XML 表示是带有 messageEventDefinition 子元素的正常启动事件声明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 <definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples"
xmlns:tns="Examples">
<message id="newInvoice" name="newInvoiceMessage" />
<process id="invoiceProcess">
<startEvent id="messageStart" >
<messageEventDefinition messageRef="tns:newInvoice" />
</startEvent>
...
</process>
</definitions>
8.2.10。信号启动事件
描述
信号启动事件可用于使用命名信号启动流程实例。可以使用中间信号抛出事件或通过 API( runtimeService.signalEventReceivedXXX方法)从流程实例中触发信号。在这两种情况下,将启动具有同名信号启动事件的所有流程定义。
请注意,在这两种情况下,也可以在流程实例的同步和异步启动之间进行选择。
signalName
必须在 API 中传递的name
是在.signal
signalRef
signalEventDefinition
图形符号
信号开始事件被可视化为带有信号事件符号的圆圈。该符号未填充,以可视化捕获(接收)行为。
XML 表示
信号开始事件的 XML 表示是带有 signalEventDefinition 子元素的正常开始事件声明:
1
2
3
4
5
6
7
8
9
10
11 <signal id="theSignal" name="The Signal" />
<process id="processWithSignalStart1">
<startEvent id="theStart">
<signalEventDefinition id="theSignalEventDefinition" signalRef="theSignal" />
</startEvent>
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="theTask" />
<userTask id="theTask" name="Task in process A" />
<sequenceFlow id="flow2" sourceRef="theTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
8.2.11。错误开始事件
图形符号
错误开始事件显示为带有错误事件符号的圆圈。该符号未填充,以可视化捕获(接收)行为。
XML 表示
错误启动事件的 XML 表示是带有 errorEventDefinition 子元素的正常启动事件声明:
1
2
3 <startEvent id="messageStart" >
<errorEventDefinition errorRef="someError" />
</startEvent>
8.2.12。结束事件
结束事件表示(子)流程的(路径)结束。一个结束事件总是抛出。这意味着当流程执行到达结束事件时,会抛出一个结果。结果的类型由事件的内部黑色图标描述。在 XML 表示中,类型由子元素的声明给出。
8.2.13。无结束事件
描述
none end 事件意味着到达事件时抛出的结果是未指定的。因此,除了结束当前的执行路径之外,引擎不会做任何额外的事情。
图形符号
无结束事件被可视化为一个带有粗边框且没有内部图标(无结果类型)的圆圈。
XML 表示
无结束事件的 XML 表示是正常的结束事件声明,没有任何子元素(其他结束事件类型都有一个声明类型的子元素)。
1 <endEvent id="end" name="my end event" />
8.2.14。错误结束事件
描述
当流程执行到达错误结束事件时,当前执行路径结束并抛出错误。此错误可以被匹配的中间边界错误事件捕获。如果没有找到匹配的边界错误事件,将抛出异常。
图形符号
错误结束事件被可视化为典型的结束事件(带有粗边框的圆圈),内部带有错误图标。错误图标是全黑的,表示抛出语义。
XML 表示
并且错误结束事件表示为一个结束事件,带有一个errorEventDefinition子元素。
1
2
3 <endEvent id="myErrorEndEvent">
<errorEventDefinition errorRef="myError" />
</endEvent>
errorRef属性可以引用在进程之外定义的错误元素:
1
2
3
4 <error id="myError" errorCode="123" />
...
<process id="myProcess">
...
错误的errorCode将用于查找匹配的捕获边界错误事件。如果errorRef不匹配任何定义的错误,则errorRef用作errorCode的快捷方式。这是一个 Activiti 特定的快捷方式。更具体地说,以下代码片段在功能上是等效的。
1
2
3
4
5
6
7
8 <error id="myError" errorCode="error123" />
...
<process id="myProcess">
...
<endEvent id="myErrorEndEvent">
<errorEventDefinition errorRef="myError" />
</endEvent>
...
相当于
1
2
3 <endEvent id="myErrorEndEvent">
<errorEventDefinition errorRef="error123" />
</endEvent>
请注意,errorRef必须符合 BPMN 2.0 模式,并且必须是有效的 QName。
8.2.15。终止结束事件
描述
当到达终止结束事件时,当前流程实例或子流程将被终止。从概念上讲,当执行到达终止结束事件时,将确定并结束第一个范围(流程或子流程)。请注意,在 BPMN 2.0 中,子流程可以是嵌入式子流程、调用活动、事件子流程或事务子流程。这个规则一般适用:例如当有一个多实例调用活动或嵌入子流程时,只有那个实例会结束,其他实例和流程实例不受影响。
有一个可选属性terminateAll可以添加。当true时,无论终止结束事件在流程定义中的位置如何,也无论是否在子流程中(甚至是嵌套的),(根)流程实例都将被终止。
图形符号
一个取消结束事件,可视化为典型的结束事件(带有粗轮廓的圆圈),里面有一个完整的黑色圆圈。
XML 表示
终止结束事件表示为一个结束事件,带有一个terminateEventDefinition子元素。
请注意,terminateAll属性是可选的(默认为false)。
1
2
3 <endEvent id="myEndEvent >
<terminateEventDefinition activiti:terminateAll="true"></terminateEventDefinition>
</endEvent>
8.2.16。取消结束事件
描述
取消结束事件只能与 bpmn 事务子流程结合使用。当达到取消结束事件时,将引发取消事件,该事件必须被取消边界事件捕获。取消边界事件然后取消事务并触发补偿。
图形符号
取消结束事件可视化为典型的结束事件(带有粗轮廓的圆圈),内部带有取消图标。取消图标是全黑的,表示投掷语义。
XML 表示
取消结束事件表示为结束事件,带有一个cancelEventDefinition子元素。
1
2
3 <endEvent id="myCancelEndEvent">
<cancelEventDefinition />
</endEvent>
8.2.17。边界事件
边界事件是捕获附加到活动的事件(边界事件永远不会被抛出)。这意味着在活动运行时,事件正在侦听某种类型的触发器。当事件被捕获时,活动被中断,并遵循事件出的顺序流。
所有边界事件都以相同的方式定义:
1
2
3 <boundaryEvent id="myBoundaryEvent" attachedToRef="theActivity">
<XXXEventDefinition/>
</boundaryEvent>
边界事件定义为
-
唯一标识符(进程范围)
-
对通过attachToRef属性附加事件的活动的引用。请注意,边界事件与它们所附加的活动在同一级别上定义(即活动中不包含边界事件)。
-
定义边界事件类型的XXXEventDefinition形式的 XML 子元素(例如TimerEventDefinition、ErrorEventDefinition等)。有关详细信息,请参阅特定的边界事件类型。
8.2.18。定时器边界事件
描述
定时器边界事件充当秒表和闹钟。当执行到达边界事件所附加到的活动时,将启动一个计时器。当定时器触发时(例如在指定的时间间隔之后),活动被中断边界事件被跟随。
图形符号
计时器边界事件可视化为典型的边界事件(即边界上的圆圈),内部带有计时器图标。
XML 表示
定时器边界事件被定义为常规边界事件。在这种情况下,特定类型的子元素是timerEventDefinition元素。
1
2
3
4
5 <boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="firstLineSupport">
<timerEventDefinition>
<timeDuration>PT4H</timeDuration>
</timerEventDefinition>
</boundaryEvent>
有关定时器配置的详细信息,请参阅定时器事件定义。
在图形表示中,圆的线是虚线的,如您在上面的示例中所见:
一个典型的用例是额外发送一封升级电子邮件,但不会中断正常的流程。
自 BPMN 2.0 以来,中断和非中断定时器事件之间存在差异。中断是默认设置。不中断导致原来的活动没有被打断,但活动一直停留在那里。相反,会创建一个额外的执行并通过事件的传出转换发送。在 XML 表示中,cancelActivity属性设置为 false:
1 <boundaryEvent id="escalationTimer" cancelActivity="false" attachedToRef="firstLineSupport"/>
注意:边界计时器事件仅在启用作业或异步执行器时触发(即需要在 中设置jobExecutorActivate或asyncExecutorActivate ,因为作业和异步执行器默认禁用)。true
activiti.cfg.xml
8.2.19。错误边界事件
描述
活动边界上的中间捕获错误,或简称边界错误事件,捕获在定义它的活动范围内引发的错误。
定义边界错误事件对嵌入式子流程或调用活动最有意义,因为子流程会为子流程内的所有活动创建范围。错误由错误结束事件引发。这样的错误将向上传播其父作用域,直到找到定义了与错误事件定义匹配的边界错误事件的作用域。
当捕获到错误事件时,定义边界事件的活动被销毁,同时也销毁其中的所有当前执行(例如并发活动、嵌套子流程等)。流程执行继续遵循边界事件的传出序列流。
图形符号
边界错误事件可视化为边界上的典型中间事件(内部带有较小圆圈的圆圈),内部带有错误图标。错误图标为白色,表示捕获语义。
xml 表示
边界错误事件被定义为典型的边界事件:
1
2
3 <boundaryEvent id="catchError" attachedToRef="mySubProcess">
<errorEventDefinition errorRef="myError"/>
</boundaryEvent>
与错误结束事件一样,errorRef引用了在流程元素之外定义的错误:
1
2
3
4 <error id="myError" errorCode="123" />
...
<process id="myProcess">
...
errorCode用于匹配捕获的错误:
-
如果省略errorRef,边界错误事件将捕获任何错误事件,而不管错误的 errorCode 是什么。
-
如果提供了errorRef并且它引用了现有错误,则边界事件将仅捕获具有相同错误代码的错误。
-
如果提供了errorRef,但BPMN 2.0 文件中没有定义错误,则将errorRef 用作 errorCode(与错误结束事件类似)。
例子
以下示例过程显示了如何使用错误结束事件。当通过声明未提供足够信息来完成“查看盈利能力”用户任务时,将引发错误。当在子流程的边界捕获此错误时,“Review sales Lead”子流程中的所有活动活动都将被销毁(即使“Review customer rating”尚未完成),并创建“Provide Additional details”用户任务.
此过程在演示设置中作为示例提供。流程 XML 和单元测试可以在org.activiti.examples.bpmn.event.error包中找到。
8.2.20。信号边界事件
描述
活动边界上附加的中间捕获 信号,或简称边界信号事件,捕获与引用的信号定义具有相同信号名称的信号。
注意:与边界错误事件等其他事件相反,边界信号事件不仅捕获从它所附加的范围抛出的信号事件。相反,信号事件具有全局范围(广播语义),这意味着可以从任何地方抛出信号,甚至可以从不同的流程实例中抛出。
注意:与错误事件等其他事件相反,如果信号被捕获,则不会消耗它。如果您有两个活动的信号边界事件捕获相同的信号事件,则这两个边界事件都会被触发,即使它们是不同流程实例的一部分。
图形符号
边界信号事件被可视化为边界上的典型中间事件(内部带有较小圆圈的圆圈),内部带有信号图标。信号图标为白色(未填充),表示捕获语义。
XML 表示
边界信号事件被定义为典型的边界事件:
1
2
3 <boundaryEvent id="boundary" attachedToRef="task" cancelActivity="true">
<signalEventDefinition signalRef="alertSignal"/>
</boundaryEvent>
例子
请参阅信号事件定义部分。
8.2.21。消息边界事件
描述
活动边界上附加的中间捕获 消息,或简称边界消息事件,捕获与引用的消息定义具有相同消息名称的消息。
图形符号
边界消息事件被可视化为边界上的典型中间事件(内部带有较小圆圈的圆圈),内部带有消息图标。消息图标为白色(未填充),表示捕获语义。
请注意,边界消息事件可以是中断(右侧)和非中断(左侧)。
XML 表示
边界消息事件被定义为典型的边界事件:
1
2
3 <boundaryEvent id="boundary" attachedToRef="task" cancelActivity="true">
<messageEventDefinition messageRef="newCustomerMessage"/>
</boundaryEvent>
例子
请参阅消息事件定义部分。
8.2.22。取消边界事件
描述
事务子流程边界上附加的中间捕获取消,或简称边界取消事件,在事务被取消时触发。当取消边界事件被触发时,它首先中断当前作用域中所有活动的执行。接下来,它开始对事务范围内所有活动的补偿边界事件进行补偿。补偿是同步执行的,即边界事件在离开事务之前等待补偿完成。当补偿完成时,事务子流程将使用超出取消边界事件的序列流。
注意:事务子流程只允许一个取消边界事件。
注意:如果事务子流程包含嵌套子流程,则仅对已成功完成的子流程触发补偿。
注意:如果在具有多实例特征的事务子流程上放置取消边界事件,如果一个实例触发取消,则边界事件取消所有实例。
图形符号
取消边界事件可视化为边界上的典型中间事件(内部带有较小圆圈的圆圈),内部带有取消图标。取消图标为白色(未填充),表示捕获语义。
XML 表示
取消边界事件被定义为典型的边界事件:
1
2
3 <boundaryEvent id="boundary" attachedToRef="transaction" >
<cancelEventDefinition />
</boundaryEvent>
由于取消边界事件总是中断,因此cancelActivity
不需要该属性。
8.2.23。补偿边界事件
描述
在活动边界上附加的中间捕获补偿或简称补偿边界事件,可用于将补偿处理程序附加到活动。
补偿边界事件必须使用定向关联引用单个补偿处理程序。
补偿边界事件与其他边界事件具有不同的激活策略。其他边界事件(例如信号边界事件)在它们所附加的活动启动时被激活。当活动离开时,它们被停用并取消相应的事件订阅。补偿边界事件不同。补偿边界事件在它附加到的活动成功完成时被激活。至此,对应的补偿事件订阅就创建好了。当触发补偿事件或相应的流程实例结束时,订阅将被删除。由此可知:
-
当触发补偿时,与补偿边界事件关联的补偿处理程序被调用的次数与它所附加的活动成功完成的次数相同。
-
如果补偿边界事件附加到具有多个实例特征的活动,则会为每个实例创建补偿事件订阅。
-
如果补偿边界事件附加到包含在循环内的活动,则每次执行活动时都会创建一个补偿事件订阅。
-
如果流程实例结束,则取消对补偿事件的订阅。
注意:嵌入式子流程不支持补偿边界事件。
图形符号
补偿边界事件被可视化为边界上的典型中间事件(内部带有较小圆圈的圆圈),内部带有补偿图标。补偿图标为白色(未填充),表示捕获语义。除了补偿边界事件之外,下图还显示了使用单向关联与边界事件关联的补偿处理程序:
XML 表示
补偿边界事件被定义为典型的边界事件:
1
2
3
4
5
6
7 <boundaryEvent id="compensateBookHotelEvt" attachedToRef="bookHotel" >
<compensateEventDefinition />
</boundaryEvent>
<association associationDirection="One" id="a1" sourceRef="compensateBookHotelEvt" targetRef="undoBookHotel" />
<serviceTask id="undoBookHotel" isForCompensation="true" activiti:class="..." />
由于活动成功完成后才激活补偿边界事件,因此cancelActivity
不支持该属性。
8.2.24。中间捕捉事件
所有中间捕获事件都以相同的方式定义:
1
2
3 <intermediateCatchEvent id="myIntermediateCatchEvent" >
<XXXEventDefinition/>
</intermediateCatchEvent>
中间捕获事件定义为
-
唯一标识符(进程范围)
-
定义中间捕获事件类型的XXXEventDefinition形式的 XML 子元素(例如TimerEventDefinition等)。有关更多详细信息,请参阅特定的捕获事件类型。
8.2.25。定时器中间捕捉事件
描述
计时器中间事件充当秒表。当执行到达捕获事件活动时,将启动计时器。当计时器触发时(例如,在指定的时间间隔之后),将遵循离开计时器中间事件的序列流。
图形符号
计时器中间事件可视化为中间捕获事件,内部带有计时器图标。
8.2.26。信号中间捕捉事件
描述
中间捕获 信号事件捕获与引用的信号定义具有相同信号名称的信号。
注意:与错误事件等其他事件相反,如果信号被捕获,则不会消耗它。如果您有两个活动的信号边界事件捕获相同的信号事件,则这两个边界事件都会被触发,即使它们是不同流程实例的一部分。
图形符号
中间信号捕获事件被可视化为典型的中间事件(内部带有较小圆圈的圆圈),内部带有信号图标。信号图标为白色(未填充),表示捕获语义。
XML 表示
信号中间事件被定义为中间捕获事件。在这种情况下,特定类型的子元素是signalEventDefinition元素。
1
2
3 <intermediateCatchEvent id="signal">
<signalEventDefinition signalRef="newCustomerSignal" />
</intermediateCatchEvent>
例子
请参阅信号事件定义部分。
8.2.27。消息中间捕获事件
描述
中间捕获 消息事件捕获具有指定名称的消息。
图形符号
中间捕获消息事件被可视化为典型的中间事件(内部带有较小圆圈的圆圈),内部带有消息图标。消息图标为白色(未填充),表示捕获语义。
XML 表示
消息中间事件被定义为中间捕获事件。在这种情况下,特定类型子元素是messageEventDefinition元素。
1
2
3 <intermediateCatchEvent id="message">
<messageEventDefinition signalRef="newCustomerMessage" />
</intermediateCatchEvent>
例子
请参阅消息事件定义部分。
8.2.28。中级投掷事件
所有中间抛出事件都以相同的方式定义:
1
2
3 <intermediateThrowEvent id="myIntermediateThrowEvent" >
<XXXEventDefinition/>
</intermediateThrowEvent>
中间抛出事件定义为
-
唯一标识符(进程范围)
-
定义中间抛出事件类型的XXXEventDefinition形式的 XML 子元素(例如signalEventDefinition等)。有关更多详细信息,请参阅特定的投掷事件类型。
8.2.29。中级投掷无事件
下面的流程图显示了一个中间无事件的简单示例,它通常用于指示流程中达到的某些状态。
这可能是监控某些 KPI 的一个很好的钩子,基本上是通过添加一个执行侦听器。
1
2
3
4
5 <intermediateThrowEvent id="noneEvent">
<extensionElements>
<activiti:executionListener class="org.activiti.engine.test.bpmn.event.IntermediateNoneEventTest$MyExecutionListener" event="start" />
</extensionElements>
</intermediateThrowEvent>
在那里,您可以添加一些自己的代码,以将一些事件发送到您的 BAM 工具或 DWH。在这种情况下,引擎本身不会做任何事情,它只是通过。
8.2.30。信号中间投掷事件
描述
中间抛出 信号事件为定义的信号抛出信号事件。
在 Activiti 中,信号被广播到所有活动的处理程序(即所有捕获信号事件)。信号可以同步或异步发布。
-
在默认配置中,信号是同步传递的。这意味着抛出流程实例会一直等待,直到信号被传递给所有正在捕获的流程实例。捕获的流程实例也会在与抛出流程实例相同的事务中得到通知,这意味着如果通知的实例之一产生技术错误(抛出异常),则所有涉及的实例都会失败。
-
信号也可以异步传递。在这种情况下,确定在达到抛出信号事件时哪些处理程序处于活动状态。对于每个活动的处理程序,一个异步通知消息(Job)由 JobExecutor 存储和传递。
图形符号
中间信号抛出事件可视化为典型的中间事件(内部带有较小圆圈的圆圈),内部带有信号图标。信号图标为黑色(实心),表示投掷语义。
XML 表示
信号中间事件被定义为中间抛出事件。在这种情况下,特定类型的子元素是signalEventDefinition元素。
1
2
3 <intermediateThrowEvent id="signal">
<signalEventDefinition signalRef="newCustomerSignal" />
</intermediateThrowEvent>
异步信号事件如下所示:
1
2
3 <intermediateThrowEvent id="signal">
<signalEventDefinition signalRef="newCustomerSignal" activiti:async="true" />
</intermediateThrowEvent>
例子
请参阅信号事件定义部分。
8.2.31。补偿中间投掷事件
描述
可以使用中间投掷补偿事件来触发补偿。
触发补偿:可以为指定的活动或承载补偿事件的范围触发补偿。通过执行与活动相关的补偿处理程序来执行补偿。
-
当为某个活动抛出补偿时,相关的补偿处理程序将执行与该活动成功完成的次数相同的次数。
-
如果对当前范围进行补偿,则补偿当前范围内的所有活动,包括并发分支上的活动。
-
补偿是分层触发的:如果要补偿的活动是子流程,则针对子流程中包含的所有活动触发补偿。如果子流程有嵌套活动,则递归地抛出补偿。但是,补偿不会传播到流程的“上层”:如果补偿在子流程内触发,则不会传播到子流程范围之外的活动。BPMN 规范规定,补偿是针对“同一级别的子流程”的活动触发的。
-
在 Activiti 中,补偿以相反的执行顺序执行。这意味着最后完成的活动首先得到补偿,依此类推。
-
中间抛出补偿事件可用于补偿成功竞争的事务子流程。
注意:如果在包含子流程的范围内抛出补偿,并且子流程包含具有补偿处理程序的活动,则只有在抛出补偿时成功完成时,补偿才会传播到子流程。如果嵌套在子流程中的某些活动已经完成并附加了补偿处理程序,则如果包含这些活动的子流程尚未完成,则补偿处理程序不会执行。考虑以下示例:
在这个流程中,我们有两个并发执行,一个执行嵌入式子流程,一个执行“信用卡充值”活动。让我们假设两个执行都已启动,并且第一个并发执行正在等待用户完成“查看预订”任务。第二次执行执行“充值信用卡”活动并抛出错误,导致“取消预订”事件触发补偿。此时并行子流程尚未完成,这意味着补偿事件没有传播到子流程,因此“取消酒店预订”补偿处理程序没有执行。如果用户任务(以及嵌入式子流程)在“取消预订”执行之前完成,
流程变量:当补偿嵌入式子流程时,用于执行补偿处理程序的执行可以访问子流程的本地流程变量,这些变量处于子流程完成执行时所处的状态。为了实现这一点,需要对与作用域执行(为执行子流程而创建的执行)关联的流程变量进行快照。形成这一点,有几个含义如下:
-
补偿处理程序无权访问添加到子流程范围内创建的并发执行的变量。
-
与层次结构中较高层执行相关的流程变量,(例如,与流程实例执行相关的流程变量不包含在快照中:补偿处理程序可以访问这些流程变量,它们处于抛出补偿时的状态。
-
变量快照仅用于嵌入式子流程,而不用于其他活动。
当前限制:
-
waitForCompletion="false"
目前不支持。当使用中间抛补偿事件触发补偿时,只有在补偿成功完成后才会留下该事件。 -
补偿本身当前由并发执行执行。并发执行以补偿活动完成的相反顺序开始。未来版本的活动可能包括按顺序执行补偿的选项。
-
补偿不会传播到调用活动产生的子流程实例。
图形符号
中间补偿抛出事件被可视化为典型的中间事件(内部带有较小圆圈的圆圈),内部带有补偿图标。补偿图标为黑色(实心),表示投掷语义。
xml 表示
补偿中间事件被定义为中间投掷事件。在这种情况下,特定类型子元素是一个补偿事件定义元素。
1
2
3 <intermediateThrowEvent id="throwCompensation">
<compensateEventDefinition />
</intermediateThrowEvent>
此外,可选参数activityRef
可用于触发特定范围/活动的补偿:
1
2
3 <intermediateThrowEvent id="throwCompensation">
<compensateEventDefinition activityRef="bookHotel" />
</intermediateThrowEvent>
8.3. 序列流
8.3.1. 描述
顺序流是流程的两个元素之间的连接器。在流程执行期间访问一个元素后,将遵循所有传出的序列流。这意味着 BPMN 2.0 的默认性质是并行的:两个传出序列流将创建两个独立的并行执行路径。
8.3.2. 图形符号
序列流可视化为从源元素指向目标元素的箭头。箭头始终指向目标。
8.3.3. XML 表示
序列流需要有一个流程唯一的id以及对现有源和目标元素的引用。
1 <sequenceFlow id="flow1" sourceRef="theStart" targetRef="theTask" />
8.3.4. 条件序列流
描述
一个顺序流可以定义一个条件。当 BPMN 2.0 活动离开时,默认行为是评估传出序列流上的条件。当条件评估为true时,将选择该传出序列流。当以这种方式选择多个序列流时,将生成多个执行,并以并行方式继续该过程。
注意:上述内容适用于 BPMN 2.0 活动(和事件),但不适用于网关。网关将以特定方式处理带有条件的序列流,具体取决于网关类型。
图形符号
条件序列流可视化为规则序列流,开头有一个小菱形。条件表达式显示在序列流旁边。
XML 表示
条件序列流在 XML 中表示为常规序列流,包含一个conditionExpression子元素。请注意,目前仅支持tFormalExpressions,省略xsi:type=""定义将默认为仅支持的表达式类型。
1
2
3
4
5 <sequenceFlow id="flow" sourceRef="theStart" targetRef="theTask">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${order.price > 100 && order.price < 250}]]>
</conditionExpression>
</sequenceFlow>
目前 conditionalExpressions只能与 UEL 一起使用,有关这些的详细信息可以在Expressions部分找到。使用的表达式应解析为布尔值,否则在评估条件时会引发异常。
-
下面的示例通过 getter 以典型的 JavaBean 样式引用流程变量的数据。
1
2
3 <conditionExpression xsi:type="tFormalExpression">
<![CDATA[${order.price > 100 && order.price < 250}]]>
</conditionExpression>
-
此示例调用解析为布尔值的方法。
1
2
3 <conditionExpression xsi:type="tFormalExpression">
<![CDATA[${order.isStandardOrder()}]]>
</conditionExpression>
Activiti 发行版包含以下使用值和方法表达式的示例流程(参见org.activiti.examples.bpmn.expression):
8.3.5。默认顺序流
描述
所有 BPMN 2.0 任务和网关都可以有一个默认的序列流。只有当且仅当没有其他序列流可以被选择时,才会选择该序列流作为该活动的传出序列流。默认序列流上的条件总是被忽略。
图形符号
默认序列流可视化为常规序列流,开头带有斜线标记。
XML 表示
某个活动的默认顺序流由该活动的默认属性定义。例如,以下 XML 片段显示了具有默认序列流flow 2的独占网关。只有当条件A和条件B 都评估为假时,才会选择它作为网关的传出序列流。
1
2
3
4
5
6
7
8 <exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" default="flow2" />
<sequenceFlow id="flow1" sourceRef="exclusiveGw" targetRef="task1">
<conditionExpression xsi:type="tFormalExpression">${conditionA}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="task2"/>
<sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="task3">
<conditionExpression xsi:type="tFormalExpression">${conditionB}</conditionExpression>
</sequenceFlow>
这对应于以下图形表示:
8.4. 网关
网关用于控制执行流程(或如 BPMN 2.0 所述,执行令牌)。网关能够消费或生成令牌。
网关以图形方式显示为菱形,内部带有图标。图标显示网关的类型。
8.4.1. 专属网关
描述
排他网关(也称为XOR 网关或更专业的基于数据的排他网关)用于对流程中的决策进行建模。当执行到达这个网关时,所有传出的序列流都会按照它们定义的顺序进行评估。选择条件评估为真(或没有条件集,概念上在序列流上定义为“真”)的序列流以继续该过程。
请注意,传出序列流的语义与 BPMN 2.0 中的一般情况不同。虽然通常选择条件评估为真的所有序列流以并行方式继续,但在使用独占网关时仅选择一个序列流。如果多个序列流的条件评估为真,则选择 XML 中定义的第一个(并且只有那个!)以继续该过程。如果无法选择顺序流,则会抛出异常。
图形符号
独占网关被可视化为内部带有X图标的典型网关(即菱形),指的是XOR语义。请注意,内部没有图标的网关默认为独占网关。BPMN 2.0 规范不允许在同一流程定义中混合带有和不带有 X 的钻石。
XML 表示
专用网关的 XML 表示是直截了当的:一行定义网关和在传出序列流上定义的条件表达式。请参阅有关条件序列流的部分,以了解哪些选项可用于此类表达式。
以下面的模型为例:
在 XML 中表示如下:
1
2
3
4
5
6
7
8
9
10
11
12
13 <exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" />
<sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="theTask1">
<conditionExpression xsi:type="tFormalExpression">${input == 1}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="theTask2">
<conditionExpression xsi:type="tFormalExpression">${input == 2}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" sourceRef="exclusiveGw" targetRef="theTask3">
<conditionExpression xsi:type="tFormalExpression">${input == 3}</conditionExpression>
</sequenceFlow>
8.4.2. 并行网关
描述
网关也可用于对流程中的并发进行建模。在流程模型中引入并发的最直接的网关是Parallel Gateway,它允许分叉到多个执行路径或加入多个传入执行路径。
并行网关的功能基于传入和传出序列流:
-
fork:所有传出的序列流并行执行,为每个序列流创建一个并发执行。
-
加入:到达并行网关的所有并发执行在网关中等待,直到每个传入序列流的执行到达。然后该过程继续通过加入网关。
请注意,如果同一个并行网关有多个传入和传出序列流,则并行网关可以同时具有 fork 和 join 行为。在这种情况下,网关将首先加入所有传入的序列流,然后拆分为多个并发执行路径。
与其他网关类型的一个重要区别是并行网关不评估条件。如果在与并行网关连接的序列流上定义了条件,它们将被简单地忽略。
图形符号
并行网关可视化为网关(菱形),内部带有加号,指的是AND语义。
XML 表示
定义并行网关需要一行 XML:
1 <parallelGateway id="myParallelGateway" />
实际行为(fork、join 或两者)由连接到并行网关的序列流定义。
例如,上面的模型归结为以下 XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 <startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="fork" />
<parallelGateway id="fork" />
<sequenceFlow sourceRef="fork" targetRef="receivePayment" />
<sequenceFlow sourceRef="fork" targetRef="shipOrder" />
<userTask id="receivePayment" name="Receive Payment" />
<sequenceFlow sourceRef="receivePayment" targetRef="join" />
<userTask id="shipOrder" name="Ship Order" />
<sequenceFlow sourceRef="shipOrder" targetRef="join" />
<parallelGateway id="join" />
<sequenceFlow sourceRef="join" targetRef="archiveOrder" />
<userTask id="archiveOrder" name="Archive Order" />
<sequenceFlow sourceRef="archiveOrder" targetRef="theEnd" />
<endEvent id="theEnd" />
在上面的例子中,进程启动后,会创建两个任务:
1
2
3
4
5
6
7
8
9
10
11
12
13 ProcessInstance pi = runtimeService.startProcessInstanceByKey("forkJoin");
TaskQuery query = taskService.createTaskQuery()
.processInstanceId(pi.getId())
.orderByTaskName()
.asc();
List<Task> tasks = query.list();
assertEquals(2, tasks.size());
Task task1 = tasks.get(0);
assertEquals("Receive Payment", task1.getName());
Task task2 = tasks.get(1);
assertEquals("Ship Order", task2.getName());
当这两个任务完成时,第二个并行网关将加入两个执行,由于只有一个传出序列流,因此不会创建并发执行路径,只有存档订单任务将处于活动状态。
请注意,并行网关不需要平衡(即对应并行网关的传入/传出序列流的匹配数量)。并行网关将简单地等待所有传入的序列流,并为每个传出的序列流创建一个并发执行路径,不受流程模型中其他构造的影响。因此,以下过程在 BPMN 2.0 中是合法的:
8.4.3. 包容性网关
描述
包容网关可以看作是独占网关和并行网关的组合。与独占网关一样,您可以在传出序列流上定义条件,并且包含网关将评估它们。但主要区别在于包容网关可以采用多个序列流,就像并行网关一样。
包容性网关的功能基于传入和传出序列流:
-
fork:评估所有传出序列流条件,并且对于评估为真的序列流条件,并行遵循流程,为每个序列流创建一个并发执行。
-
加入:到达包含网关的所有并发执行在网关中等待,直到每个具有流程令牌的传入序列流的执行到达。这是与并行网关的一个重要区别。所以换句话说,包容网关将只等待将要执行的传入序列流。加入后,该过程继续通过加入包容性网关。
请注意,如果同一个包容性网关有多个传入和传出序列流,则一个包容性网关可以同时具有 fork 和 join 行为。在这种情况下,网关将首先加入所有具有流程令牌的传入序列流,然后拆分为多个并发执行路径,用于具有评估为真条件的传出序列流。
图形符号
包容性网关可视化为网关(菱形),内部带有圆形符号。
XML 表示
定义一个包容性网关需要一行 XML:
1 <inclusiveGateway id="myInclusiveGateway" />
实际行为(fork、join 或两者)由连接到包容性网关的序列流定义。
例如,上面的模型归结为以下 XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 <startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="fork" />
<inclusiveGateway id="fork" />
<sequenceFlow sourceRef="fork" targetRef="receivePayment" >
<conditionExpression xsi:type="tFormalExpression">${paymentReceived == false}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="fork" targetRef="shipOrder" >
<conditionExpression xsi:type="tFormalExpression">${shipOrder == true}</conditionExpression>
</sequenceFlow>
<userTask id="receivePayment" name="Receive Payment" />
<sequenceFlow sourceRef="receivePayment" targetRef="join" />
<userTask id="shipOrder" name="Ship Order" />
<sequenceFlow sourceRef="shipOrder" targetRef="join" />
<inclusiveGateway id="join" />
<sequenceFlow sourceRef="join" targetRef="archiveOrder" />
<userTask id="archiveOrder" name="Archive Order" />
<sequenceFlow sourceRef="archiveOrder" targetRef="theEnd" />
<endEvent id="theEnd" />
在上面的例子中,流程启动后,流程变量paymentReceived == false和shipOrder == true会创建两个任务。如果这些流程变量中只有一个等于 true,则只会创建一个任务。如果没有条件评估为真并抛出异常。这可以通过指定默认的传出序列流来防止。在以下示例中,将创建一个任务,即发货订单任务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 HashMap<String, Object> variableMap = new HashMap<String, Object>();
variableMap.put("receivedPayment", true);
variableMap.put("shipOrder", true);
ProcessInstance pi = runtimeService.startProcessInstanceByKey("forkJoin");
TaskQuery query = taskService.createTaskQuery()
.processInstanceId(pi.getId())
.orderByTaskName()
.asc();
List<Task> tasks = query.list();
assertEquals(1, tasks.size());
Task task = tasks.get(0);
assertEquals("Ship Order", task.getName());
此任务完成后,第二个包容网关将加入两个执行,由于只有一个传出序列流,因此不会创建并发执行路径,只有存档订单任务将处于活动状态。
请注意,包容网关不需要平衡(即,对应的包容网关的传入/传出序列流的匹配数量)。包容性网关将简单地等待所有传入的序列流,并为每个传出的序列流创建一个并发执行路径,不受流程模型中其他构造的影响。
8.4.4. 基于事件的网关
描述
基于事件的网关允许根据事件做出决定。网关的每个传出序列流都需要连接到一个中间捕获事件。当流程执行到达基于事件的网关时,网关就像等待状态一样:执行被暂停。此外,对于每个传出序列流,都会创建一个事件订阅。
请注意,从基于事件的网关流出的序列流与普通的序列流不同。这些序列流从未真正“执行”过。相反,它们允许流程引擎确定到达基于事件的网关的执行需要订阅哪些事件。以下限制适用:
-
基于事件的网关必须有两个或多个传出序列流。
-
基于事件的网关只能连接到类型元素
intermediateCatchEvent
。(Activiti 不支持在基于事件的网关之后接收任务。) -
连接
intermediateCatchEvent
到基于事件的网关必须具有单个传入序列流。
图形符号
基于事件的网关与其他带有特殊图标的 BPMN 网关一样,被可视化为菱形。
XML 表示
用于定义基于事件的网关的 XML 元素是eventBasedGateway
.
例子)
以下流程是具有基于事件的网关的流程示例。当执行到达基于事件的网关时,流程执行被暂停。此外,流程实例订阅警报信号事件并创建一个在 10 分钟后触发的计时器。这有效地导致流程引擎等待十分钟等待信号事件。如果信号在 10 分钟内出现,则取消计时器并在信号后继续执行。如果未触发信号,则在计时器后继续执行,并取消信号订阅。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 <definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<signal id="alertSignal" name="alert" />
<process id="catchSignal">
<startEvent id="start" />
<sequenceFlow sourceRef="start" targetRef="gw1" />
<eventBasedGateway id="gw1" />
<sequenceFlow sourceRef="gw1" targetRef="signalEvent" />
<sequenceFlow sourceRef="gw1" targetRef="timerEvent" />
<intermediateCatchEvent id="signalEvent" name="Alert">
<signalEventDefinition signalRef="alertSignal" />
</intermediateCatchEvent>
<intermediateCatchEvent id="timerEvent" name="Alert">
<timerEventDefinition>
<timeDuration>PT10M</timeDuration>
</timerEventDefinition>
</intermediateCatchEvent>
<sequenceFlow sourceRef="timerEvent" targetRef="exGw1" />
<sequenceFlow sourceRef="signalEvent" targetRef="task" />
<userTask id="task" name="Handle alert"/>
<exclusiveGateway id="exGw1" />
<sequenceFlow sourceRef="task" targetRef="exGw1" />
<sequenceFlow sourceRef="exGw1" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>
8.5。任务
8.5.1。用户任务
描述
用户任务用于对需要由人类参与者完成的工作进行建模。当流程执行到达此类用户任务时,将在分配给该任务的用户或组的任务列表中创建一个新任务。
图形符号
用户任务可视化为典型任务(圆角矩形),左上角有一个小用户图标。
XML 表示
用户任务在 XML 中定义如下。id属性是必需的,name属性是可选的。
1 <userTask id="theTask" name="Important task" />
用户任务也可以有描述。事实上,任何 BPMN 2.0 元素都可以有描述。通过添加文档元素来定义描述。
1
2
3
4 <userTask id="theTask" name="Schedule meeting" >
<documentation>
Schedule an engineering meeting for next week with the new hire.
</documentation>
可以以标准 Java 方式从任务中检索描述文本:
1 task.getDescription()
截止日期
每个任务都有一个字段,指示该任务的截止日期。查询 API 可用于查询在某个日期、之前或之后到期的任务。
有一个活动扩展允许您在任务定义中指定一个表达式,以在创建任务时设置任务的初始截止日期。该表达式应始终解析为java.util.Date
, java.util.String (ISO8601 formatted)
, ISO8601 持续时间(例如 PT50M)或null
. 例如,您可以使用在流程中之前的表单中输入的日期或在之前的服务任务中计算的日期。如果使用持续时间,则根据当前时间计算到期日期,并按给定时间段递增。例如,当“PT30M”用作到期日期时,任务将在三十分钟后到期。
1 <userTask id="theTask" name="Important task" activiti:dueDate="${dateVariable}"/>
也可以使用传递的TaskService
或 in来更改任务的截止日期。TaskListener
DelegateTask
用户分配
用户任务可以直接分配给用户。这是通过定义humanPerformer子元素来完成的。这样的humanPerformer定义需要一个实际定义用户的resourceAssignmentExpression。目前,仅支持正式表达式。
1
2
3
4
5
6
7
8
9
10
11 <process >
...
<userTask id='theTask' name='important task' >
<humanPerformer>
<resourceAssignmentExpression>
<formalExpression>kermit</formalExpression>
</resourceAssignmentExpression>
</humanPerformer>
</userTask>
只能将一名用户指定为该任务的人工执行者。在 Activiti 术语中,这个用户被称为受让人。具有受让人的任务在其他人的任务列表中不可见,而是可以在受让人的所谓个人任务列表中找到。
直接分配给用户的任务可以通过TaskService检索如下:
1 List<Task> tasks = taskService.createTaskQuery().taskAssignee("kermit").list();
任务也可以放在所谓的人的候选任务列表中。在这种情况下,必须使用potentialOwner构造。用法类似于humanPerformer构造。请注意,需要为正式表达式中的每个元素定义以指定它是用户还是组(引擎无法猜测)。
1
2
3
4
5
6
7
8
9
10
11 <process >
...
<userTask id='theTask' name='important task' >
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>user(kermit), group(management)</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
使用潜在所有者构造定义的任务,可以按如下方式检索(或与受让人的任务类似的TaskQuery用法):
1 List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit");
这将检索 kermit 是候选 user的所有任务,即正式表达式包含user(kermit)。这还将检索分配给 kermit 所属组的所有任务(例如group(management),如果 kermit 是该组的成员并且使用了 Activiti 身份组件)。用户组在运行时解析,可以通过IdentityService进行管理。
如果没有具体说明给定的文本字符串是用户还是组,则引擎默认为组。因此,以下内容与声明 group(accountancy)时相同。
1 <formalExpression>accountancy</formalExpression>
用于任务分配的 Activiti 扩展
很明显,对于分配不复杂的用例,用户和组分配非常麻烦。为了避免这些复杂性,可以对用户任务进行自定义扩展。
-
受让人属性:此自定义扩展允许直接将用户任务分配给给定用户。
1 <userTask id="theTask" name="my task" activiti:assignee="kermit" />
这与使用上面定义的humanPerformer构造完全相同。
-
CandidateUsers 属性:此自定义扩展允许使用户成为任务的候选人。
1 <userTask id="theTask" name="my task" activiti:candidateUsers="kermit, gonzo" />
这与使用上述定义的potentialOwner构造完全相同。请注意,不需要像潜在所有者构造那样使用user(kermit)声明,因为该属性只能用于用户。
-
CandidateGroups 属性:此自定义扩展允许使组成为任务的候选者。
1 <userTask id="theTask" name="my task" activiti:candidateGroups="management, accountancy" />
这与使用上述定义的potentialOwner构造完全相同。请注意,不需要像潜在所有者构造那样使用组(管理)声明,因为该属性只能用于组。
-
CandidateUsers和CandidateGroups都可以在同一个用户任务上定义。
注意:尽管 Activiti 提供了一个身份管理组件,它通过IdentityService暴露出来,但不会检查身份组件是否知道提供的用户。这允许 Activiti 在嵌入到应用程序时与现有的身份管理解决方案集成。
自定义身份链接类型(实验性)
BPMN 标准支持单个分配的用户或humanPerformer或一组用户,这些用户形成了用户分配中定义的潜在所有者池。此外,Activiti 为 User Task 定义了扩展属性元素,可以代表任务的受理人或候选所有者。
支持的 Activiti 身份链接类型有:
1
2
3
4
5
6
7
8 public class IdentityLinkType {
/* Activiti native roles */
public static final String ASSIGNEE = "assignee";
public static final String CANDIDATE = "candidate";
public static final String OWNER = "owner";
public static final String STARTER = "starter";
public static final String PARTICIPANT = "participant";
}
BPMN 标准和 Activiti 示例授权身份是user和group。如上一节所述,Activiti 身份管理实现并非旨在用于生产用途,但应根据支持的授权方案进行扩展。
如果需要其他链接类型,可以使用以下语法将自定义资源定义为扩展元素:
1
2
3
4
5
6
7
8
9 <userTask id="theTask" name="make profit">
<extensionElements>
<activiti:customResource activiti:name="businessAdministrator">
<resourceAssignmentExpression>
<formalExpression>user(kermit), group(management)</formalExpression>
</resourceAssignmentExpression>
</activiti:customResource>
</extensionElements>
</userTask>
自定义链接表达式被添加到TaskDefinition类中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 protected Map<String, Set<Expression>> customUserIdentityLinkExpressions =
new HashMap<String, Set<Expression>>();
protected Map<String, Set<Expression>> customGroupIdentityLinkExpressions =
new HashMap<String, Set<Expression>>();
public Map<String,
Set<Expression>> getCustomUserIdentityLinkExpressions() {
return customUserIdentityLinkExpressions;
}
public void addCustomUserIdentityLinkExpression(String identityLinkType,
Set<Expression> idList)
customUserIdentityLinkExpressions.put(identityLinkType, idList);
}
public Map<String,
Set<Expression>> getCustomGroupIdentityLinkExpressions() {
return customGroupIdentityLinkExpressions;
}
public void addCustomGroupIdentityLinkExpression(String identityLinkType,
Set<Expression> idList) {
customGroupIdentityLinkExpressions.put(identityLinkType, idList);
}
在运行时由UserTaskActivityBehavior 的 handleAssignments方法填充。
最后,必须扩展IdentityLinkType类以支持自定义身份链接类型:
1
2
3
4
5
6
7
8
9 package com.yourco.engine.task;
public class IdentityLinkType
extends org.activiti.engine.task.IdentityLinkType
{
public static final String ADMINISTRATOR = "administrator";
public static final String EXCLUDED_OWNER = "excludedOwner";
}
通过任务侦听器自定义分配
如果前面的方法还不够,可以使用create 事件上的任务侦听器委托给自定义分配逻辑:
1
2
3
4
5 <userTask id="task1" name="My task" >
<extensionElements>
<activiti:taskListener event="create" class="org.activiti.MyAssignmentHandler" />
</extensionElements>
</userTask>
DelegateTask
传递给实现的TaskListener
,允许设置受让人和候选用户/组:
1
2
3
4
5
6
7
8
9
10
11
12
13 public class MyAssignmentHandler implements TaskListener {
public void notify(DelegateTask delegateTask) {
// Execute custom identity lookups here
// and then for example call following methods:
delegateTask.setAssignee("kermit");
delegateTask.addCandidateUser("fozzie");
delegateTask.addCandidateGroup("management");
...
}
}
使用 Spring 时,可以使用上面部分中描述的自定义分配属性,并使用带有监听任务创建事件的表达式的任务监听器委托给 Spring bean。在以下示例中,将通过调用Spring bean上的来设置受让人。传递的emp参数是一个流程变量>。findManagerOfEmployee
ldapService
1 <userTask id="task" name="My Task" activiti:assignee="${ldapService.findManagerForEmployee(emp)}"/>
这也适用于候选用户和组:
1 <userTask id="task" name="My Task" activiti:candidateUsers="${ldapService.findAllSales()}"/>
请注意,这仅在调用方法的返回类型为String
or时才有效Collection<String>
(对于候选用户和组):
1
2
3
4
5
6
7
8
9
10
11 public class FakeLdapService {
public String findManagerForEmployee(String employee) {
return "Kermit The Frog";
}
public List<String> findAllSales() {
return Arrays.asList("kermit", "gonzo", "fozzie");
}
}
8.5.2. 脚本任务
描述
脚本任务是一种自动活动。当一个流程执行到达脚本任务时,相应的脚本就会被执行。
图形符号
脚本任务可视化为典型的 BPMN 2.0 任务(圆角矩形),矩形左上角有一个小脚本图标。
XML 表示
脚本任务是通过指定脚本和scriptFormat来定义的。
1
2
3
4
5
6
7
8 <scriptTask id="theScriptTask" name="Execute script" scriptFormat="groovy">
<script>
sum = 0
for ( i in inputArray ) {
sum += i
}
</script>
</scriptTask>
scriptFormat属性的值必须是与JSR-223(Java 平台脚本)兼容的名称。默认情况下,每个 JDK 都包含 JavaScript,因此不需要任何额外的 jar。如果您想使用另一个(与 JSR-223 兼容的)脚本引擎,只需将相应的 jar 添加到类路径并使用适当的名称即可。例如,Activiti 单元测试经常使用 Groovy,因为其语法与 Java 非常相似。
请注意,Groovy 脚本引擎与 groovy-all jar 捆绑在一起。在 2.0 版之前,脚本引擎是常规 Groovy jar 的一部分。因此,现在必须添加以下依赖项:
1
2
3
4
5 <dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.x.x<version>
</dependency>
脚本中的变量
通过到达脚本任务的执行可访问的所有流程变量都可以在脚本中使用。在示例中,脚本变量“inputArray”实际上是一个过程变量(整数数组)。
1
2
3
4
5
6 <script>
sum = 0
for ( i in inputArray ) {
sum += i
}
</script>
也可以在脚本中设置流程变量,只需调用execution.setVariable("variableName", variableValue)。默认情况下,不会自动存储任何变量(注意:在 Activiti 5.12 之前就是这种情况!)。通过在to上设置属性,可以自动存储脚本中定义的任何变量(例如上例中的sum ) 。但是,最好不要这样做,而是使用显式的 execution.setVariable() 调用,因为在某些最新版本的 JDK 中,变量的自动存储不适用于某些脚本语言。有关更多详细信息,请参阅此链接。autoStoreVariables
scriptTask
true
1 <scriptTask id="script" scriptFormat="JavaScript" activiti:autoStoreVariables="false">
此参数的默认值为false
,这意味着如果在脚本任务定义中省略该参数,则所有声明的变量将仅在脚本执行期间存在。
如何在脚本中设置变量的示例:
1
2
3
4 <script>
def scriptVar = "test123"
execution.setVariable("myVar", scriptVar)
</script>
注意:以下名称是保留的,不能用作变量名:out、out:print、lang:import、context、elcontext。
脚本结果
通过将流程变量名称指定为脚本任务定义的'activiti:resultVariable'属性的文字值,可以将脚本任务的返回值分配给已经存在的或新的流程变量。特定流程变量的任何现有值都将被脚本执行的结果值覆盖。未指定结果变量名称时,脚本结果值将被忽略。
1
2
3 <scriptTask id="theScriptTask" name="Execute script" scriptFormat="juel" activiti:resultVariable="myVar">
<script>#{echo}</script>
</scriptTask>
在上面的示例中,脚本执行的结果(解析表达式'#{echo}'的值)在脚本完成后设置为名为'myVar'的流程变量。
安全
使用javascript作为脚本语言时也可以使用安全脚本。请参阅安全脚本部分。
8.5.3. Java 服务任务
描述
Java 服务任务用于调用外部 Java 类。
图形符号
服务任务显示为一个圆角矩形,左上角有一个小齿轮图标。
XML 表示
有 4 种方式来声明如何调用 Java 逻辑:
-
指定实现 JavaDelegate 或 ActivityBehavior 的类
-
评估解析为委托对象的表达式
-
调用方法表达式
-
评估值表达式
要指定在流程执行期间调用的类,需要通过activiti:class属性提供完全限定的类名。
1
2
3 <serviceTask id="javaService"
name="My Java Service Task"
activiti:class="org.activiti.MyJavaDelegate" />
有关如何使用此类的更多详细信息,请参见实现部分。
也可以使用解析为对象的表达式。activiti:class
该对象必须遵循与使用该属性时创建的对象相同的规则(请参阅更多信息)。
1 <serviceTask id="serviceTask" activiti:delegateExpression="${delegateExpressionBean}" />
这里,delegateExpressionBean
是一个实现JavaDelegate
接口的 bean,例如在 Spring 容器中定义。
要指定应评估的 UEL 方法表达式,请使用属性activiti:expression。
1
2
3 <serviceTask id="javaService"
name="My Java Service Task"
activiti:expression="#{printer.printMessage()}" />
printMessage
将在名为 的命名对象上调用方法(不带参数) printer
。
也可以使用表达式中使用的方法传递参数。
1
2
3 <serviceTask id="javaService"
name="My Java Service Task"
activiti:expression="#{printer.printMessage(execution, myVar)}" />
方法printMessage
将在名为 的对象上调用printer
。传递的第一个参数是DelegateExecution
,默认情况下它在表达式上下文中可用execution
。myVar
传递的第二个参数是当前执行中带有名称的变量的值。
要指定应评估的 UEL 值表达式,请使用属性activiti:expression。
1
2
3 <serviceTask id="javaService"
name="My Java Service Task"
activiti:expression="#{split.ready}" />
ready
property的getter 方法getReady
(不带参数)将在名为 的命名 bean 上调用split
。命名对象在执行的流程变量中解析,并且(如果适用)在 Spring 上下文中解析。
执行
要实现一个可以在流程执行过程中调用的类,这个类需要实现org.activiti.engine.delegate.JavaDelegate接口,并在execute方法中提供所需的逻辑。当流程执行到达此特定步骤时,它将执行该方法中定义的此逻辑,并以默认的 BPMN 2.0 方式保留活动。
例如,让我们创建一个可用于将流程变量 String 更改为大写的 Java 类。这个类需要实现org.activiti.engine.delegate.JavaDelegate接口,这需要我们实现execute(DelegateExecution)方法。引擎将调用此操作,并且需要包含业务逻辑。通过DelegateExecution界面可以访问和操作流程变量等流程实例信息(单击链接可查看其操作的详细 Javadoc)。
1
2
3
4
5
6
7
8
9 public class ToUppercase implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
String var = (String) execution.getVariable("input");
var = var.toUpperCase();
execution.setVariable("input", var);
}
}
注意:将只为定义它的 serviceTask 创建该 Java 类的一个实例。所有流程实例共享同一个类实例,用于调用execute(DelegateExecution)。这意味着该类不能使用任何成员变量并且必须是线程安全的,因为它可以从不同的线程同时执行。这也会影响处理现场注入的方式。
流程定义中引用的类(即使用activiti:class
)在部署期间不会实例化。只有当流程执行第一次到达流程中使用该类的点时,才会创建该类的实例。如果找不到该类,ActivitiException
则会抛出一个。这样做的原因是您部署时的环境(更具体地说是classpath )通常与实际的运行时环境不同。例如,当使用ant或 Activiti Explorer 中的业务档案上传来部署流程时,类路径不包含引用的类。
【INTERNAL:非公开实现类】也可以提供实现org.activiti.engine.impl.pvm.delegate.ActivityBehavior接口的类。然后,实现可以访问更强大的ActivityExecution,例如,它还允许影响流程的控制流。但是请注意,这不是一个很好的做法,应尽可能避免。因此,建议仅将ActivityBehavior接口用于高级用例并且如果您确切知道自己在做什么。
现场注入
可以将值注入到委托类的字段中。支持以下类型的注入:
-
固定字符串值
-
表达式
如果可用,该值将通过委托类上的公共 setter 方法注入,遵循 Java Bean 命名约定(例如,字段firstName
具有 setter setFirstName(…)
)。如果该字段没有可用的设置器,则将在委托上设置私有成员的值。某些环境中的 SecurityManagers 不允许修改私有字段,因此为您想要注入的字段公开公共设置方法会更安全。
无论流程定义中声明的值的类型如何,注入目标上的 setter/private 字段的类型应始终为org.activiti.engine.delegate.Expression
. 解析表达式后,可以将其强制转换为适当的类型。
使用'actviiti:class'属性时支持字段注入。使用activiti:delegateExpression属性时也可以进行字段注入,但是适用于线程安全的特殊规则(请参阅下一节)。
以下代码片段显示了如何将常量值注入到类上声明的字段中。请注意,我们需要在实际的字段注入声明之前声明一个extensionElements XML 元素,这是 BPMN 2.0 XML Schema 的要求。
1
2
3
4
5
6
7 <serviceTask id="javaService"
name="Java service invocation"
activiti:class="org.activiti.examples.bpmn.servicetask.ToUpperCaseFieldInjected">
<extensionElements>
<activiti:field name="text" stringValue="Hello World" />
</extensionElements>
</serviceTask>
该类ToUpperCaseFieldInjected
有一个text
类型为的字段org.activiti.engine.delegate.Expression
。调用时,会返回text.getValue(execution)
配置的字符串值:Hello World
1
2
3
4
5
6
7
8
9 public class ToUpperCaseFieldInjected implements JavaDelegate {
private Expression text;
public void execute(DelegateExecution execution) {
execution.setVariable("var", ((String)text.getValue(execution)).toUpperCase());
}
}
或者,对于长文本(例如内联电子邮件),可以使用'activiti:string'子元素:
1
2
3
4
5
6
7
8
9
10
11 <serviceTask id="javaService"
name="Java service invocation"
activiti:class="org.activiti.examples.bpmn.servicetask.ToUpperCaseFieldInjected">
<extensionElements>
<activiti:field name="text">
<activiti:string>
This is a long string with a lot of words and potentially way longer even!
</activiti:string>
</activiti:field>
</extensionElements>
</serviceTask>
要注入在运行时动态解析的值,可以使用表达式。这些表达式可以使用流程变量或 Spring 定义的 bean(如果使用 Spring)。如服务任务实现中所述,当使用activiti:class属性时,Java 类的实例在服务任务中的所有流程实例之间共享。要在字段中动态注入值,您可以在 a 中注入值和方法表达式,可以使用传入的方法org.activiti.engine.delegate.Expression
进行评估/调用。DelegateExecution
execute
下面的示例类使用注入的表达式并使用当前的DelegateExecution
. 在传递性别变量时使用了generBean方法调用。完整的代码和测试可以在org.activiti.examples.bpmn.servicetask.JavaServiceTaskTest.testExpressionFieldInjection
1
2
3
4
5
6
7
8
9
10
11
12 <serviceTask id="javaService" name="Java service invocation"
activiti:class="org.activiti.examples.bpmn.servicetask.ReverseStringsFieldInjected">
<extensionElements>
<activiti:field name="text1">
<activiti:expression>${genderBean.getGenderString(gender)}</activiti:expression>
</activiti:field>
<activiti:field name="text2">
<activiti:expression>Hello ${gender == 'male' ? 'Mr.' : 'Mrs.'} ${name}</activiti:expression>
</activiti:field>
</ extensionElements>
</ serviceTask>
1
2
3
4
5
6
7
8
9
10
11
12
13 public class ReverseStringsFieldInjected implements JavaDelegate {
private Expression text1;
private Expression text2;
public void execute(DelegateExecution execution) {
String value1 = (String) text1.getValue(execution);
execution.setVariable("var1", new StringBuffer(value1).reverse().toString());
String value2 = (String) text2.getValue(execution);
execution.setVariable("var2", new StringBuffer(value2).reverse().toString());
}
}
或者,您也可以将表达式设置为属性而不是子元素,以减少 XML 的冗长。
1
2 <activiti:field name="text1" expression="${genderBean.getGenderString(gender)}" />
<activiti:field name="text1" expression="Hello ${gender == 'male' ? 'Mr.' : 'Mrs.'} ${name}" />
字段注入和线程安全
通常,将服务任务与 Java 委托和字段注入一起使用是线程安全的。然而,根据 Activiti 运行的设置或环境,有一些情况下不能保证线程安全。
当使用activiti:class属性时,使用字段注入总是线程安全的。对于每个引用某个类的服务任务,将实例化一个新实例,并在创建实例时注入一次字段。在不同的任务或流程定义中多次重用同一个类是没有问题的。
使用activiti:expression属性时,无法使用字段注入。参数通过方法调用传递,这些始终是线程安全的。
使用activiti:delegateExpression属性时,委托实例的线程安全性将取决于表达式的解析方式。如果在各种任务和/或流程定义中重用委托表达式并且表达式总是返回相同的实例,则使用字段注入不是线程安全的。让我们看几个例子来澄清一下。
假设表达式是${factory.createDelegate(someVariable)},其中 factory 是引擎已知的 Java bean(例如使用 Spring 集成时的 Spring bean),每次解析表达式时都会创建一个新实例。在这种情况下使用字段注入时,在线程安全方面没有问题:每次解析表达式时,都会将字段注入到这个新实例上。
但是,假设表达式是${someJavaDelegateBean},它解析为 JavaDelegate 类的实现,并且我们在创建每个 bean 的单例实例的环境中运行(如 Spring,但也有许多其他实例)。在不同的任务和/或流程定义中使用此表达式时,该表达式将始终解析为相同的实例。在这种情况下,使用字段注入不是线程安全的。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13 <serviceTask id="serviceTask1" activiti:delegateExpression="${someJavaDelegateBean}">
<extensionElements>
<activiti:field name="someField" expression="${input * 2}"/>
</extensionElements>
</serviceTask>
<!-- other process definition elements -->
<serviceTask id="serviceTask2" activiti:delegateExpression="${someJavaDelegateBean}">
<extensionElements>
<activiti:field name="someField" expression="${input * 2000}"/>
</extensionElements>
</serviceTask>
此示例代码段有两个服务任务,它们使用相同的委托表达式,但为Expression字段注入不同的值。如果表达式解析为同一个实例,则在执行进程时注入字段someField时,并发场景中可能会出现竞争条件。
解决这个问题的最简单的解决方案是
-
重写 Java 委托以使用表达式并通过方法参数将所需的数据传递给委托。
-
每次解析委托表达式时返回委托类的新实例。例如在使用 Spring 时,这意味着必须将 bean 的范围设置为原型(例如通过在委托类中添加 @Scope(SCOPE_PROTOTYPE) 注释)
从 Activiti 5.21 版开始,可以通过设置delegateExpressionFieldInjectionMode属性的值(它采用org.activiti.engine中的值之一)来配置流程引擎配置,以禁用在委托表达式上使用字段注入。 imp.cfg.DelegateExpressionFieldInjectionMode枚举)。
可以进行以下设置:
-
DISABLED:使用委托表达式时完全禁用字段注入。不会尝试现场注入。就线程安全而言,这是最安全的模式。
-
兼容性:在此模式下,行为将与 5.21 版之前完全相同:使用委托表达式时可以进行字段注入,并且在委托类上未定义字段时将引发异常。就线程安全而言,这当然是最不安全的模式,但它可能是向后兼容所需要的,或者当委托表达式仅用于一组进程定义中的一个任务时可以安全使用(因此没有并发竞争条件可能发生)。
-
MIXED:在使用 delegateExpressions 时允许注入,但在未在委托上定义字段时不会引发异常。这允许混合行为,其中一些代表具有注入(例如,因为它们不是单例)而另一些则没有。
-
Activiti 5.x 版本的默认模式是 COMPATIBILITY。
-
Activiti 6.x 版本的默认模式是 MIXED。
例如,假设我们正在使用MIXED模式并且我们正在使用 Spring 集成。假设我们在 Spring 配置中有以下 bean:
1
2
3
4
5
6 <bean id="singletonDelegateExpressionBean"
class="org.activiti.spring.test.fieldinjection.SingletonDelegateExpressionBean" />
<bean id="prototypeDelegateExpressionBean"
class="org.activiti.spring.test.fieldinjection.PrototypeDelegateExpressionBean"
scope="prototype" />
第一个 bean 是一个普通的 Spring bean,因此是一个单例。第二个以原型为作用域,每次请求 bean 时,Spring 容器都会返回一个新实例。
给定以下流程定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 <serviceTask id="serviceTask1" activiti:delegateExpression="${prototypeDelegateExpressionBean}">
<extensionElements>
<activiti:field name="fieldA" expression="${input * 2}"/>
<activiti:field name="fieldB" expression="${1 + 1}"/>
<activiti:field name="resultVariableName" stringValue="resultServiceTask1"/>
</extensionElements>
</serviceTask>
<serviceTask id="serviceTask2" activiti:delegateExpression="${prototypeDelegateExpressionBean}">
<extensionElements>
<activiti:field name="fieldA" expression="${123}"/>
<activiti:field name="fieldB" expression="${456}"/>
<activiti:field name="resultVariableName" stringValue="resultServiceTask2"/>
</extensionElements>
</serviceTask>
<serviceTask id="serviceTask3" activiti:delegateExpression="${singletonDelegateExpressionBean}">
<extensionElements>
<activiti:field name="fieldA" expression="${input * 2}"/>
<activiti:field name="fieldB" expression="${1 + 1}"/>
<activiti:field name="resultVariableName" stringValue="resultServiceTask1"/>
</extensionElements>
</serviceTask>
<serviceTask id="serviceTask4" activiti:delegateExpression="${singletonDelegateExpressionBean}">
<extensionElements>
<activiti:field name="fieldA" expression="${123}"/>
<activiti:field name="fieldB" expression="${456}"/>
<activiti:field name="resultVariableName" stringValue="resultServiceTask2"/>
</extensionElements>
</serviceTask>
我们有四个服务任务,其中第一个和第二个使用${prototypeDelegateExpressionBean}委托表达式,第三个和第四个使用${singletonDelegateExpressionBean}委托表达式。
我们先看一下原型bean:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 public class PrototypeDelegateExpressionBean implements JavaDelegate {
public static AtomicInteger INSTANCE_COUNT = new AtomicInteger(0);
private Expression fieldA;
private Expression fieldB;
private Expression resultVariableName;
public PrototypeDelegateExpressionBean() {
INSTANCE_COUNT.incrementAndGet();
}
@Override
public void execute(DelegateExecution execution) throws Exception {
Number fieldAValue = (Number) fieldA.getValue(execution);
Number fieldValueB = (Number) fieldB.getValue(execution);
int result = fieldAValue.intValue() + fieldValueB.intValue();
execution.setVariable(resultVariableName.getValue(execution).toString(), result);
}
}
当我们在运行上述流程定义的流程实例后检查INSTANCE_COUNT时,我们会得到两个,因为每次解析${prototypeDelegateExpressionBean}时都会创建一个新实例。这里可以毫无问题地注入字段,我们可以在这里看到三个表达式成员字段。
然而,单例 bean 看起来略有不同:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 public class SingletonDelegateExpressionBean implements JavaDelegate {
public static AtomicInteger INSTANCE_COUNT = new AtomicInteger(0);
public SingletonDelegateExpressionBean() {
INSTANCE_COUNT.incrementAndGet();
}
@Override
public void execute(DelegateExecution execution) throws Exception {
Expression fieldAExpression = DelegateHelper.getFieldExpression(execution, "fieldA");
Number fieldA = (Number) fieldAExpression.getValue(execution);
Expression fieldBExpression = DelegateHelper.getFieldExpression(execution, "fieldB");
Number fieldB = (Number) fieldBExpression.getValue(execution);
int result = fieldA.intValue() + fieldB.intValue();
String resultVariableName = DelegateHelper.getFieldExpression(execution, "resultVariableName").getValue(execution).toString();
execution.setVariable(resultVariableName, result);
}
}
INSTANCE_COUNT将永远是一个,因为它是一个单例。在此委托中,没有表达式成员字段。这是可能的,因为我们在MIXED模式下运行。在COMPATIBILITY模式下,这将引发异常,因为它期望成员字段存在。DISABLED模式也适用于此 bean,但它不允许使用上面使用字段注入的原型 bean。
在这个委托代码中,使用了org.activiti.engine.delegate.DelegateHelper类,它有一些有用的实用方法来执行相同的逻辑,但是当委托是单例时以线程安全的方式。它不是注入Expression,而是通过getFieldExpression方法获取。这意味着当涉及到服务任务 xml 时,字段的定义与单例 bean 完全相同。如果您查看上面的 xml 片段,您会发现它们在定义上是相同的,只有实现逻辑不同。
(技术说明:getFieldExpression将自省 BpmnModel 并在执行该方法时动态创建 Expression,使其成为线程安全的)
-
对于 Activiti 5.x 版本,DelegateHelper 不能用于ExecutionListener或TaskListener(由于架构缺陷)。要创建这些侦听器的线程安全实例,请使用表达式或确保每次解析委托表达式时都创建一个新实例。
-
对于 Activiti 版本 6.x,DelegateHelper 确实在ExecutionListener和TaskListener实现中工作。例如,在 6.x 版本中,可以使用DelegateHelper编写以下代码:
1
2
3
4
5
6
7 <extensionElements>
<activiti:executionListener
delegateExpression="${testExecutionListener}" event="start">
<activiti:field name="input" expression="${startValue}" />
<activiti:field name="resultVar" stringValue="processStartValue" />
</activiti:executionListener>
</extensionElements>
其中testExecutionListener解析为实现 ExecutionListener 接口的实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 @Component("testExecutionListener")
public class TestExecutionListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution) {
Expression inputExpression = DelegateHelper.getFieldExpression(execution, "input");
Number input = (Number) inputExpression.getValue(execution);
int result = input.intValue() * 100;
Expression resultVarExpression = DelegateHelper.getFieldExpression(execution, "resultVar");
execution.setVariable(resultVarExpression.getValue(execution).toString(), result);
}
}
服务任务结果
通过将流程变量名称指定为服务任务的“activiti:resultVariable”属性的文字值,可以将服务执行的返回值(仅用于使用表达式的服务任务)分配给已经存在的或新的流程变量定义。特定流程变量的任何现有值都将被服务执行的结果值覆盖。未指定结果变量名称时,服务执行结果值将被忽略。
1
2
3 <serviceTask id="aMethodExpressionServiceTask"
activiti:expression="#{myService.doSomething()}"
activiti:resultVariable="myVar" />
在上面的示例中,设置了服务执行的结果(在流程变量或 Spring bean 中以名称“myService”提供的对象上的“doSomething()”方法调用的返回值)被设置服务执行完成后到名为“myVar”的流程变量。
处理异常
在执行自定义逻辑时,往往需要捕捉某些业务异常,并在周边流程内部进行处理。Activiti 提供了不同的选项来做到这一点。
引发 BPMN 错误
可以从服务任务或脚本任务中的用户代码抛出 BPMN 错误。为此,可以在 JavaDelegates、脚本、表达式和委托表达式中抛出一个称为BpmnError的特殊 ActivitiException。引擎将捕获此异常并将其转发给适当的错误处理程序,例如边界错误事件或错误事件子流程。
1
2
3
4
5
6
7
8
9
10
11 public class ThrowBpmnErrorDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
try {
executeBusinessLogic();
} catch (BusinessException e) {
throw new BpmnError("BusinessExceptionOccurred");
}
}
}
构造函数参数是一个错误代码,将用于确定对错误负责的错误处理程序。有关如何捕获 BPMN 错误的信息,请参阅边界错误事件。
该机制应该仅用于应由流程定义中建模的边界错误事件或错误事件子流程处理的业务故障。技术错误应该由其他异常类型表示,并且通常不在进程内部处理。
异常映射
也可以通过mapException
扩展直接将java异常映射到业务异常。单一映射是最简单的形式:
1
2
3
4
5
6 <serviceTask id="servicetask1" name="Service Task" activiti:class="...">
<extensionElements>
<activiti:mapException
errorCode="myErrorCode1">org.activiti.SomeException</activiti:mapException>
</extensionElements>
</serviceTask>
在上面的代码中,如果在服务任务中抛出一个实例org.activiti.SomeException
,它将被捕获并转换为具有给定错误代码的 BPMN 异常。从这一点开始,它将完全像正常的 BPMN 异常一样处理。任何其他异常都将被视为没有适当的映射。它将传播给 API 调用者。
includeChildExceptions
可以通过使用属性将某个异常的所有子异常映射在一行中。
1
2
3
4
5
6 <serviceTask id="servicetask1" name="Service Task" activiti:class="...">
<extensionElements>
<activiti:mapException errorCode="myErrorCode1"
includeChildExceptions="true">org.activiti.SomeException</activiti:mapException>
</extensionElements>
</serviceTask>
上面的代码将导致 activiti 将任何直接或间接的后代转换SomeException
为具有给定错误代码的 BPMN 错误。
includeChildExceptions
未给出时将被视为“假”。
最通用的映射是默认映射。默认地图是没有类的地图。它将匹配任何 java 异常:
1
2
3
4
5 <serviceTask id="servicetask1" name="Service Task" activiti:class="...">
<extensionElements>
<activiti:mapException errorCode="myErrorCode1"/>
</extensionElements>
</serviceTask>
映射按顺序检查,从上到下,将遵循第一个找到的匹配项,默认映射除外。只有在所有地图都未成功检查后才会选择默认地图。只有第一个没有类的地图将被视为默认地图。includeChildExceptions
默认地图被忽略。
异常序列流
另一种选择是通过另一条路径路由流程执行,以防发生某些异常。以下示例显示了这是如何完成的。
1
2
3
4
5
6
7 <serviceTask id="javaService"
name="Java service invocation"
activiti:class="org.activiti.ThrowsExceptionBehavior">
</serviceTask>
<sequenceFlow id="no-exception" sourceRef="javaService" targetRef="theEnd" />
<sequenceFlow id="exception" sourceRef="javaService" targetRef="fixException" />
这里,服务任务有两个传出序列流,称为exception
和no-exception
。此序列流 ID 将用于在出现异常时指导流程流:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 public class ThrowsExceptionBehavior implements ActivityBehavior {
public void execute(ActivityExecution execution) throws Exception {
String var = (String) execution.getVariable("var");
PvmTransition transition = null;
try {
executeLogic(var);
transition = execution.getActivity().findOutgoingTransition("no-exception");
} catch (Exception e) {
transition = execution.getActivity().findOutgoingTransition("exception");
}
execution.take(transition);
}
}
在 JavaDelegate 中使用 Activiti 服务
对于某些用例,可能需要从 Java 服务任务中使用 Activiti 服务(例如,如果 callActivity 不适合您的需要,则通过 RuntimeService 启动流程实例)。org.activiti.engine.delegate.DelegateExecution允许通过org.activiti.engine.EngineServices接口轻松使用这些服务:
1
2
3
4
5
6
7
8 public class StartProcessInstanceTestDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
RuntimeService runtimeService = execution.getEngineServices().getRuntimeService();
runtimeService.startProcessInstanceByKey("myProcess");
}
}
所有的 Activiti 服务 API 都可以通过这个接口获得。
由于使用这些 API 调用而发生的所有数据更改都将成为当前事务的一部分。这也适用于依赖注入的环境,如 Spring 和 CDI,无论是否启用了 JTA 数据源。例如,下面的代码片段将与上面的代码片段执行相同的操作,但现在 RuntimeService 被注入,而不是通过org.activiti.engine.EngineServices接口获取。
1
2
3
4
5
6
7
8
9
10
11 @Component("startProcessInstanceDelegate")
public class StartProcessInstanceTestDelegateWithInjection {
@Autowired
private RuntimeService runtimeService;
public void startProcess() {
runtimeService.startProcessInstanceByKey("oneTaskProcess");
}
}
重要的技术说明:由于服务调用是作为当前事务的一部分完成的,因此在执行服务任务之前生成或更改的任何数据尚未刷新到数据库中。所有 API 调用都对数据库数据起作用,这意味着这些未提交的更改在服务任务的 api 调用中是不可见的。
8.5.4. 网络服务任务
描述
Web Service 任务用于同步调用外部 Web 服务。
图形符号
Web 服务任务的可视化与 Java 服务任务相同。
XML 表示
要使用 Web 服务,我们需要导入其操作和复杂类型。这可以通过使用指向 Web 服务的 WSDL 的导入标记自动完成:
1
2
3 <import importType="http://schemas.xmlsoap.org/wsdl/"
location="http://localhost:63081/counter?wsdl"
namespace="http://webservice.activiti.org/" />
前面的声明告诉 Activiti 导入定义,但它不会为您创建项目定义和消息。假设我们要调用一个名为prettyPrint的特定方法,因此我们需要为请求和响应消息创建相应的消息和项目定义:
1
2
3
4
5 <message id="prettyPrintCountRequestMessage" itemRef="tns:prettyPrintCountRequestItem" />
<message id="prettyPrintCountResponseMessage" itemRef="tns:prettyPrintCountResponseItem" />
<itemDefinition id="prettyPrintCountRequestItem" structureRef="counter:prettyPrintCount" />
<itemDefinition id="prettyPrintCountResponseItem" structureRef="counter:prettyPrintCountResponse" />
在声明服务任务之前,我们必须定义实际引用 Web 服务接口和操作的 BPMN 接口和操作。基本上,我们定义和接口以及所需的操作。对于每个操作,我们将先前定义的消息重用于 in 和 out。例如,以下声明定义了计数器接口和prettyPrintCountOperation操作:
1
2
3
4
5
6
7 <interface name="Counter Interface" implementationRef="counter:Counter">
<operation id="prettyPrintCountOperation" name="prettyPrintCount Operation"
implementationRef="counter:prettyPrintCount">
<inMessageRef>tns:prettyPrintCountRequestMessage</inMessageRef>
<outMessageRef>tns:prettyPrintCountResponseMessage</outMessageRef>
</operation>
</interface>
然后我们可以通过使用##WebService 实现和对Web 服务操作的引用来声明一个Web 服务任务。
1
2
3
4 <serviceTask id="webService"
name="Web service invocation"
implementation="##WebService"
operationRef="tns:prettyPrintCountOperation">
Web 服务任务 IO 规范
除非我们对数据输入和输出关联使用简单的方法(见下文),否则每个 Web 服务任务都需要声明一个 IO 规范,其中说明哪些是任务的输入和输出。该方法非常简单,并且符合 BPMN 2.0,对于我们的 prettyPrint 示例,我们根据之前声明的项目定义来定义输入和输出集:
1
2
3
4
5
6
7
8
9
10 <ioSpecification>
<dataInput itemSubjectRef="tns:prettyPrintCountRequestItem" id="dataInputOfServiceTask" />
<dataOutput itemSubjectRef="tns:prettyPrintCountResponseItem" id="dataOutputOfServiceTask" />
<inputSet>
<dataInputRefs>dataInputOfServiceTask</dataInputRefs>
</inputSet>
<outputSet>
<dataOutputRefs>dataOutputOfServiceTask</dataOutputRefs>
</outputSet>
</ioSpecification>
Web 服务任务数据输入关联
有两种指定数据输入关联的方法:
-
使用表达式
-
使用简单的方法
要使用表达式指定数据输入关联,我们需要定义源项和目标项,并指定每个项的字段之间的对应分配。在以下示例中,我们分配项目的前缀和后缀字段:
1
2
3
4
5
6
7
8
9
10
11
12 <dataInputAssociation>
<sourceRef>dataInputOfProcess</sourceRef>
<targetRef>dataInputOfServiceTask</targetRef>
<assignment>
<from>${dataInputOfProcess.prefix}</from>
<to>${dataInputOfServiceTask.prefix}</to>
</assignment>
<assignment>
<from>${dataInputOfProcess.suffix}</from>
<to>${dataInputOfServiceTask.suffix}</to>
</assignment>
</dataInputAssociation>
另一方面,我们可以使用简单得多的简单方法。sourceRef元素是一个 Activiti 变量名,而targetRef元素是项目定义的一个属性。在下面的示例中,我们将变量 PrefixVariable 的值分配给前缀字段,将变量SuffixVariable的值分配给后缀字段。
1
2
3
4
5
6
7
8 <dataInputAssociation>
<sourceRef>PrefixVariable</sourceRef>
<targetRef>prefix</targetRef>
</dataInputAssociation>
<dataInputAssociation>
<sourceRef>SuffixVariable</sourceRef>
<targetRef>suffix</targetRef>
</dataInputAssociation>
Web 服务任务数据输出关联
有两种指定数据输出关联的方法:
-
使用表达式
-
使用简单的方法
要使用表达式指定数据输出关联,我们需要定义目标变量和源表达式。该方法非常简单且相似的数据输入关联:
1
2
3
4 <dataOutputAssociation>
<targetRef>dataOutputOfProcess</targetRef>
<transformation>${dataOutputOfServiceTask.prettyPrint}</transformation>
</dataOutputAssociation>
另一方面,我们可以使用简单得多的简单方法。sourceRef元素是项目定义的一个属性,而targetRef元素是一个 Activiti 变量名。该方法非常简单且相似的数据输入关联:
1
2
3
4 <dataOutputAssociation>
<sourceRef>prettyPrint</sourceRef>
<targetRef>OutputVariable</targetRef>
</dataOutputAssociation>
8.5.5。业务规则任务
描述
业务规则任务用于同步执行一个或多个规则。Activiti 使用 Drools Expert,即 Drools 规则引擎来执行业务规则。目前,包含业务规则的 .drl 文件必须与定义业务规则任务的流程定义一起部署以执行这些规则。这意味着流程中使用的所有 .drl 文件都必须打包在流程 BAR 文件中,例如任务表单。有关为 Drools Expert 创建业务规则的更多信息,请参阅JBoss Drools的 Drools 文档
如果你想插入你的规则任务的实现,例如因为你想以不同的方式使用 Drools 或者你想使用完全不同的规则引擎,那么你可以使用 BusinessRuleTask 上的 class 或 expression 属性,它的行为将完全一样服务任务
图形符号
业务规则任务通过表格图标进行可视化。
XML 表示
要执行部署在与流程定义相同的 BAR 文件中的一个或多个业务规则,我们需要定义输入和结果变量。对于输入变量定义,可以定义一系列过程变量,用逗号分隔。输出变量定义只能包含一个变量名,用于将执行的业务规则的输出对象存储在流程变量中。请注意,结果变量将包含一个对象列表。如果默认没有指定结果变量名,则使用 org.activiti.engine.rules.OUTPUT。
以下业务规则任务执行随流程定义部署的所有业务规则:
1
2
3
4
5
6
7
8
9
10
11
12
13 <process id="simpleBusinessRuleProcess">
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="businessRuleTask" />
<businessRuleTask id="businessRuleTask" activiti:ruleVariablesInput="${order}"
activiti:resultVariable="rulesOutput" />
<sequenceFlow sourceRef="businessRuleTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
业务规则任务也可以配置为仅执行已部署的 .drl 文件中定义的一组规则。必须为此指定以逗号分隔的规则名称列表。
1
2 <businessRuleTask id="businessRuleTask" activiti:ruleVariablesInput="${order}"
activiti:rules="rule1, rule2" />
在这种情况下,仅执行 rule1 和 rule2。
您还可以定义应从执行中排除的规则列表。
1
2 <businessRuleTask id="businessRuleTask" activiti:ruleVariablesInput="${order}"
activiti:rules="rule1, rule2" exclude="true" />
在这种情况下,部署在与流程定义相同的 BAR 文件中的所有规则都将被执行,除了 rule1 和 rule2。
如前所述,另一种选择是自己挂钩 BusinessRuleTask 的实现:
1 <businessRuleTask id="businessRuleTask" activiti:class="${MyRuleServiceDelegate}" />
现在 BusinessRuleTask 的行为与 ServiceTask 完全一样,但仍保留 BusinessRuleTask 图标以可视化我们在此处进行业务规则处理。
8.5.6。电子邮件任务
Activiti 允许通过向一个或多个收件人发送电子邮件的自动邮件服务任务来增强业务流程,包括对 cc、bcc、HTML 内容等的支持。请注意,邮件任务不是BPMN 2.0的官方任务规范(因此它没有专用图标)。因此,在 Activiti 中,邮件任务被实现为一个专门的服务任务。
邮件服务器配置
Activiti 引擎通过具有 SMTP 功能的外部邮件服务器发送电子邮件。要真正发送电子邮件,引擎需要知道如何到达邮件服务器。可以在activiti.cfg.xml配置文件中设置以下属性:
财产 | 必需的? | 描述 |
---|---|---|
邮件服务器主机 |
不 |
您的邮件服务器的主机名(例如 mail.mycorp.com)。默认为 |
邮件服务器端口 |
是的,如果不在默认端口上 |
邮件服务器上 SMTP 流量的端口。默认值为25 |
mailServerDefaultFrom |
不 |
电子邮件发件人的默认电子邮件地址,当用户未提供时。默认情况下这是activiti@activiti.org |
邮件服务器用户名 |
如果适用于您的服务器 |
某些邮件服务器需要凭据才能发送电子邮件。默认不设置。 |
邮件服务器密码 |
如果适用于您的服务器 |
某些邮件服务器需要凭据才能发送电子邮件。默认不设置。 |
邮件服务器使用SSL |
如果适用于您的服务器 |
一些邮件服务器需要 ssl 通信。默认设置为假。 |
邮件服务器使用TLS |
如果适用于您的服务器 |
某些邮件服务器(例如 gmail)需要 TLS 通信。默认设置为假。 |
定义电子邮件任务
电子邮件任务被实现为一个专用的服务任务,并通过为服务任务的类型设置“邮件”来定义。
1 <serviceTask id="sendMail" activiti:type="mail">
电子邮件任务由字段注入配置。这些属性的所有值都可以包含 EL 表达式,这些表达式在流程执行期间在运行时解析。可以设置以下属性:
财产 | 必需的? | 描述 |
---|---|---|
到 |
是的 |
收件人,如果是电子邮件。多个收件人在逗号分隔的列表中定义 |
从 |
不 |
发件人电子邮件地址。如果未提供,则使用从地址配置的默认值。 |
主题 |
不 |
电子邮件的主题。 |
抄送 |
不 |
电子邮件的抄送。多个收件人在逗号分隔的列表中定义 |
密件抄送 |
不 |
电子邮件的密件抄送。多个收件人在逗号分隔的列表中定义 |
字符集 |
不 |
允许更改电子邮件的字符集,这是许多非英语语言所必需的。 |
html |
不 |
一段 HTML,它是电子邮件的内容。 |
文本 |
不 |
电子邮件的内容,以防需要发送普通的非丰富电子邮件。可以与html结合使用,用于不支持丰富内容的电子邮件客户端。然后,客户端将回退到这种纯文本替代方案。 |
htmlVar |
不 |
保存作为电子邮件内容的 HTML 的流程变量的名称。这与 html 之间的主要区别在于,此内容将在被邮件任务发送之前替换表达式。 |
文本变量 |
不 |
保存电子邮件纯文本内容的过程变量的名称。这与 html 之间的主要区别在于,此内容将在被邮件任务发送之前替换表达式。 |
忽略异常 |
不 |
处理电子邮件时失败是否会引发 ActivitiException。默认情况下,这设置为 false。 |
异常变量名 |
不 |
当电子邮件处理由于ignoreException = true未引发异常时,使用给定名称的变量来保存失败消息 |
示例用法
以下 XML 片段显示了使用电子邮件任务的示例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 <serviceTask id="sendMail" activiti:type="mail">
<extensionElements>
<activiti:field name="from" stringValue="order-shipping@thecompany.com" />
<activiti:field name="to" expression="${recipient}" />
<activiti:field name="subject" expression="Your order ${orderId} has been shipped" />
<activiti:field name="html">
<activiti:expression>
<![CDATA[
<html>
<body>
Hello ${male ? 'Mr.' : 'Mrs.' } ${recipientName},<br/><br/>
As of ${now}, your order has been <b>processed and shipped</b>.<br/><br/>
Kind regards,<br/>
TheCompany.
</body>
</html>
]]>
</activiti:expression>
</activiti:field>
</extensionElements>
</serviceTask>
结果如下:
8.5.7。骡子任务
mule 任务允许向 Mule 发送消息以增强 Activiti 的集成功能。请注意,mule 任务不是BPMN 2.0 规范的官方任务(因此它没有专用图标)。因此,在 Activiti 中,mule 任务被实现为一个专门的服务任务。
定义 Mule 任务
Mule 任务被实现为专用的服务任务,并通过为服务任务的类型设置“mule”来定义。
1 <serviceTask id="sendMule" activiti:type="mule">
Mule 任务是通过field injection配置的。这些属性的所有值都可以包含 EL 表达式,这些表达式在流程执行期间在运行时解析。可以设置以下属性:
财产 | 必需的? | 描述 |
---|---|---|
端点网址 |
是的 |
您要调用的 Mule 端点。 |
语 |
是的 |
您要用于评估 payloadExpression 字段的语言。 |
有效载荷表达式 |
是的 |
将成为消息有效负载的表达式。 |
结果变量 |
不 |
将存储调用结果的变量的名称。 |
示例用法
以下 XML 片段显示了使用 Mule 任务的示例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14 <extensionElements>
<activiti:field name="endpointUrl">
<activiti:string>vm://in</activiti:string>
</activiti:field>
<activiti:field name="language">
<activiti:string>juel</activiti:string>
</activiti:field>
<activiti:field name="payloadExpression">
<activiti:string>"hi"</activiti:string>
</activiti:field>
<activiti:field name="resultVariable">
<activiti:string>theVariable</activiti:string>
</activiti:field>
</extensionElements>
8.5.8。骆驼任务
Camel 任务允许向 Camel 发送消息和从 Camel 接收消息,从而增强了 Activiti 的集成功能。请注意,Camel 任务不是BPMN 2.0 规范的官方任务(因此它没有专用图标)。因此,在 Activiti 中,Camel 任务被实现为一个专门的服务任务。还要注意在项目中包含 Activiti Camel 模块以使用 Camel 任务功能。
定义骆驼任务
Camel 任务被实现为专用的服务任务,并通过为服务任务的类型设置“骆驼”来定义。
1 <serviceTask id="sendCamel" activiti:type="camel">
流程定义本身只需要服务任务上的骆驼类型定义。集成逻辑全部委托给 Camel 容器。默认情况下,Activiti Engine 在 Spring 容器中寻找一个 camelContext bean。camelContext bean 定义了将由 Camel 容器加载的 Camel 路由。在下面的示例中,路由是从特定的 Java 包加载的,但您也可以直接在 Spring 配置本身中定义路由。
1
2
3
4
5 <camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
<packageScan>
<package>org.activiti.camel.route</package>
</packageScan>
</camelContext>
有关骆驼路线的更多文档,您可以查看骆驼网站。本文档中的几个小示例演示了基本概念。在第一个示例中,我们将从 Activiti 工作流中执行最简单形式的 Camel 调用。我们称之为 SimpleCamelCall。
如果您想定义多个 Camel 上下文 bean 和/或想使用不同的 bean 名称,可以在 Camel 任务定义上重写,如下所示:
1
2
3
4
5 <serviceTask id="serviceTask1" activiti:type="camel">
<extensionElements>
<activiti:field name="camelContext" stringValue="customCamelContext" />
</extensionElements>
</serviceTask>
简单的骆驼呼叫示例
与本示例相关的所有文件都可以在 activiti-camel 模块的 org.activiti.camel.examples.simpleCamelCall 包中找到。目标只是激活特定的骆驼路线。首先,我们需要一个 Spring 上下文,其中包含前面提到的路由介绍。文件的这一部分用于此目的:
1
2
3
4
5 <camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
<packageScan>
<package>org.activiti.camel.examples.simpleCamelCall</package>
</packageScan>
</camelContext>
1
2
3
4
5
6
7 public class SimpleCamelCallRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from("activiti:SimpleCamelCallProcess:simpleCall").to("log:org.activiti.camel.examples.SimpleCamelCall");
}
}
该路由仅记录消息正文,仅此而已。注意 from 端点的格式。它由三部分组成:
端点网址部分 | 描述 |
---|---|
活动 |
指的是 Activiti 端点 |
SimpleCamelCallProcess |
进程名称 |
简单呼叫 |
进程中的骆驼服务的名称 |
好的,我们的路线现在已正确配置并可供骆驼访问。现在是工作流程部分。工作流程如下所示:
1
2
3
4
5
6
7
8
9 <process id="SimpleCamelCallProcess">
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="simpleCall"/>
<serviceTask id="simpleCall" activiti:type="camel"/>
<sequenceFlow id="flow2" sourceRef="simpleCall" targetRef="end"/>
<endEvent id="end"/>
</process>
乒乓球示例
我们的例子奏效了,但在 Camel 和 Activiti 之间并没有真正转移,而且没有太多优点。在这个例子中,我们尝试向 Camel 发送和接收数据。我们发送一个字符串,camel 将一些东西连接到它并返回结果。发送者部分很简单,我们以变量的形式将消息发送到 Camel Task。这是我们的调用者代码:
1
2
3
4
5
6
7
8
9
10
11
12
13 @Deployment
public void testPingPong() {
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("input", "Hello");
Map<String, String> outputMap = new HashMap<String, String>();
variables.put("outputMap", outputMap);
runtimeService.startProcessInstanceByKey("PingPongProcess", variables);
assertEquals(1, outputMap.size());
assertNotNull(outputMap.get("outputValue"));
assertEquals("Hello World", outputMap.get("outputValue"));
}
变量“input”实际上是 Camel 路由的输入,而 outputMap 用于从 Camel 捕获返回的结果。该过程应该是这样的:
1
2
3
4
5
6
7
8
9 <process id="PingPongProcess">
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="ping"/>
<serviceTask id="ping" activiti:type="camel"/>
<sequenceFlow id="flow2" sourceRef="ping" targetRef="saveOutput"/>
<serviceTask id="saveOutput" activiti:class="org.activiti.camel.examples.pingPong.SaveOutput" />
<sequenceFlow id="flow3" sourceRef="saveOutput" targetRef="end"/>
<endEvent id="end"/>
</process>
请注意,SaveOutput Service 任务将“Output”变量的值从上下文存储到前面提到的 OutputMap。现在我们必须知道变量是如何发送到 Camel 并返回的。这里将骆驼行为的概念引入了戏剧中。变量与 Camel 通信的方式可通过 CamelBehavior 进行配置。在这里,我们在示例中使用默认值,然后对其他值进行简短描述。使用这样的代码,您可以配置所需的骆驼行为:
1
2
3
4
5 <serviceTask id="serviceTask1" activiti:type="camel">
<extensionElements>
<activiti:field name="camelBehaviorClass" stringValue="org.activiti.camel.impl.CamelBehaviorCamelBodyImpl" />
</extensionElements>
</serviceTask>
如果您没有指定具体的行为,那么将设置 org.activiti.camel.impl.CamelBehaviorDefaultImpl。此行为将变量复制到同名的 Camel 属性。作为回报,不管选择的行为如何,如果骆驼消息体是一个映射,那么它的每个元素都被复制为一个变量,否则整个对象被复制到一个名为“camelBody”的特定变量中。知道了这一点,这条骆驼路线结束了我们的第二个例子:
1
2
3
4 @Override
public void configure() throws Exception {
from("activiti:PingPongProcess:ping").transform().simple("${property.input} World");
}
在此路由中,字符串“world”被连接到名为“input”的属性的末尾,结果将在消息正文中。它可以通过检查 java 服务任务中的“camelBody”变量并复制到“outputMap”并在测试用例中检查来访问。现在关于其默认行为的示例有效,让我们看看其他可能性是什么。在启动每条骆驼路线时,流程实例 ID 将被复制到特定名称为“PROCESS_ID_PROPERTY”的骆驼属性中。它稍后用于关联流程实例和骆驼路线。它也可以在骆驼路线中被利用。
Activiti 中已经提供了三种不同的行为。该行为可以被路由 URL 中的特定短语覆盖。这是一个覆盖 URL 中已定义行为的示例:
1 from("activiti:asyncCamelProcess:serviceTaskAsync2?copyVariablesToProperties=true").
下表概述了三种可用的骆驼行为:
行为 | 在网址中 | 描述 |
---|---|---|
CamelBehaviorDefaultImpl |
复制变量到属性 |
将 Activiti 变量复制为 Camel 属性 |
CamelBehaviorCamelBodyImpl |
复制CamelBodyToBody |
仅将名为“camelBody”的 Activiti 变量复制为骆驼消息正文 |
CamelBehaviorBodyAsMapImpl |
copyVariablesToBodyAsMap |
将地图中的所有 Activiti 变量复制为 Camel 消息体 |
上表解释了如何将 Activiti 变量传输到 Camel。下表解释了如何将 Camel 变量返回给 Activiti。这只能在路由 URL 中配置。
网址 | 描述 |
---|---|
默认 |
如果 Camel body 是一个 map,将每个元素复制为 Activiti 变量,否则将整个 Camel 体复制为“camelBody”Activiti 变量 |
copyVariablesFromProperties |
将 Camel 属性复制为 Activiti 同名变量 |
复制CamelBodyToBodyAsString |
和默认一样,但是如果camel Body不是贴图,先转成String再复制到“camelBody” |
copyVariablesFromHeader |
另外将骆驼标题复制到同名的Activiti变量 |
返回变量
上面提到的关于传递变量的内容,仅适用于变量传输的开始端,从 Camel 到 Activiti 的两个方向,反之亦然。
需要注意的是,由于 Activiti 的特殊非阻塞行为,变量不会自动从 Activiti 返回到 Camel。为此,可以使用一种特殊的语法。Camel 路由 URL 中可以有一个或多个参数,格式为var.return.someVariableName
. 所有名称等于这些参数之一但没有var.return
部分的变量将被视为输出变量,并将作为具有相同名称的骆驼属性复制回来。
例如在如下路线中:
from("direct:start").to("activiti:process?var.return.exampleVar").to("mock:result");
名为 的 Activiti 变量exampleVar
将被视为输出变量,并将作为同名的骆驼属性复制回来。
异步乒乓示例
前面的例子都是同步的。工作流停止,直到骆驼路线结束并返回。在某些情况下,我们可能需要 Activiti 工作流程才能继续。为此,Camel 服务任务的异步功能很有用。您可以通过将 Camel 服务任务的 async 属性设置为 true 来使用此功能。
1 <serviceTask id="serviceAsyncPing" activiti:type="camel" activiti:async="true"/>
通过设置这个特性,指定的 Camel 路由被 Activiti 作业执行器异步激活。当您在 Camel 路由中定义队列时,Activiti 进程将继续执行 Camel 服务任务之后的活动。Camel 路由将与流程执行完全异步执行。如果您想在流程定义中的某处等待 Camel 服务任务的响应,您可以使用接收任务。
1 <receiveTask id="receiveAsyncPing" name="Wait State" />
流程实例将等待直到收到信号,例如来自 Camel。在 Camel 中,您可以通过向适当的 Activiti 端点发送消息来向流程实例发送信号。
1 from("activiti:asyncPingProcess:serviceAsyncPing").to("activiti:asyncPingProcess:receiveAsyncPing");
-
常量字符串“activiti”
-
进程名称
-
接收任务名称
从骆驼路线实例化工作流程
在我们之前的所有示例中,Activiti 工作流首先启动,Camel 路线在工作流中启动。从另一边也可以。有可能从已经开始的骆驼路线实例化工作流。它与信令接收任务非常相似,只是最后一部分不存在。这是一个示例路线:
1 from("direct:start").to("activiti:camelProcess");
如您所见,url 有两部分,第一部分是常量字符串“activiti”,第二部分是进程的名称。显然,该过程应该已经部署并且可以通过引擎配置启动。
也可以将进程的发起者设置为在 Camel 标头中提供的某个经过身份验证的用户 ID。要实现这一点,首先必须在流程定义中指定启动器变量:
1 <startEvent id="start" activiti:initiator="initiator" />
然后假设用户 id 包含在名为CamelProcessInitiatorHeader的 Camel 标头中,Camel 路由可以定义如下:
1
2
3 from("direct:startWithInitiatorHeader")
.setHeader("CamelProcessInitiatorHeader", constant("kermit"))
.to("activiti:InitiatorCamelCallProcess?processInitiatorHeaderName=CamelProcessInitiatorHeader");
8.5.9。手动任务
描述
手动任务定义 BPM 引擎外部的任务。它用于对某人完成的工作进行建模,引擎不需要知道,也没有系统或 UI 界面。对于引擎,手动任务作为传递活动处理,从流程执行到达其中的那一刻起自动继续流程。
图形符号
手动任务显示为圆角矩形,左上角有一个小手图标
XML 表示
1 <manualTask id="myManualTask" name="Call client for more information" />
8.5.10。Java 接收任务
描述
接收任务是一个等待特定消息到达的简单任务。目前,我们只为这个任务实现了 Java 语义。当流程执行到达接收任务时,流程状态将提交到持久存储。这意味着该进程将保持此等待状态,直到引擎接收到特定消息,这会触发该进程在 Receive Task 之后的继续。
图形符号
接收任务可视化为左上角带有消息图标的任务(圆角矩形)。消息是白色的(黑色消息图标将具有发送语义)
XML 表示
1 <receiveTask id="waitState" name="wait" />
要继续当前在此类接收任务中等待的流程实例,必须使用到达接收任务的执行 ID 调用runtimeService.signal(executionId) 。以下代码片段显示了它在实践中的工作原理:
1
2
3
4
5
6
7
8 ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
Execution execution = runtimeService.createExecutionQuery()
.processInstanceId(pi.getId())
.activityId("waitState")
.singleResult();
assertNotNull(execution);
runtimeService.signal(execution.getId());
8.5.11。外壳任务
描述
shell 任务允许运行 shell 脚本和命令。请注意,Shell 任务不是BPMN 2.0 规范的官方任务(因此它没有专用图标)。
定义 shell 任务
shell 任务被实现为专用的服务任务,并通过为服务任务的类型设置“shell”来定义。
1 <serviceTask id="shellEcho" activiti:type="shell">
Shell 任务由字段注入配置。这些属性的所有值都可以包含 EL 表达式,这些表达式在流程执行期间在运行时解析。可以设置以下属性:
财产 | 必需的? | 类型 | 描述 | 默认 |
---|---|---|---|---|
命令 |
是的 |
细绳 |
要执行的 Shell 命令。 |
|
arg0-5 |
不 |
细绳 |
参数 0 至参数 5 |
|
等待 |
不 |
真假 |
如有必要,请等待,直到 shell 进程终止。 |
真的 |
重定向错误 |
不 |
真假 |
将标准错误与标准输出合并。 |
错误的 |
清洁环境 |
不 |
真假 |
Shell 进程不继承当前环境。 |
错误的 |
输出变量 |
不 |
细绳 |
包含输出的变量名称 |
不记录输出。 |
错误代码变量 |
不 |
细绳 |
包含结果错误代码的变量名称 |
错误级别未注册。 |
目录 |
不 |
细绳 |
shell进程的默认目录 |
当前目录 |
示例用法
以下 XML 片段显示了使用 shell 任务的示例。它运行 shell 脚本“cmd /c echo EchoTest”,等待它终止并将结果放入 resultVar
1
2
3
4
5
6
7
8
9
10 <serviceTask id="shellEcho" activiti:type="shell" >
<extensionElements>
<activiti:field name="command" stringValue="cmd" />
<activiti:field name="arg1" stringValue="/c" />
<activiti:field name="arg2" stringValue="echo" />
<activiti:field name="arg3" stringValue="EchoTest" />
<activiti:field name="wait" stringValue="true" />
<activiti:field name="outputVariable" stringValue="resultVar" />
</extensionElements>
</serviceTask>
8.5.12。执行监听器
兼容性说明:发布 5.3 后,我们发现执行侦听器和任务侦听器和表达式仍然在非公共 API 中。这些类位于 的子包中org.activiti.engine.impl…
,其中包含impl
)。org.activiti.engine.impl.pvm.delegate.ExecutionListener
,org.activiti.engine.impl.pvm.delegate.TaskListener
并且org.activiti.engine.impl.pvm.el.Expression
已被弃用。从现在开始,您应该使用org.activiti.engine.delegate.ExecutionListener
,org.activiti.engine.delegate.TaskListener
和org.activiti.engine.delegate.Expression
。在新的公开 API 中,访问权限ExecutionListenerExecution.getEventSource()
已被删除。除了弃用编译器警告之外,现有代码应该可以正常运行。但考虑切换到新的公共 API 接口(包名称中没有 .impl.)。
执行侦听器允许您在流程执行期间发生某些事件时执行外部 Java 代码或评估表达式。可以捕获的事件有:
-
流程实例的开始和结束。
-
进行过渡。
-
活动的开始和结束。
-
网关的开始和结束。
-
中间事件的开始和结束。
-
结束开始事件或开始结束事件。
以下流程定义包含 3 个执行侦听器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 <process id="executionListenersProcess">
<extensionElements>
<activiti:executionListener class="org.activiti.examples.bpmn.executionlistener.ExampleExecutionListenerOne" event="start" />
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="firstTask" />
<userTask id="firstTask" />
<sequenceFlow sourceRef="firstTask" targetRef="secondTask">
<extensionElements>
<activiti:executionListener class="org.activiti.examples.bpmn.executionListener.ExampleExecutionListenerTwo" />
</extensionElements>
</sequenceFlow>
<userTask id="secondTask" >
<extensionElements>
<activiti:executionListener expression="${myPojo.myMethod(execution.event)}" event="end" />
</extensionElements>
</userTask>
<sequenceFlow sourceRef="secondTask" targetRef="thirdTask" />
<userTask id="thirdTask" />
<sequenceFlow sourceRef="thirdTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
当进程启动时,会通知第一个执行侦听器。侦听器是一个外部 Java 类(如ExampleExecutionListenerOne
),应该实现org.activiti.engine.delegate.ExecutionListener
接口。当事件发生时(在本例中为end
事件),该方法notify(ExecutionListenerExecution execution)
被调用。
1
2
3
4
5
6
7 public class ExampleExecutionListenerOne implements ExecutionListener {
public void notify(ExecutionListenerExecution execution) throws Exception {
execution.setVariable("variableSetInExecutionListener", "firstValue");
execution.setVariable("eventReceived", execution.getEventName());
}
}
也可以使用实现org.activiti.engine.delegate.JavaDelegate
接口的委托类。然后可以在其他构造中重用这些委托类,例如 serviceTask 的委托。
进行转换时调用第二个执行侦听器。请注意,该listener
元素没有定义event
,因为只有take
事件会在转换时触发。当在转换上定义侦听器时,属性中的值将被忽略。event
secondTask
活动结束时调用最后一个执行侦听器。不是使用class
on 监听器声明,expression
而是定义了 a ,它在事件被触发时被评估/调用。
1 <activiti:executionListener expression="${myPojo.myMethod(execution.eventName)}" event="end" />
与其他表达式一样,执行变量被解析并可以使用。因为执行实现对象具有公开事件名称的属性,所以可以使用 . 将事件名称传递给您的方法execution.eventName
。
执行侦听器也支持使用delegateExpression
,类似于服务任务。
1 <activiti:executionListener event="start" delegateExpression="${myExecutionListenerBean}" />
在 Activiti 5.12 中,我们还引入了一种新型的执行监听器,org.activiti.engine.impl.bpmn.listener.ScriptExecutionListener。此脚本执行侦听器允许您为执行侦听器事件执行一段脚本逻辑。
1
2
3
4
5
6
7
8
9
10
11
12 <activiti:executionListener event="start" class="org.activiti.engine.impl.bpmn.listener.ScriptExecutionListener" >
<activiti:field name="script">
<activiti:string>
def bar = "BAR"; // local variable
foo = "FOO"; // pushes variable to execution context
execution.setVariable("var1", "test"); // test access to execution instance
bar // implicit return value
</activiti:string>
</activiti:field>
<activiti:field name="language" stringValue="groovy" />
<activiti:field name="resultVariable" stringValue="myVar" />
</activiti:executionListener>
执行侦听器上的字段注入
使用配置了该class
属性的执行侦听器时,可以应用字段注入。这与使用的服务任务字段注入机制完全相同,其中包含对字段注入提供的可能性的概述。
下面的片段显示了一个简单的示例流程,其中包含一个注入字段的执行侦听器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 <process id="executionListenersProcess">
<extensionElements>
<activiti:executionListener class="org.activiti.examples.bpmn.executionListener.ExampleFieldInjectedExecutionListener" event="start">
<activiti:field name="fixedValue" stringValue="Yes, I am " />
<activiti:field name="dynamicValue" expression="${myVar}" />
</activiti:executionListener>
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="firstTask" />
<userTask id="firstTask" />
<sequenceFlow sourceRef="firstTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
1
2
3
4
5
6
7
8
9
10 public class ExampleFieldInjectedExecutionListener implements ExecutionListener {
private Expression fixedValue;
private Expression dynamicValue;
public void notify(ExecutionListenerExecution execution) throws Exception {
execution.setVariable("var", fixedValue.getValue(execution).toString() + dynamicValue.getValue(execution).toString());
}
}
该类ExampleFieldInjectedExecutionListener
连接了 2 个注入的字段(一个是固定的,另一个是动态的)并将其存储在 process 变量中var
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14 @Deployment(resources = {"org/activiti/examples/bpmn/executionListener/ExecutionListenersFieldInjectionProcess.bpmn20.xml"})
public void testExecutionListenerFieldInjection() {
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("myVar", "listening!");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("executionListenersProcess", variables);
Object varSetByListener = runtimeService.getVariable(processInstance.getId(), "var");
assertNotNull(varSetByListener);
assertTrue(varSetByListener instanceof String);
// Result is a concatenation of fixed injected field and injected expression
assertEquals("Yes, I am listening!", varSetByListener);
}
请注意,关于线程安全的相同规则适用于服务任务。请阅读相关部分以获取更多信息。
8.5.13。任务监听器
任务侦听器用于在发生特定任务相关事件时执行自定义 Java 逻辑或表达式。
任务侦听器只能作为用户任务的子元素添加到流程定义中。请注意,这也必须作为BPMN 2.0 extensionElements的子元素和在activiti命名空间中发生,因为任务侦听器是 Activiti 特定的构造。
1
2
3
4
5 <userTask id="myTask" name="My Task" >
<extensionElements>
<activiti:taskListener event="create" class="org.activiti.MyTaskCreateListener" />
</extensionElements>
</userTask>
任务监听器支持以下属性:
-
event(必需):将调用任务侦听器的任务事件的类型。可能的事件是
-
create:在创建任务并设置所有任务属性时发生。
-
assignment:当任务分配给某人时发生。注意:当流程执行到达 userTask 时,首先会触发assignment事件,然后触发create事件。这似乎是一个不自然的顺序,但原因是实用的:当接收到create事件时,我们通常希望检查任务的所有属性,包括受让人。
-
完成:在任务完成时以及从运行时数据中删除任务之前发生。
-
delete:在要删除任务之前发生。请注意,它也将在通过 completeTask 正常完成任务时执行。
-
-
class:必须调用的委托类。这个类必须实现
org.activiti.engine.delegate.TaskListener
接口。
1
2
3
4
5
6
7 public class MyTaskCreateListener implements TaskListener {
public void notify(DelegateTask delegateTask) {
// Custom logic goes here
}
}
也可以使用字段注入将流程变量或执行传递给委托类。请注意,委托类的实例是在流程部署时创建的(就像 Activiti 中的任何类委托一样),这意味着该实例在所有流程实例执行之间共享。
-
表达式:(不能与类属性一起使用):指定事件发生时将执行的表达式。可以将
DelegateTask
对象和事件名称(使用task.eventName
)作为参数传递给被调用对象。
1 <activiti:taskListener event="create" expression="${myObject.callMethod(task, task.eventName)}" />
-
delegateExpression允许指定解析为实现
TaskListener
接口的对象的表达式,类似于服务任务。
1 <activiti:taskListener event="create" delegateExpression="${myTaskListenerBean}" />
-
在 Activiti 5.12 中我们还引入了一种新型的任务监听器,org.activiti.engine.impl.bpmn.listener.ScriptTaskListener。此脚本任务侦听器允许您为任务侦听器事件执行一段脚本逻辑。
1
2
3
4
5
6
7
8
9
10
11
12 <activiti:taskListener event="complete" class="org.activiti.engine.impl.bpmn.listener.ScriptTaskListener" >
<activiti:field name="script">
<activiti:string>
def bar = "BAR"; // local variable
foo = "FOO"; // pushes variable to execution context
task.setOwner("kermit"); // test access to task instance
bar // implicit return value
</activiti:string>
</activiti:field>
<activiti:field name="language" stringValue="groovy" />
<activiti:field name="resultVariable" stringValue="myVar" />
</activiti:taskListener>
8.5.14。多实例(每个)
描述
多实例活动是一种为业务流程中的某个步骤定义重复的方式。在编程概念中,多实例与for each构造相匹配:它允许为给定集合中的每个项目按顺序或并行执行某个步骤甚至完整的子流程。
多实例是具有定义的额外属性(所谓的“多实例特征”)的常规活动,这将导致活动在运行时执行多次。以下活动可以成为多实例活动:
根据规范的要求,为每个实例创建的执行的每个父执行都将具有以下变量:
-
nrOfInstances : 实例总数
-
nrOfActiveInstances:当前活动的,即尚未完成的实例的数量。对于顺序多实例,这将始终为 1。
-
nrOfCompletedInstances:已完成实例的数量。
可以通过调用该execution.getVariable(x)
方法来检索这些值。
此外,每个创建的执行都将有一个执行局部变量(即对其他执行不可见,并且不存储在流程实例级别):
-
loopCounter:指示该特定实例的 for-each 循环中的索引。loopCounter 变量可以通过 Activiti elementIndexVariable属性重命名。
图形符号
如果一个活动是多实例的,则由该活动底部的三个短线表示。三条垂直线表示实例将并行执行,而三条水平线表示顺序执行。
xml 表示
要使活动多实例,活动 xml 元素必须有一个multiInstanceLoopCharacteristics
子元素。
1
2
3 <multiInstanceLoopCharacteristics isSequential="false|true">
...
</multiInstanceLoopCharacteristics>
isSequential属性指示该活动的实例是顺序执行还是并行执行。
实例数在进入活动时计算一次。有几种配置方法。On way 是通过使用loopCardinality子元素直接指定一个数字。
1
2
3 <multiInstanceLoopCharacteristics isSequential="false|true">
<loopCardinality>5</loopCardinality>
</multiInstanceLoopCharacteristics>
解析为正数的表达式也是可能的:
1
2
3 <multiInstanceLoopCharacteristics isSequential="false|true">
<loopCardinality>${nrOfOrders-nrOfCancellations}</loopCardinality>
</multiInstanceLoopCharacteristics>
定义实例数量的另一种方法是指定流程变量的名称,该流程变量是使用loopDataInputRef
子元素的集合。对于集合中的每个项目,将创建一个实例。inputDataItem
或者,可以使用子元素为实例设置集合的特定项。这显示在以下 XML 示例中:
1
2
3
4
5
6 <userTask id="miTasks" name="My Task ${loopCounter}" activiti:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false">
<loopDataInputRef>assigneeList</loopDataInputRef>
<inputDataItem name="assignee" />
</multiInstanceLoopCharacteristics>
</userTask>
假设变量assigneeList
包含值\[kermit, gonzo, fozzie\]
。在上面的代码片段中,将并行创建三个用户任务。每个执行都将有一个名为的流程变量assignee
,其中包含集合的一个值,在此示例中用于分配用户任务。
loopDataInputRef
and的缺点inputDataItem
是 1) 名称很难记住 2) 由于 BPMN 2.0 模式限制,它们不能包含表达式。Activiti 通过提供collection和elementVariable属性来解决这个问题multiInstanceCharacteristics
:
1
2
3
4
5 <userTask id="miTasks" name="My Task" activiti:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="true"
activiti:collection="${myService.resolveUsersForTask()}" activiti:elementVariable="assignee" >
</multiInstanceLoopCharacteristics>
</userTask>
当所有实例都完成时,多实例活动结束。但是,可以指定每次一个实例结束时计算的表达式。当这个表达式的计算结果为真时,所有剩余的实例都被销毁并且多实例活动结束,继续这个过程。这样的表达式必须在completionCondition子元素中定义。
1
2
3
4
5
6 <userTask id="miTasks" name="My Task" activiti:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false"
activiti:collection="assigneeList" activiti:elementVariable="assignee" >
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
在此示例中,将为assigneeList
集合的每个元素创建并行实例。但是,当完成 60% 的任务时,将删除其他任务并继续该过程。
边界事件和多实例
由于多实例是常规活动,因此可以在其边界上定义边界事件。在中断边界事件的情况下,当事件被捕获时,所有仍处于活动状态的实例都将被销毁。以下面的多实例子流程为例:
在这里,当计时器触发时,子流程的所有实例都将被销毁,无论有多少实例或当前尚未完成哪些内部活动。
多实例和执行侦听器
(适用于 Activiti 5.18 及更高版本)
将执行侦听器与多实例结合使用时需要注意。以下面的 BPMN 2.0 xml 片段为例,它定义在与设置multiInstanceLoopCharacteristics xml 元素相同的级别:
1
2
3
4 <extensionElements>
<activiti:executionListener event="start" class="org.activiti.MyStartListener"/>
<activiti:executionListener event="end" class="org.activiti.MyEndListener"/>
</extensionElements>
对于正常的 BPMN 活动,在活动开始和结束时会调用这些侦听器。
但是,当活动是多实例时,行为是不同的:
-
当进入多实例活动时,在执行任何内部活动之前,会抛出一个开始事件。loopCounter变量尚未设置(为空)。
-
对于访问的每个实际活动,都会引发一个开始事件。loopCounter变量已设置。
同样的推理也适用于结束事件:
-
当离开实际活动时,甚至抛出结束。loopCounter变量已设置。
-
当多实例活动作为一个整体完成时,将引发结束事件。未设置loopCounter变量。
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13 <subProcess id="subprocess1" name="Sub Process">
<extensionElements>
<activiti:executionListener event="start" class="org.activiti.MyStartListener"/>
<activiti:executionListener event="end" class="org.activiti.MyEndListener"/>
</extensionElements>
<multiInstanceLoopCharacteristics isSequential="false">
<loopDataInputRef>assignees</loopDataInputRef>
<inputDataItem name="assignee"></inputDataItem>
</multiInstanceLoopCharacteristics>
<startEvent id="startevent2" name="Start"></startEvent>
<endEvent id="endevent2" name="End"></endEvent>
<sequenceFlow id="flow3" name="" sourceRef="startevent2" targetRef="endevent2"></sequenceFlow>
</subProcess>
在此示例中,假设受理人列表包含三个项目。在运行时会发生以下情况:
-
为整个多实例引发启动事件。调用启动执行侦听器。不会设置loopCounter和assignee变量(即它们将为空)。
-
为每个活动实例引发一个开始事件。启动执行侦听器被调用了 3 次。将设置loopCounter和assignee变量(即不同于null)。
-
因此,总共调用了四次启动执行侦听器。
请注意,当multiInstanceLoopCharacteristics也被定义在子流程之外的其他东西上时,这同样适用。例如,如果上面的示例是一个简单的 userTask,同样的推理仍然适用。
8.5.15。补偿处理程序
描述
如果一个活动用于补偿另一个活动的影响,则可以将其声明为补偿处理程序。补偿处理程序不包含在正常流程中,仅在引发补偿事件时执行。
补偿处理程序不得有传入或传出序列流。
补偿处理程序必须使用定向关联与补偿边界事件相关联。
图形符号
如果活动是补偿处理程序,则补偿事件图标显示在中间底部区域。以下来自流程图的摘录显示了一个服务任务,该服务任务带有与补偿处理程序相关联的附加补偿边界事件。请注意“取消酒店预订”服务任务底部中心区域的补偿处理程序图标。
XML 表示
为了将活动声明为补偿处理程序,我们需要将属性 isForCompensation 设置为 true:
1
2 <serviceTask id="undoBookHotel" isForCompensation="true" activiti:class="...">
</serviceTask>
8.6. 子流程和调用活动
8.6.1. 子流程
描述
子流程是包含其他活动、网关、事件等的活动,这些活动本身形成一个流程,该流程是更大流程的一部分。子流程完全定义在父流程内部(这就是为什么它通常被称为嵌入式子流程)。
子流程有两个主要用例:
-
子流程允许分层建模。许多建模工具允许折叠子流程,隐藏子流程的所有细节并显示业务流程的高级端到端概览。
-
子流程为事件创建了一个新范围。在子流程执行期间抛出的事件可以被子流程边界上的边界事件捕获,从而为该事件创建一个仅限于子流程的范围。
使用子流程确实会施加一些限制:
-
一个子流程只能有一个无启动事件,不允许有其他启动事件类型。一个子流程必须至少有一个结束事件。请注意,BPMN 2.0 规范允许在子流程中省略开始和结束事件,但当前的 Activiti 实现不支持这一点。
-
序列流不能跨越子流程边界。
图形符号
子流程可视化为典型活动,即圆角矩形。如果 Sub-Process 被折叠,则仅显示名称和加号,从而对流程进行高级概述:
如果子流程展开,子流程的步骤将显示在子流程边界内:
使用子流程的主要原因之一是为特定事件定义范围。以下流程模型显示了这一点:调查软件/调查硬件任务都需要并行完成,但两个任务都需要在一定时间内完成,然后才咨询2 级支持。在这里,计时器的范围(即哪些活动必须及时完成)受子流程的约束。
XML 表示
子流程由子流程元素定义。作为子流程一部分的所有活动、网关、事件等都需要包含在此元素中。
1
2
3
4
5
6
7
8
9 <subProcess id="subProcess">
<startEvent id="subProcessStart" />
... other Sub-Process elements ...
<endEvent id="subProcessEnd" />
</subProcess>
8.6.2. 事件子流程
描述
事件子流程是 BPMN 2.0 中的新功能。事件子流程是由事件触发的子流程。可以在流程级别或任何子流程级别添加事件子流程。用于触发事件子流程的事件是使用启动事件配置的。由此可见,事件子流程不支持任何启动事件。可以使用消息事件、错误事件、信号事件、计时器事件或补偿事件等事件来触发事件子流程。当创建托管事件子流程的范围(流程实例或子流程)时,将创建对启动事件的订阅。当作用域被销毁时,订阅被删除。
事件子流程可能是中断的或非中断的。中断子进程会取消当前范围内的所有执行。一个不中断的事件子流程产生一个新的并发执行。虽然对于托管它的范围的每次激活只能触发一次中断事件子流程,但可以多次触发非中断事件子流程。子流程是否中断的事实是使用触发事件子流程的启动事件来配置的。
事件子流程不得有任何传入或传出的序列流。由于事件子流程是由事件触发的,因此传入的序列流没有意义。当一个事件子流程结束时,要么结束当前范围(在中断事件子流程的情况下),要么结束为非中断子流程产生的并发执行。
当前限制:
-
Activiti 只支持中断事件子流程。
-
Activiti 仅支持使用错误启动事件或消息启动事件触发的事件子流程。
XML 表示
事件子流程使用 XML 以与嵌入式子流程相同的方式表示。此外,该属性triggeredByEvent
必须具有值true
:
1
2
3 <subProcess id="eventSubProcess" triggeredByEvent="true">
...
</subProcess>
例子
以下是使用错误开始事件触发的事件子流程的示例。事件子流程位于“流程级别”,即范围为流程实例:
这就是事件子流程在 XML 中的样子:
1
2
3
4
5
6
7 <subProcess id="eventSubProcess" triggeredByEvent="true">
<startEvent id="catchError">
<errorEventDefinition errorRef="error" />
</startEvent>
<sequenceFlow id="flow2" sourceRef="catchError" targetRef="taskAfterErrorCatch" />
<userTask id="taskAfterErrorCatch" name="Provide additional data" />
</subProcess>
如前所述,事件子流程也可以添加到嵌入式子流程中。如果将其添加到嵌入的子流程中,它就会成为边界事件的替代方案。考虑以下两个流程图。在这两种情况下,嵌入式子流程都会引发错误事件。两次都使用用户任务捕获和处理错误。
相对于:
在这两种情况下,都会执行相同的任务。但是,两种建模方法之间存在差异:
-
嵌入式子流程使用执行它所在范围的相同执行来执行。这意味着嵌入式子流程可以访问其范围内的本地变量。当使用边界事件时,为执行嵌入的子流程而创建的执行会被离开边界事件的序列流删除。这意味着嵌入式子流程创建的变量不再可用。
-
使用事件子流程时,事件完全由添加它的子流程处理。使用边界事件时,该事件由父进程处理。
这两个差异可以帮助您确定是边界事件还是嵌入式子流程更适合解决特定流程建模/实现问题。
8.6.3. 交易子流程
描述
事务子流程是一个嵌入式子流程,可用于将多个活动分组到一个事务中。事务是一个逻辑工作单元,它允许对一组单独的活动进行分组,以便它们集体成功或失败。
交易的可能结果:交易可以有三种不同的结果:
-
一个事务是成功的,如果它既没有被取消也没有被危险终止。如果事务子流程成功,则使用传出的序列流留下它。如果在流程后期抛出补偿事件,则可能会补偿成功的事务。注意:就像“普通”嵌入式子流程一样,事务可以在成功完成后使用中间抛出补偿事件进行补偿。
-
如果执行到达取消结束事件,则取消事务。在这种情况下,所有执行都将终止并删除。然后将单个剩余执行设置为触发补偿的取消边界事件。补偿完成后,使用取消边界事件的传出序列流留下事务子流程。
-
一个事务由一个hazard结束,如果一个错误事件被抛出,那么这个事件不会在事务子流程的范围内被捕获。(如果错误是在事务子流程的边界上捕获的,这也适用。)在这种情况下,不执行补偿。
下图说明了三种不同的结果:
与 ACID 事务的关系:重要的是不要将 bpmn 事务子流程与技术 (ACID) 事务混淆。bpmn 事务子流程不是确定技术事务范围的方法。要了解 Activiti 中的事务管理,请阅读并发和事务部分。bpmn 交易与技术交易的不同之处在于:
-
虽然 ACID 事务通常是短暂的,但 bpmn 事务可能需要数小时、数天甚至数月才能完成。(考虑按事务分组的活动之一是用户任务的情况,通常人们的响应时间比应用程序长。或者,在另一种情况下,bpmn 事务可能会等待某些业务事件发生,例如特定订单已完成。)此类操作通常比更新数据库中的记录或使用事务队列存储消息要花费更长的时间。
-
因为不可能将技术事务限定为业务活动的持续时间,所以 bpmn 事务通常跨越多个 ACID 事务。
-
由于一个 bpmn 事务跨越多个 ACID 事务,我们失去了 ACID 属性。例如,考虑上面给出的示例。让我们假设“book hotel”和“charge credit card”操作在不同的 ACID 事务中执行。我们还假设“预订酒店”活动成功。现在我们有一个中介不一致的状态,因为我们已经执行了酒店预订但还没有从信用卡中扣款。现在,在 ACID 事务中,我们还将按顺序执行不同的操作,因此也会出现中间不一致的状态。这里不同的是,不一致的状态在事务范围之外是可见的。例如,如果使用外部预订服务进行预订,使用相同预订服务的其他方可能已经看到酒店已被预订。这意味着,在实现业务事务时,我们完全放松了隔离属性(当然:我们通常在处理 ACID 事务时也放松隔离以允许更高级别的并发性,但是我们有细粒度的控制,并且中间不一致只存在于很短的时间)。
-
一个bpmn业务事务也不能传统意义上的回滚。由于它跨越多个 ACID 事务,因此在取消 bpmn 事务时,其中一些 ACID 事务可能已经提交。在这一点上,它们不能再回滚了。
由于 bpmn 事务本质上是长时间运行的,因此需要以不同的方式处理缺乏隔离和回滚机制的问题。在实践中,通常没有比以特定领域的方式处理这些问题更好的解决方案:
-
使用补偿执行回滚。如果在事务范围内抛出取消事件,则所有成功执行并具有补偿处理程序的活动的影响都会得到补偿。
-
缺乏隔离也经常使用特定领域的解决方案来解决。例如,在上面的示例中,在我们实际确定第一个客户可以付款之前,酒店房间可能看起来是为第二个客户预订的。由于从业务角度来看这可能是不可取的,预订服务可能会选择允许一定数量的超额预订。
-
此外,由于在发生危险的情况下可以中止交易,因此预订服务必须处理预订了酒店房间但从未尝试付款的情况(因为交易被中止)。在这种情况下,预订服务可能会选择一种策略,即预订酒店房间的时间最长,如果在那之前没有收到付款,则取消预订。
总结一下:虽然 ACID 事务为此类问题(回滚、隔离级别和启发式结果)提供了通用解决方案,但在实现业务事务时,我们需要找到针对这些问题的特定领域解决方案。
当前限制:
-
BPMN 规范要求流程引擎对底层事务协议发出的事件做出反应,例如,如果在底层协议中发生取消事件,则取消事务。作为一个嵌入式引擎,Activiti 目前不支持这个。(有关这方面的一些后果,请参阅下面关于一致性的段落。)
ACID 事务和乐观并发之上的一致性:bpmn 事务保证了一致性,即所有活动都成功竞争,或者如果某些活动无法执行,则补偿所有其他成功活动的影响。所以无论哪种方式,我们最终都会处于一致的状态。但是,重要的是要认识到,在 Activiti 中,bpmn 事务的一致性模型叠加在流程执行的一致性模型之上。Activiti 以事务方式执行流程。并发使用乐观锁定来解决。在 Activiti 中,bpmn 错误、取消和补偿事件建立在相同的酸事务和乐观锁定之上。例如,取消结束事件只有在实际到达时才能触发补偿。如果服务任务之前抛出了一些未声明的异常,则不会到达。或者,如果底层 ACID 事务中的其他参与者将事务设置为仅回滚状态,则无法提交补偿处理程序的效果。或者,当两个并发执行到达取消结束事件时,补偿可能会被触发两次并因乐观锁定异常而失败。所有这一切都是说,在 Activiti 中实现 bpmn 事务时,应用与实现“普通”流程和子流程时相同的规则集。因此,为了有效地保证一致性,以一种考虑到乐观事务执行模型的方式实现流程非常重要。补偿可能会被触发两次并因乐观锁定异常而失败。所有这一切都是说,在 Activiti 中实现 bpmn 事务时,应用与实现“普通”流程和子流程时相同的规则集。因此,为了有效地保证一致性,以一种考虑到乐观事务执行模型的方式实现流程非常重要。补偿可能会被触发两次并因乐观锁定异常而失败。所有这一切都是说,在 Activiti 中实现 bpmn 事务时,应用与实现“普通”流程和子流程时相同的规则集。因此,为了有效地保证一致性,以一种考虑到乐观事务执行模型的方式实现流程非常重要。
XML 表示
事务子流程使用 xml transaction
标记表示:
1
2
3 <transaction id="myTransaction" >
...
</transaction>
例子
下面是一个事务子流程的例子:
8.6.4. 调用活动(子流程)
描述
BPMN 2.0 将常规子流程(通常也称为嵌入式子流程)和看起来非常相似的调用活动区分开来。从概念的角度来看,当流程执行到达活动时,两者都会调用子流程。
不同之处在于调用活动引用了流程定义外部的流程,而子流程嵌入在原始流程定义中。调用活动的主要用例是拥有一个可重用的流程定义,可以从多个其他流程定义中调用。
当流程执行到达调用活动时,会创建一个新的执行,它是到达调用活动的执行的子执行。这个子执行然后用于执行子进程,可能像在常规进程中一样创建并行子执行。超级执行等待子进程完全结束,然后继续原来的进程。
XML 表示
call 活动是一个常规活动,它需要一个被调用的元素,该元素通过它的key引用一个流程定义。实际上,这意味着在被调用元素中使用了进程的id。
1 <callActivity id="callCheckCreditProcess" name="Check credit" calledElement="checkCreditProcess" />
请注意,子流程的流程定义是在运行时解析的。这意味着如果需要,可以独立于调用进程部署子进程。
传递变量
您可以将流程变量传递给子流程,反之亦然。数据在启动时复制到子进程中,并在结束时复制回主进程中。
1
2
3
4
5
6 <callActivity id="callSubProcess" calledElement="checkCreditProcess" >
<extensionElements>
<activiti:in source="someVariableInMainProcess" target="nameOfVariableInSubProcess" />
<activiti:out source="someVariableInSubProcess" target="nameOfVariableInMainProcess" />
</extensionElements>
</callActivity>
我们使用 Activiti Extension 作为称为dataInputAssociation和dataOutputAssociation的 BPMN 标准元素的快捷方式,它们仅在您以 BPMN 2.0 标准方式声明流程变量时才有效。
这里也可以使用表达式:
1
2
3
4
5
6 <callActivity id="callSubProcess" calledElement="checkCreditProcess" >
<extensionElements>
<activiti:in sourceExpression="${x+5}" target="y" />
<activiti:out source="${y+5}" target="z" />
</extensionElements>
</callActivity>
所以最后 z = y+5 = x+5+5
callActivity 元素还支持使用自定义的 activiti 属性扩展在启动的子流程实例上设置业务键。businessKey属性可用于在子流程实例上设置自定义业务键值。
<callActivity id="callSubProcess" calledElement="checkCreditProcess" activiti:businessKey="${myVariable}"> ... </callActivity>
使用 true 值定义inheritBusinessKey属性会将子流程上的业务键值设置为调用流程中定义的业务键值。
<callActivity id="callSubProcess" calledElement="checkCreditProcess" activiti:inheritBusinessKey="true"> ... </callActivity>
例子
以下流程图显示了订单的简单处理。由于检查客户信用对于许多其他流程来说可能是通用的,因此此处将检查信用步骤建模为呼叫活动。
该过程如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13 <startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="receiveOrder" />
<manualTask id="receiveOrder" name="Receive Order" />
<sequenceFlow id="flow2" sourceRef="receiveOrder" targetRef="callCheckCreditProcess" />
<callActivity id="callCheckCreditProcess" name="Check credit" calledElement="checkCreditProcess" />
<sequenceFlow id="flow3" sourceRef="callCheckCreditProcess" targetRef="prepareAndShipTask" />
<userTask id="prepareAndShipTask" name="Prepare and Ship" />
<sequenceFlow id="flow4" sourceRef="prepareAndShipTask" targetRef="end" />
<endEvent id="end" />
子流程如下所示:
子流程的流程定义没有什么特别之处。它也可以在不被另一个进程调用的情况下使用。
8.7. 事务和并发
8.7.1。异步延续
Activiti 以交易方式执行流程,可以根据您的需要进行配置。让我们先来看看 Activiti 如何正常地定义事务范围。如果你触发了 Activiti(即启动一个进程,完成一个任务,发出执行信号),Activiti 将在进程中前进,直到它在每个活动的执行路径上达到等待状态。更具体地说,它通过进程图执行深度优先搜索,如果在每个执行分支上都达到等待状态,则返回。等待状态是“稍后”执行的任务,这意味着 Activiti 保持当前执行并等待再次触发。触发器可以来自外部源,例如,如果我们有用户任务或接收消息任务,也可以来自 Activiti 本身,如果我们有计时器事件。
我们看到一个 BPMN 流程的一部分,其中包含一个用户任务、一个服务任务和一个计时器事件。完成 usertask 和验证地址是同一工作单元的一部分,因此它应该自动成功或失败。这意味着如果服务任务抛出异常,我们希望回滚当前事务,以便执行回溯到用户任务并且用户任务仍然存在于数据库中。这也是 Activiti 的默认行为。在 (1) 中,应用程序或客户端线程完成任务。在同一个线程中,Activiti 现在正在执行服务并继续前进,直到它达到等待状态,在本例中为计时器事件 (2)。然后它将控制权返回给潜在地提交事务的调用者(3)(如果它是由 Activiti 启动的)。
在某些情况下,这不是我们想要的。有时我们需要对流程中的事务边界进行自定义控制,以便能够确定逻辑工作单元的范围。这就是异步延续发挥作用的地方。考虑以下过程(片段):
这次我们正在完成用户任务,生成发票,然后将该发票发送给客户。这次发票的生成不是同一工作单元的一部分,因此如果生成发票失败,我们不希望回滚用户任务的完成。所以我们希望 Activiti 做的是完成用户任务(1),提交事务并将控制权返回给调用应用程序。然后我们想在后台线程中异步生成发票。这个后台线程是 Activiti 作业执行器(实际上是一个线程池),它定期轮询数据库中的作业。所以在幕后,当我们到达“生成发票”任务时,我们正在为 Activiti 创建一个作业“消息”,以便稍后继续该过程并将其持久化到数据库中。然后,该作业由作业执行器拾取并执行。我们还给本地作业执行者一点提示,即有一个新作业,以提高性能。
为了使用这个特性,我们可以使用activiti:async="true"扩展。例如,服务任务如下所示:
1 <serviceTask id="service1" name="Generate Invoice" activiti:class="my.custom.Delegate" activiti:async="true" />
activiti:async可以在以下 BPMN 任务类型上指定:task、serviceTask、scriptTask、businessRuleTask、sendTask、receiveTask、userTask、subProcess、callActivity
在 userTask、receiveTask 或其他等待状态下,异步延续允许我们在单独的线程/事务中执行启动执行侦听器。
8.7.2. 失败重试
Activiti 在其默认配置中,会重新运行作业 3 次,以防在执行作业时出现任何异常。这也适用于异步任务作业。在某些情况下,需要更大的灵活性。有两个参数需要配置:
-
重试次数
-
重试之间的延迟 这些参数可以按
activiti:failedJobRetryTimeCycle
元素配置。这是一个示例用法:
1
2
3
4
5 <serviceTask id="failingServiceTask" activiti:async="true" activiti:class="org.activiti.engine.test.jobexecutor.RetryFailingDelegate">
<extensionElements>
<activiti:failedJobRetryTimeCycle>R5/PT7M</activiti:failedJobRetryTimeCycle>
</extensionElements>
</serviceTask>
时间循环表达式遵循 ISO 8601 标准,就像计时器事件表达式一样。上面的示例使作业执行者重试作业 5 次,并在每次重试之前等待 7 分钟。
8.7.3. 独家工作
从 Activiti 5.9 开始,JobExecutor 确保来自单个流程实例的作业永远不会同时执行。为什么是这样?
为什么要独家乔布斯?
考虑以下流程定义:
我们有一个并行网关,后面跟着三个服务任务,它们都执行异步延续。结果,三个作业被添加到数据库中。一旦这样的作业出现在数据库中,它就可以由 JobExecutor 处理。JobExecutor 获取作业并将它们委托给实际处理作业的工作线程的线程池。这意味着使用异步延续,您可以将工作分配到此线程池(在集群场景中甚至跨集群中的多个线程池)。这通常是一件好事。然而,它也存在一个固有的问题:一致性。考虑服务任务之后的并行连接。当一个服务任务的执行完成后,我们到达了并行连接,需要决定是等待其他的执行还是可以继续前进。
为什么这是个问题?由于服务任务是使用异步延续来配置的,因此可能会同时获取相应的作业,并由 JobExecutor 委托给不同的工作线程。结果是执行服务的事务和 3 个单独执行到达并行连接的事务可能重叠。如果他们这样做,每个单独的事务将不会“看到”另一个事务同时到达同一个并行连接,因此假设它必须等待其他事务。但是,如果每个事务都假设它必须等待其他事务,则在并行连接之后没有一个事务将继续该流程,并且流程实例将永远保持该状态。
Activiti 如何解决这个问题?Activiti 执行乐观锁定。每当我们根据可能不是最新的数据做出决定时(因为另一个事务可能会在我们提交之前修改它,我们确保在两个事务中增加同一数据库行的版本)。这样,无论哪个事务首先提交都会获胜,而其他事务会因乐观锁定异常而失败。这解决了上面讨论的流程的问题:如果多个执行同时到达并行连接,它们都假设它们必须等待,增加其父执行(流程实例)的版本,然后尝试提交。无论哪个执行首先将能够提交,而其他执行将因乐观锁定异常而失败。由于执行是由作业触发的,
这是一个好的解决方案吗?正如我们所见,乐观锁定允许 Activiti 防止不一致。它确保我们不会“一直卡在加入网关”,这意味着:所有执行都已通过网关,或者数据库中有作业确保我们重试通过它。然而,虽然从持久性和一致性的角度来看,这是一个完美的解决方案,但这可能并不总是在更高级别上是理想的行为:
-
Activiti 将仅重试相同的作业固定的最大次数(默认配置中为3次)。之后,该作业仍将存在于数据库中,但不再主动重试。这意味着操作员需要手动触发作业。
-
如果作业具有非事务性副作用,则失败的事务不会回滚这些副作用。例如,如果“预订音乐会门票”服务与 Activiti 不共享相同的事务,如果我们重试该作业,我们可能会预订多张门票。
什么是专属工作?
一个独占作业不能与来自同一流程实例的另一个独占作业同时执行。考虑上面的流程:如果我们声明服务任务是独占的,JobExecutor 会确保相应的作业不会并发执行。相反,它将确保每当它从某个流程实例获取独占作业时,它会从同一流程实例获取所有其他独占作业并将它们委托给同一个工作线程。这确保了作业的顺序执行。
如何启用此功能?从 Activiti 5.9 开始,独占作业是默认配置。因此,默认情况下,所有异步延续和计时器事件都是排他的。此外,如果您希望作业是非排他性的,您可以使用activiti:exclusive="false"
. 例如,以下服务任务将是异步的但非排他性的。
1 <serviceTask id="service" activiti:expression="${myService.performBooking(hotel, dates)}" activiti:async="true" activiti:exclusive="false" />
这是一个好的解决方案吗?我们有一些人问这是否是一个好的解决方案。他们担心的是,这会阻止您并行“做事”,因此会成为性能问题。再次,必须考虑两件事:
-
如果您是专家并且知道自己在做什么(并且已经理解了名为“为什么要独家工作?”的部分),则可以将其关闭。除此之外,对于大多数用户来说,如果像异步延续和计时器这样的东西可以正常工作,它会更直观。
-
这实际上不是性能问题。在重负载下,性能是一个问题。重负载意味着作业执行器的所有工作线程一直都很忙。通过独占作业,Activiti 将简单地以不同方式分配负载。独占作业意味着来自单个流程实例的作业由同一线程按顺序执行。但请考虑:您有不止一个流程实例。来自其他流程实例的作业被委托给其他线程并同时执行。这意味着对于独占作业,Activiti 不会同时执行来自同一个流程实例的作业,但它仍会同时执行多个实例。从整体吞吐量的角度来看,这在大多数情况下都是可取的,因为它通常会导致单个实例更快地完成。此外,执行同一流程实例的后续作业所需的数据将已经在执行集群节点的缓存中。如果作业没有此节点亲和性,则可能需要再次从数据库中获取该数据。
8.8. 进程启动授权
默认情况下,每个人都可以启动已部署流程定义的新流程实例。流程启动授权功能允许定义用户和组,以便 Web 客户端可以选择性地限制用户启动新流程实例。注意授权定义不会被 Activiti Engine 以任何方式验证。此功能仅用于开发人员简化 Web 客户端中授权规则的实施。该语法类似于用户任务的用户分配语法。可以使用 <activiti:potentialStarter> 标签将用户或组指定为进程的潜在发起者。这是一个例子:
1
2
3
4
5
6
7
8
9
10
11 <process id="potentialStarter">
<extensionElements>
<activiti:potentialStarter>
<resourceAssignmentExpression>
<formalExpression>group2, group(group3), user(user3)</formalExpression>
</resourceAssignmentExpression>
</activiti:potentialStarter>
</extensionElements>
<startEvent id="theStart"/>
...
在上面的 xml 摘录中,user(user3) 直接指用户 user3,group(group3) 指组 group3。没有指标会默认为组类型。也可以使用 <process> 标签的属性,即 <activiti:candidateStarterUsers> 和 <activiti:candidateStarterGroups>。这是一个例子:
1
2
3 <process id="potentialStarter" activiti:candidateStarterUsers="user1, user2"
activiti:candidateStarterGroups="group1">
...
可以同时使用这两个属性。
在定义了流程启动授权之后,开发者可以使用以下方法来检索授权定义。此代码检索可以由给定用户启动的流程定义列表:
1 processDefinitions = repositoryService.createProcessDefinitionQuery().startableByUser("userxxx").list();
还可以检索被定义为特定流程定义的潜在启动器的所有身份链接
1 identityLinks = repositoryService.getIdentityLinksForProcessDefinition("processDefinitionId");
以下示例显示了如何获取可以启动给定进程的用户列表:
1 List<User> authorizedUsers = identityService().createUserQuery().potentialStarter("processDefinitionId").list();
完全相同的方式,可以检索配置为给定流程定义的潜在启动器的组列表:
1 List<Group> authorizedGroups = identityService().createGroupQuery().potentialStarter("processDefinitionId").list();
8.9。数据对象
BPMN 提供了将数据对象定义为流程或子流程元素的一部分的可能性。根据 BPMN 规范,可以包含可能从 XSD 定义中导入的复杂 XML 结构。作为在 Activiti 中支持数据对象的第一个开始,支持以下 XSD 类型:
1
2
3
4
5
6 <dataObject id="dObj1" name="StringTest" itemSubjectRef="xsd:string"/>
<dataObject id="dObj2" name="BooleanTest" itemSubjectRef="xsd:boolean"/>
<dataObject id="dObj3" name="DateTest" itemSubjectRef="xsd:datetime"/>
<dataObject id="dObj4" name="DoubleTest" itemSubjectRef="xsd:double"/>
<dataObject id="dObj5" name="IntegerTest" itemSubjectRef="xsd:int"/>
<dataObject id="dObj6" name="LongTest" itemSubjectRef="xsd:long"/>
使用name属性值作为新变量的名称,数据对象定义将自动转换为流程变量。除了数据对象的定义,Activiti 还提供了一个扩展元素来为变量分配一个默认值。以下 BPMN 片段提供了一个示例:
1
2
3
4
5
6
7 <process id="dataObjectScope" name="Data Object Scope" isExecutable="true">
<dataObject id="dObj123" name="StringTest123" itemSubjectRef="xsd:string">
<extensionElements>
<activiti:value>Testing123</activiti:value>
</extensionElements>
</dataObject>
...
9. 表格
Activiti 提供了一种方便灵活的方式来为您的业务流程的手动步骤添加表单。我们支持两种处理表单的策略:带有表单属性的内置表单渲染和外部表单渲染。
9.1。表单属性
与业务流程相关的所有信息要么包含在流程变量本身中,要么通过流程变量引用。Activiti 支持将复杂的 Java 对象存储为过程变量,如Serializable
对象、JPA 实体或整个 XML 文档String
。
启动流程和完成用户任务是人们参与流程的地方。与人交流需要在某些 UI 技术中呈现表单。为了方便多种 UI 技术,流程定义可以包括将流程变量中的复杂 Java 类型对象转换Map<String,String>
为属性的逻辑。
然后,任何 UI 技术都可以使用公开属性信息的 Activiti API 方法在这些属性之上构建表单。这些属性可以提供关于流程变量的专用(和更有限的)视图。显示表单所需的属性在FormData返回值中可用,例如
1 StartFormData FormService.getStartFormData(String processDefinitionId)
或者
1 TaskFormdata FormService.getTaskFormData(String taskId)
默认情况下,内置表单引擎会看到属性以及流程变量。因此,如果它们与流程变量匹配 1-1,则无需声明任务表单属性。例如,使用以下声明:
1 <startEvent id="start" />
当执行到达 startEvent 时,所有流程变量都可用,但
1 formService.getStartFormData(String processDefinitionId).getFormProperties()
将是空的,因为没有定义特定的映射。
在上述情况下,所有提交的属性都将存储为流程变量。这意味着只需在表单中添加一个新的输入字段,就可以存储一个新的变量。
属性派生自流程变量,但它们不必存储为流程变量。例如,流程变量可以是地址类的 JPA 实体。UI技术使用的表单属性StreetName
可以与表达式链接#{address.street}
类似地,用户应该在表单中提交的属性可以存储为流程变量,也可以存储为流程变量之一中的嵌套属性,其中一个 UEL 值表达式,例如#{address.street}
。
类似地,提交的属性的默认行为是将它们存储为流程变量,除非formProperty
声明另有说明。
类型转换也可以作为表单属性和流程变量之间处理的一部分应用。
例如:
1
2
3
4
5
6
7
8 <userTask id="task">
<extensionElements>
<activiti:formProperty id="room" />
<activiti:formProperty id="duration" type="long"/>
<activiti:formProperty id="speaker" variable="SpeakerName" writable="false" />
<activiti:formProperty id="street" expression="#{address.street}" required="true" />
</extensionElements>
</userTask>
-
表单属性
room
将room
作为字符串映射到流程变量 -
表单属性
duration
将duration
作为 java.lang.Long映射到流程变量 -
表单属性
speaker
将映射到流程变量SpeakerName
。它仅在 TaskFormData 对象中可用。如果提交了属性 speaker,将抛出 ActivitiException。类似地,使用 attributereadable="false"
,可以从 FormData 中排除属性,但仍会在提交中进行处理。 -
表单属性
street
将作为字符串映射到street
流程变量中的 Java bean 属性。address
如果未提供该属性,则 required="true" 将在提交期间抛出异常。
也可以提供类型元数据作为从方法返回的 FormData 的一部分StartFormData FormService.getStartFormData(String processDefinitionId)
,TaskFormdata FormService.getTaskFormData(String taskId)
我们支持以下表单属性类型:
-
string
(org.activiti.engine.impl.form.StringFormType -
long
(org.activiti.engine.impl.form.LongFormType) -
enum
(org.activiti.engine.impl.form.EnumFormType) -
date
(org.activiti.engine.impl.form.DateFormType) -
boolean
(org.activiti.engine.impl.form.BooleanFormType)
对于声明的每个表单属性,以下FormProperty
信息将通过List<FormProperty> formService.getStartFormData(String processDefinitionId).getFormProperties()
和List<FormProperty> formService.getTaskFormData(String taskId).getFormProperties()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 public interface FormProperty {
/** the key used to submit the property in {@link FormService#submitStartFormData(String, java.util.Map)}
* or {@link FormService#submitTaskFormData(String, java.util.Map)} */
String getId();
/** the display label */
String getName();
/** one of the types defined in this interface like e.g. {@link #TYPE_STRING} */
FormType getType();
/** optional value that should be used to display in this property */
String getValue();
/** is this property read to be displayed in the form and made accessible with the methods
* {@link FormService#getStartFormData(String)} and {@link FormService#getTaskFormData(String)}. */
boolean isReadable();
/** is this property expected when a user submits the form? */
boolean isWritable();
/** is this property a required input field */
boolean isRequired();
}
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 <startEvent id="start">
<extensionElements>
<activiti:formProperty id="speaker"
name="Speaker"
variable="SpeakerName"
type="string" />
<activiti:formProperty id="start"
type="date"
datePattern="dd-MMM-yyyy" />
<activiti:formProperty id="direction" type="enum">
<activiti:value id="left" name="Go Left" />
<activiti:value id="right" name="Go Right" />
<activiti:value id="up" name="Go Up" />
<activiti:value id="down" name="Go Down" />
</activiti:formProperty>
</extensionElements>
</startEvent>
所有这些信息都可以通过 API 访问。可以使用 获取类型名称 formProperty.getType().getName()
。甚至可以使用日期模式,并且可以使用formProperty.getType().getInformation("datePattern")
枚举值访问formProperty.getType().getInformation("values")
Activiti explorer 支持表单属性并且会根据表单定义渲染表单。以下 XML 片段
1
2
3
4
5
6
7 <startEvent>
<extensionElements>
<activiti:formProperty id="numberOfDays" name="Number of days" value="${numberOfDays}" type="long" required="true"/>
<activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" value="${startDate}" datePattern="dd-MM-yyyy hh:mm" type="date" required="true" />
<activiti:formProperty id="vacationMotivation" name="Motivation" value="${vacationMotivation}" type="string" />
</extensionElements>
</userTask>
在 Activiti Explorer 中使用时将呈现为进程启动表单
9.2. 外部表单渲染
API 还允许您在 Activiti 引擎之外执行您自己的任务表单渲染。这些步骤解释了您可以用来自己呈现任务表单的钩子。
本质上,呈现表单所需的所有数据都组装在以下两种服务方法之一中:StartFormData FormService.getStartFormData(String processDefinitionId)
和TaskFormdata FormService.getTaskFormData(String taskId)
.
提交表单属性可以使用ProcessInstance FormService.submitStartFormData(String processDefinitionId, Map<String,String> properties)
和void FormService.submitTaskFormData(String taskId, Map<String,String> properties)
要了解表单属性如何映射到流程变量,请参阅表单属性
您可以将任何表单模板资源放置在您部署的业务档案中(以防您希望将它们与流程一起存储)。它将作为部署中的资源提供,您可以使用以下方法检索它:String ProcessDefinition.getDeploymentId()
这InputStream RepositoryService.getResourceAsStream(String deploymentId, String resourceName);
可能是您的模板定义文件,您可以使用该文件在您自己的应用程序中呈现/显示表单。
您也可以将这种访问部署资源的功能用于任务表单之外的任何其他目的。
该属性<userTask activiti:formKey="…"
由 API 通过String FormService.getStartFormData(String processDefinitionId).getFormKey()
和公开String FormService.getTaskFormData(String taskId).getFormKey()
。您可以使用它在部署中存储模板的全名(例如org/activiti/example/form/my-custom-form.xml
),但这根本不是必需的。例如,您还可以在表单属性中存储通用键并应用算法或转换来获取需要使用的实际模板。当您想为不同的 UI 技术呈现不同的表单时,这可能会很方便,例如一种用于正常屏幕尺寸的 Web 应用程序的表单,一种用于手机小屏幕的表单,甚至可能是 IM 表单或电子邮件表单的模板.
10. JPA
您可以使用 JPA-Entities 作为流程变量,允许您:
-
根据可以在 userTask 的表单中填写或在 serviceTask 中生成的流程变量更新现有的 JPA 实体。
-
重用现有领域模型,无需编写显式服务来获取实体和更新值
-
根据现有实体的属性做出决策(网关)。
-
…
10.1。要求
仅支持符合以下条件的实体:
-
实体应使用 JPA 注释进行配置,我们支持字段和属性访问。也可以使用映射的超类。
-
实体应该有一个用 注释的
@Id
主键,不支持复合主键(@EmbeddedId
和@IdClass
)。Id 字段/属性可以是 JPA 规范中支持的任何类型:原始类型及其包装器(不包括布尔值)String
、、、、和。BigInteger
BigDecimal
java.util.Date
java.sql.Date
10.2. 配置
为了能够使用 JPA 实体,引擎必须具有对EntityManagerFactory
. 这可以通过配置引用或提供持久性单元名称来完成。用作变量的 JPA 实体将被自动检测并进行相应处理。
下面的示例配置使用 jpaPersistenceUnitName:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 <bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">
<!-- Database configurations -->
<property name="databaseSchemaUpdate" value="true" />
<property name="jdbcUrl" value="jdbc:h2:mem:JpaVariableTest;DB_CLOSE_DELAY=1000" />
<property name="jpaPersistenceUnitName" value="activiti-jpa-pu" />
<property name="jpaHandleTransaction" value="true" />
<property name="jpaCloseEntityManager" value="true" />
<!-- job executor configurations -->
<property name="jobExecutorActivate" value="false" />
<!-- mail server configurations -->
<property name="mailServerPort" value="5025" />
</bean>
下面的下一个示例配置提供了一个EntityManagerFactory
我们自己定义的配置(在本例中,是一个 open-jpa 实体管理器)。请注意,该片段仅包含与示例相关的 bean,其他的被省略。open-jpa 实体管理器的完整工作示例可以在 activiti-spring-examples ( /activiti-spring/src/test/java/org/activiti/spring/test/jpa/JPASpringTest.java
)中找到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitManager" ref="pum"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter">
<property name="databasePlatform" value="org.apache.openjpa.jdbc.sql.H2Dictionary" />
</bean>
</property>
</bean>
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseSchemaUpdate" value="true" />
<property name="jpaEntityManagerFactory" ref="entityManagerFactory" />
<property name="jpaHandleTransaction" value="true" />
<property name="jpaCloseEntityManager" value="true" />
<property name="jobExecutorActivate" value="false" />
</bean>
以编程方式构建引擎时也可以进行相同的配置,例如:
1
2
3
4 ProcessEngine processEngine = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResourceDefault()
.setJpaPersistenceUnitName("activiti-pu")
.buildProcessEngine();
配置属性:
-
jpaPersistenceUnitName
: 要使用的持久性单元的名称。(确保持久性单元在类路径中可用。根据规范,默认位置是/META-INF/persistence.xml
)。使用jpaEntityManagerFactory
或jpaPersistenceUnitName
。 -
jpaEntityManagerFactory
:对 bean 实现的引用javax.persistence.EntityManagerFactory
,将用于加载实体和刷新更新。使用jpaEntityManagerFactory或jpaPersistenceUnitName。 -
jpaHandleTransaction
: 指示引擎应该开始并在使用的EntityManager实例上提交/回滚事务的标志。使用Java 事务 API (JTA)时设置为 false 。 -
jpaCloseEntityManager
: 指示引擎应该关闭EntityManager
从EntityManagerFactory
. 当EntityManager由容器管理时设置为 false (例如,当使用未限定为单个事务的扩展持久性上下文时)。
10.3. 用法
10.3.1. 简单示例
使用 JPA 变量的示例可以在 Activiti 源代码中的 JPAVariableTest 中找到。我们将JPAVariableTest.testUpdateJPAEntityValues
逐步解释。
首先,我们为持久化单元创建一个EntityManagerFactory,它基于META-INF/persistence.xml
. 这包含应该包含在持久性单元中的类和一些特定于供应商的配置。
我们在测试中使用了一个简单的实体,它有一个 id 和String
value 属性,它也是持久的。在运行测试之前,我们创建一个实体并保存它。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 @Entity(name = "JPA_ENTITY_FIELD")
public class FieldAccessJPAEntity {
@Id
@Column(name = "ID_")
private Long id;
private String value;
public FieldAccessJPAEntity() {
// Empty constructor needed for JPA
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
我们启动一个新的流程实例,将实体添加为变量。与其他变量一样,它们存储在引擎的持久存储中。下次请求该变量时,将EntityManager
根据类和存储的 Id 从加载。
1
2
3
4 Map<String, Object> variables = new HashMap<String, Object>();
variables.put("entityToUpdate", entityToUpdate);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("UpdateJPAValuesProcess", variables);
我们流程定义中的第一个节点包含一个serviceTask
将调用setValue
on方法的方法entityToUpdate
,该方法解析为我们之前在启动流程实例时设置的 JPA 变量,并将从EntityManager
与当前引擎的上下文关联的关联中加载。
1
2 <serviceTask id='theTask' name='updateJPAEntityTask'
activiti:expression="${entityToUpdate.setValue('updatedValue')}" />
当服务任务完成时,流程实例在流程定义中定义的 userTask 中等待,这允许我们检查流程实例。此时,EntityManager
已刷新,并且对实体的更改已推送到数据库。当我们获得变量 的值时entityToUpdate
,它会再次加载,我们会获得value
属性设置为的实体updatedValue
。
1
2
3
4 // Servicetask in process 'UpdateJPAValuesProcess' should have set value on entityToUpdate.
Object updatedEntity = runtimeService.getVariable(processInstance.getId(), "entityToUpdate");
assertTrue(updatedEntity instanceof FieldAccessJPAEntity);
assertEquals("updatedValue", ((FieldAccessJPAEntity)updatedEntity).getValue());
10.3.2. 查询 JPA 流程变量
您可以查询具有特定 JPA 实体作为变量值的ProcessInstance
s 和s。请注意,只有JPA-Entities on和才支持。方法、、和不受支持,当 JPA 实体作为值传递时将抛出一个。Execution
variableValueEquals(name, entity)
ProcessInstanceQuery
ExecutionQuery
variableValueNotEquals
variableValueGreaterThan
variableValueGreaterThanOrEqual
variableValueLessThan
variableValueLessThanOrEqual
ActivitiException
1
2 ProcessInstance result = runtimeService.createProcessInstanceQuery()
.variableValueEquals("entityToQuery", entityToQuery).singleResult();
10.3.3. 使用 Spring bean 和 JPA 的高级示例
JPASpringTest
可以在 中找到更高级的示例activiti-spring-examples
。它描述了以下简单的用例:
-
一个现有的使用 JPA 实体的 Spring-bean 已经存在,它允许存储贷款请求。
-
使用 Activiti,我们可以使用现有的实体,通过现有的 bean 获得,并将它们用作我们流程中的变量。流程定义为以下步骤:
-
使用启动流程时收到的现有 using 变量创建新 LoanRequest 的服务任务
LoanRequestBean
(例如,可能来自启动表单)。创建的实体存储为变量,使用activiti:resultVariable
该变量将表达式结果存储为变量。 -
允许经理审查请求和批准/拒绝的 UserTask,它存储为布尔变量
approvedByManager
-
ServiceTask 更新贷款请求实体,使实体与流程同步。
-
根据实体属性的值
approved
,使用独占网关来决定下一步要走什么路径:当请求被批准时,流程结束,否则,将有一个额外的任务可用(发送拒绝信),所以可以通过拒绝信手动通知客户。
-
请注意,该过程不包含任何表格,因为它仅用于单元测试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 <?xml version="1.0" encoding="UTF-8"?>
<definitions id="taskAssigneeExample"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="org.activiti.examples">
<process id="LoanRequestProcess" name="Process creating and handling loan request">
<startEvent id='theStart' />
<sequenceFlow id='flow1' sourceRef='theStart' targetRef='createLoanRequest' />
<serviceTask id='createLoanRequest' name='Create loan request'
activiti:expression="${loanRequestBean.newLoanRequest(customerName, amount)}"
activiti:resultVariable="loanRequest"/>
<sequenceFlow id='flow2' sourceRef='createLoanRequest' targetRef='approveTask' />
<userTask id="approveTask" name="Approve request" />
<sequenceFlow id='flow3' sourceRef='approveTask' targetRef='approveOrDissaprove' />
<serviceTask id='approveOrDissaprove' name='Store decision'
activiti:expression="${loanRequest.setApproved(approvedByManager)}" />
<sequenceFlow id='flow4' sourceRef='approveOrDissaprove' targetRef='exclusiveGw' />
<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway approval" />
<sequenceFlow id="endFlow1" sourceRef="exclusiveGw" targetRef="theEnd">
<conditionExpression xsi:type="tFormalExpression">${loanRequest.approved}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="endFlow2" sourceRef="exclusiveGw" targetRef="sendRejectionLetter">
<conditionExpression xsi:type="tFormalExpression">${!loanRequest.approved}</conditionExpression>
</sequenceFlow>
<userTask id="sendRejectionLetter" name="Send rejection letter" />
<sequenceFlow id='flow5' sourceRef='sendRejectionLetter' targetRef='theOtherEnd' />
<endEvent id='theEnd' />
<endEvent id='theOtherEnd' />
</process>
</definitions>
尽管上面的示例非常简单,但它显示了将 JPA 与 Spring 和参数化方法表达式结合使用的强大功能。该过程根本不需要自定义 java 代码(当然 Spring bean 除外)并大大加快了开发速度。
11. 历史
历史是捕获流程执行期间发生的事情并将其永久存储的组件。与运行时数据相比,历史数据在流程实例完成后仍将保留在数据库中。
有 5 个历史实体:
-
HistoricProcessInstance
s 包含有关当前和过去流程实例的信息。 -
HistoricVariableInstance
s 包含流程变量或任务变量的最新值。 -
HistoricActivityInstance
s 包含有关单个活动执行的信息(流程中的节点)。 -
HistoricTaskInstance
s 包含有关当前和过去(已完成和已删除)任务实例的信息。 -
HistoricDetail
s 包含与历史流程实例、活动实例或任务实例相关的各种信息。
由于数据库包含过去和正在进行的实例的历史实体,您可能需要考虑查询这些表,以最大限度地减少对运行时流程实例数据的访问,从而保持运行时执行的高性能。
稍后,这些信息将在 Activiti Explorer 中公开。此外,这将是生成报告的信息。
11.1。查询历史
在 API 中,可以查询所有 5 个历史实体。HistoryService 公开方法createHistoricProcessInstanceQuery()
、createHistoricVariableInstanceQuery()
、createHistoricActivityInstanceQuery()
和 。createHistoricDetailQuery()
createHistoricTaskInstanceQuery()
下面是几个示例,展示了历史查询 API 的一些可能性。可以在包中的javadocsorg.activiti.engine.history
中找到有关可能性的完整描述。
11.1.1. 历史进程实例查询
在定义为XXX的所有已完成进程中,获取 10 个HistoricProcessInstances
已完成且完成时间最长(最长持续时间)的进程。
1
2
3
4
5 historyService.createHistoricProcessInstanceQuery()
.finished()
.processDefinitionId("XXX")
.orderByProcessInstanceDuration().desc()
.listPage(0, 10);
11.1.2. 历史变量实例查询
从已完成的流程实例中获取所有HistoricVariableInstances
内容,其中 id xxx按变量名排序。
1
2
3
4 historyService.createHistoricVariableInstanceQuery()
.processInstanceId("XXX")
.orderByVariableName.desc()
.list();
11.1.3. 历史活动实例查询
获取已在使用 ID 为 XXX 的 processDefinition 的任何进程中完成的最后一个serviceTaskHistoricActivityInstance
类型。
1
2
3
4
5
6 historyService.createHistoricActivityInstanceQuery()
.activityType("serviceTask")
.processDefinitionId("XXX")
.finished()
.orderByHistoricActivityInstanceEndTime().desc()
.listPage(0, 1);
11.1.4. 历史细节查询
下一个示例,获取所有已在进程中完成的 id 为 123 的变量更新HistoricVariableUpdate
。此查询仅返回 s。HistoricVariableUpdate
请注意,每次在流程中更新变量时,某个变量名称可能有多个条目。您可以使用orderByTime
(变量更新完成的时间)或orderByVariableRevision
(更新时运行时变量的修订)来找出它们发生的顺序。
1
2
3
4
5 historyService.createHistoricDetailQuery()
.variableUpdates()
.processInstanceId("123")
.orderByVariableName().asc()
.list()
此示例获取在任何任务中提交的所有表单属性,或者在以 id "123" 启动进程时提交。此查询仅HistoricFormProperties
返回 s。
1
2
3
4
5 historyService.createHistoricDetailQuery()
.formProperties()
.processInstanceId("123")
.orderByVariableName().asc()
.list()
最后一个示例获取对 ID 为“123”的任务执行的所有变量更新。这将返回所有HistoricVariableUpdates
在任务上设置的变量(任务局部变量),而不是在流程实例上。
1
2
3
4
5 historyService.createHistoricDetailQuery()
.variableUpdates()
.taskId("123")
.orderByVariableName().asc()
.list()
任务局部变量可以使用, 内部的TaskService
or来设置:DelegateTask
TaskListener
1 taskService.setVariableLocal("123", "myVariable", "Variable value");
1
2
3 public void notify(DelegateTask delegateTask) {
delegateTask.setVariableLocal("myVariable", "Variable value");
}
11.1.5。历史任务实例查询
获取 10HistoricTaskInstance
秒完成的所有任务,其中完成时间最长(持续时间最长)。
1
2
3
4 historyService.createHistoricTaskInstanceQuery()
.finished()
.orderByHistoricTaskInstanceDuration().desc()
.listPage(0, 10);
获取HistoricTaskInstance
被删除的 s,删除原因包含“无效”,最后分配给用户kermit。
1
2
3
4
5 historyService.createHistoricTaskInstanceQuery()
.finished()
.taskDeleteReasonLike("%invalid%")
.taskAssignee("kermit")
.listPage(0, 10);
11.2. 历史配置
可以使用枚举 org.activiti.engine.impl.history.HistoryLevel (或5.11 之前的版本定义的HISTORY常量)以编程方式配置历史级别:ProcessEngineConfiguration
1
2
3
4 ProcessEngine processEngine = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResourceDefault()
.setHistory(HistoryLevel.AUDIT.getKey())
.buildProcessEngine();
也可以在 activiti.cfg.xml 或 spring-context 中配置级别:
1
2
3
4 <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">
<property name="history" value="audit" />
...
</bean>
可以配置以下历史级别:
-
none
: 跳过所有历史归档。这是运行时流程执行的最高性能,但没有可用的历史信息。 -
activity
:归档所有流程实例和活动实例。在流程实例结束时,顶级流程实例变量的最新值将被复制到历史变量实例中。不会存档任何详细信息。 -
audit
: 这是默认的。它归档所有流程实例、活动实例,保持变量值持续同步以及提交的所有表单属性,以便通过表单进行的所有用户交互都是可追溯的并且可以被审计。 -
full
:这是历史存档的最高级别,因此是最慢的。此级别存储级别中的所有信息audit
以及所有其他可能的详细信息,主要是流程变量更新。
在 Activiti 5.11 之前,历史级别存储在数据库中(表ACT_GE_PROPERTY
,带有名称的属性historyLevel
)。从 5.11 开始,不再使用此值,并从数据库中忽略/删除。现在可以在引擎的 2 次启动之间更改历史记录,如果级别从之前的引擎启动发生更改,则不会引发异常。
11.3. 用于审计目的的历史记录
配置时至少audit
要配置级别。然后通过方法提交的所有属性 FormService.submitStartFormData(String processDefinitionId, Map<String, String> properties)
并被FormService.submitTaskFormData(String taskId, Map<String, String> properties)
记录下来。
可以使用查询 API 检索表单属性,如下所示:
1
2
3
4
5 historyService
.createHistoricDetailQuery()
.formProperties()
...
.list();
HistoricFormProperty
在这种情况下,仅返回类型的历史详细信息。
如果您在调用提交方法之前设置了经过身份验证的用户,IdentityService.setAuthenticatedUserId(String)
那么提交表单的经过身份验证的用户将可以在历史记录中访问,也可以HistoricProcessInstance.getStartUserId()
用于启动表单和 HistoricActivityInstance.getAssignee()
任务表单。
12. Eclipse 设计器
Activiti 带有一个 Eclipse 插件,即 Activiti Eclipse Designer,可用于图形化建模、测试和部署 BPMN 2.0 流程。
12.1. 安装
以下安装说明在Eclipse Kepler 和 Indigo上进行了验证。请注意,不支持 Eclipse Helios。
转到帮助 → 安装新软件。在以下面板中,单击添加按钮并填写以下字段:
-
*姓名:*Activiti BPMN 2.0 设计师
-
*地点:*http://activiti.org/designer/update/
确保选中“联系所有更新站点..”复选框,因为 Eclipse 将下载所有必要的插件。
12.2. Activiti Designer 编辑器功能
-
创建 Activiti 项目和图表。
-
Activiti Designer 在创建新的 Activiti 图时会创建一个 .bpmn 文件。当使用 Activiti Diagram Editor 视图打开时,这将提供一个图形建模画布和调色板。但是,可以使用 XML 编辑器打开同一个文件,然后它会显示流程定义的 BPMN 2.0 XML 元素。因此,Activiti Designer 只使用一个文件用于图形图表和 BPMN 2.0 XML。请注意,在 Activiti 5.9 中,.bpmn 扩展名尚不支持作为流程定义的部署工件。因此,Activiti Designer 的“创建部署工件”特性会生成一个带有 .bpmn20.xml 文件的 BAR 文件,该文件包含 .bpmn 文件的内容。您也可以自己进行快速文件重命名。另请注意,您也可以使用 Activiti Diagram Editor 视图打开一个 .bpmn20.xml 文件。
-
可以将 BPMN 2.0 XML 文件导入 Activiti Designer 并创建图表。只需将 BPMN 2.0 XML 文件复制到您的项目并使用 Activiti Diagram Editor 视图打开该文件。Activiti Designer 使用文件的 BPMN DI 信息来创建图表。如果您有一个没有 BPMN DI 信息的 BPMN 2.0 XML 文件,则无法创建图表。
-
对于部署,Activiti Designer 通过右键单击包资源管理器中的 Activiti 项目并选择弹出菜单底部的Create deployment artifacts选项来创建 BAR 文件和可选的 JAR 文件。有关 Designer 部署功能的更多信息,请查看部署部分。
-
生成单元测试(在包资源管理器中右键单击 BPMN 2.0 XML 文件并选择生成单元测试)使用在嵌入式 H2 数据库上运行的 Activiti 配置生成单元测试。您现在可以运行单元测试来测试您的流程定义。
-
Activiti 项目作为 Maven 项目生成。要配置依赖项,您需要运行mvn eclipse:eclipse,Maven 依赖项将按预期进行配置。请注意,流程设计不需要 Maven 依赖项。它们只需要运行单元测试。
12.3. Activiti Designer BPMN 功能
-
支持启动无事件、启动错误事件、定时器启动事件、结束无事件、结束错误事件、顺序流、并行网关、独占网关、包含网关、事件网关、嵌入式子流程、事件子流程、调用活动、池、车道, 脚本任务, 用户任务, 服务任务, 邮件任务, 手动任务, 业务规则任务, 接收任务, 定时器边界事件, 错误边界事件, 信号边界事件, 定时器捕获事件, 信号捕获事件, 信号抛出事件, 无抛出事件和四个 Alfresco 特定元素(用户、脚本、邮件任务和启动事件)。
-
您可以通过将鼠标悬停在元素上并选择新的任务类型来快速更改任务的类型。
-
您可以快速添加新元素,将鼠标悬停在元素上并选择新元素类型。
-
Java 服务任务支持 Java 类、表达式或委托表达式配置。此外,可以配置字段扩展。
-
支持池和车道。因为 Activiti 将不同的池读取为不同的进程定义,所以只使用一个池是最有意义的。如果您使用多个池,请注意,在 Activiti Engine 中部署流程时,池之间的绘制顺序流将导致问题。您可以根据需要向池中添加尽可能多的车道。
-
您可以通过填充 name 属性向序列流添加标签。您可以自己定位标签,因为该位置保存为 BPMN 2.0 XML DI 信息的一部分。
-
支持事件子流程。
-
支持扩展的嵌入式子流程。您还可以在另一个嵌入式子流程中添加一个嵌入式子流程。
-
支持任务和嵌入式子流程上的计时器边界事件。虽然,定时器边界事件在用户任务或 Activiti Designer 中的嵌入式子流程上使用时最有意义。
-
支持额外的 Activiti 扩展,如邮件任务、用户任务的候选配置和脚本任务配置。
-
支持 Activiti 执行和任务监听器。您还可以为执行侦听器添加字段扩展。
-
支持序列流的条件。
12.4. Activiti Designer 部署特性
在 Activiti Engine 上部署流程定义和任务表单并不难。您需要一个 BAR 文件,其中包含流程定义 BPMN 2.0 XML 文件和可选的任务表单以及可以在 Activiti Explorer 中查看的流程图像。在 Activiti Designer 中,创建 BAR 文件变得非常容易。完成流程实施后,只需在包资源管理器中右键单击 Activiti 项目,然后选择弹出菜单底部的Create deployment artifacts选项。
然后创建一个部署目录,其中包含 BAR 文件和可选的 JAR 文件,其中包含 Activiti 项目的 Java 类。
现在可以使用 Activiti Explorer 中的部署选项卡将此文件上传到 Activiti 引擎,您就可以开始了。
当您的项目包含 Java 类时,部署工作会多一些。在这种情况下,Activiti Designer 中的Create deployment artifacts步骤也将生成一个包含已编译类的 JAR 文件。这个 JAR 文件必须部署到你的 Activiti Tomcat 安装目录中的 activiti-XXX/WEB-INF/lib 目录下。这使得这些类在 Activiti 引擎的类路径上可用。
12.5。扩展 Activiti Designer
您可以扩展 Activiti Designer 提供的默认功能。本节记录了哪些扩展可用,如何使用它们并提供一些使用示例。扩展 Activiti Designer 在默认功能不适合您的需要、您需要附加功能或在建模业务流程时具有特定领域要求的情况下很有用。Activiti Designer 的扩展分为两个不同的类别,扩展调色板和扩展输出格式。这些扩展方式中的每一种都需要特定的方法和不同的技术专长。
扩展 Activiti Designer 需要技术知识,更具体地说,需要 Java 编程知识。根据您要创建的扩展类型,您可能还需要熟悉 Maven、Eclipse、OSGi、Eclipse 扩展和 SWT。 |
12.5.1. 自定义调色板
您可以自定义在建模过程时提供给用户的调色板。调色板是可以在流程图中拖到画布上并显示在画布右侧的形状集合。正如您在默认调色板中看到的那样,默认形状被分组到用于事件、网关等的隔间(这些称为“抽屉”)中。Activiti Designer 内置了两个选项来自定义调色板中的抽屉和形状:
-
将您自己的形状/节点添加到现有或新抽屉中
-
禁用 Activiti Designer 提供的任何或所有默认 BPMN 2.0 形状,连接和选择工具除外
为了自定义调色板,您创建一个 JAR 文件,将其添加到 Activiti Designer 的特定安装中(稍后将详细介绍如何执行此操作)。这样的 JAR 文件称为扩展名。通过编写包含在您的扩展中的类,Activiti Designer 了解您希望进行哪些自定义。为了让它工作,你的类应该实现某些接口。有一个集成库可用于这些接口和基类进行扩展,您应该将其添加到项目的类路径中。
您可以在 Activiti Designer 的源代码控制中找到下面列出的代码示例。在Activiti源代码目录下examples/money-tasks
的目录下看一下。projects/designer
您可以使用您喜欢的任何工具设置项目,并使用您选择的构建工具构建 JAR。对于以下说明,假设使用 Eclipse Kepler 或 Indigo 进行设置,使用 Maven (3.x) 作为构建工具,但任何设置都应该使您能够创建相同的结果。 |
扩展设置(Eclipse/Maven)
下载并解压Eclipse(最新版本应该可以使用)和Apache Maven的最新版本 (3.x) 。如果您使用 2.x 版本的 Maven,在构建项目时会遇到问题,因此请确保您的版本是最新的。我们假设您熟悉在 Eclipse 中使用基本功能和 Java 编辑器。您是喜欢使用 Eclipse 的 Maven 功能还是从命令提示符运行 Maven 命令取决于您。
在 Eclipse 中创建一个新项目。这可以是一般项目类型。在项目的根目录创建一个pom.xml
文件以包含 Maven 项目设置。还要为src/main/java
和文件夹创建文件src/main/resources
夹,它们分别是 Java 源文件和资源的 Maven 约定。打开pom.xml
文件并添加以下行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 <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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.acme</groupId>
<artifactId>money-tasks</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Acme Corporation Money Tasks</name>
...
</project>
如您所见,这只是一个基本的 pom.xml 文件,它为项目定义了groupId
,artifactId
和version
。我们将为我们的货币业务创建一个包含单个自定义节点的自定义。
通过在文件中包含此依赖项,将集成库添加到项目的依赖项中pom.xml
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 <dependencies>
<dependency>
<groupId>org.activiti.designer</groupId>
<artifactId>org.activiti.designer.integration</artifactId>
<version>5.12.0</version> <!-- Use the current Activiti Designer version -->
<scope>compile</scope>
</dependency>
</dependencies>
...
<repositories>
<repository>
<id>Activiti</id>
<url>https://maven.alfresco.com/nexus/content/groups/public/</url>
</repository>
</repositories>
最后,在 pom.xml
文件中添加配置,maven-compiler-plugin
以便 Java 源级别至少为 1.5(请参见下面的代码段)。您将需要它才能使用注释。您还可以包含 Maven 生成 JARMANIFEST.MF
文件的说明。这不是必需的,但您可以使用清单中的特定属性为您的扩展提供名称(此名称可能会显示在设计器中的某些位置,如果您在设计器中有多个扩展,则主要用于将来使用) . 如果您希望这样做,请在中包含以下代码段pom.xml
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 <build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
<optimize>true</optimize>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<archive>
<index>true</index>
<manifest>
<addClasspath>false</addClasspath>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<ActivitiDesigner-Extension-Name>Acme Money</ActivitiDesigner-Extension-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
扩展名由ActivitiDesigner-Extension-Name
属性描述。现在唯一要做的就是告诉 Eclipse 根据pom.xml
. 因此,打开一个命令 shell 并转到 Eclipse 工作区中项目的根文件夹。然后执行以下 Maven 命令:
mvn 日食:日食
等待构建成功。刷新项目(使用项目的上下文菜单(右键单击)并选择Refresh
)。您现在应该将src/main/java
和src/main/resources
文件夹作为 Eclipse 项目中的源文件夹。
当然,您也可以使用m2eclipse插件,只需从项目的上下文菜单(右键单击)中启用 Maven 依赖项管理。然后从项目的上下文菜单中选择 |
这就是设置。现在您可以开始为 Activiti Designer 创建自定义了!
将扩展应用到 Activiti Designer
您可能想知道如何将您的扩展添加到 Activiti Designer 以便应用您的自定义。以下是执行此操作的步骤: * 一旦您创建了扩展 JAR(例如,通过在项目中执行 mvn install 以使用 Maven 构建它),您需要将扩展传输到 Activiti Designer 所在的计算机已安装;* 将扩展存储在硬盘驱动器上的某个位置,以便能够保留并记住该位置。注意:该位置必须在 Activiti Designer 的 Eclipse 工作区之外 - 将扩展存储在工作空间内会导致用户收到弹出错误消息并且扩展不可用;* 启动 Activiti Designer 并从菜单中选择Window
> Preferences
* 在首选项屏幕中,键入user
作为关键字。User Libraries
您应该会在 Eclipse部分中看到一个访问 Eclipse 的选项Java
。
-
选择 User Libraries 项目,右侧会显示一个树视图,您可以在其中添加库。您应该看到默认组,您可以在其中向 Activiti Designer 添加扩展(取决于您的 Eclipse 安装,您可能还会看到其他几个)。
-
选择
Activiti Designer Extensions
组并单击Add JARs…
按钮。导航到存储扩展的文件夹,然后选择要添加的扩展文件。完成此操作后,您的首选项屏幕应将扩展显示为Activiti Designer Extensions
组的一部分,如下所示。
-
单击
OK
按钮保存并关闭首选项对话框。该Activiti Designer Extensions
组会自动添加到您创建的新 Activiti 项目中。您可以在 Navigator 或 Package Explorer 中将用户库视为项目树中的条目。如果您已经在工作区中有 Activiti 项目,您还应该看到新的扩展出现在组中。一个例子如下所示。
您打开的图表现在将在其调色板中具有来自新扩展的形状(或禁用形状,具体取决于扩展中的自定义)。如果您已经打开了图表,请关闭并重新打开它以查看调色板中的更改。
将形状添加到调色板
设置好项目后,您现在可以轻松地将形状添加到调色板。您希望添加的每个形状都由 JAR 中的一个类表示。请注意,这些类不是 Activiti 引擎在运行时使用的类。在您的扩展中,您描述了可以在 Activiti Designer 中为每个形状设置的属性。从这些形状中,您还可以定义当流程实例到达流程中的节点时引擎应该使用的运行时特性。运行时特性可以使用 Activiti 为常规ServiceTask
s 支持的任何选项。有关详细信息,请参阅本节。
形状的类是一个简单的 Java 类,其中添加了许多注释。类应该实现CustomServiceTask
接口,但你不应该自己实现这个接口。而是扩展AbstractCustomServiceTask
基类(目前您必须直接扩展此类,因此两者之间没有抽象类)。在该类的 Javadoc 中,您可以找到有关它提供的默认值以及何时应该覆盖它已经实现的任何方法的说明。覆盖允许您做一些事情,例如为调色板和画布上的形状提供图标(这些可以不同)并指定您希望节点具有的基本形状(活动、事件、网关)。
1
2
3
4
5
6
7
8 /**
* @author John Doe
* @version 1
* @since 1.0.0
*/
public class AcmeMoneyTask extends AbstractCustomServiceTask {
...
}
您将需要实现该getName()
方法来确定节点在调色板中的名称。您还可以将节点放在自己的抽屉中并提供图标。覆盖 中的适当方法AbstractCustomServiceTask
。如果您想提供一个图标,请确保它src/main/resources
在您的 JAR 包中,并且大约为 16x16 像素和 JPEG 或 PNG 格式。您提供的路径是相对于该文件夹的。
您可以通过将成员添加到类并使用注释对它们进行注释来向形状添加属性,@Property
如下所示:
1
2
3 @Property(type = PropertyType.TEXT, displayName = "Account Number")
@Help(displayHelpShort = "Provide an account number", displayHelpLong = HELP_ACCOUNT_NUMBER_LONG)
private String accountNumber;
您可以使用几个值,本节PropertyType
将详细介绍这些值。您可以通过将 required 属性设置为 true 来创建必填字段。如果用户未填写该字段,则会出现一条消息和红色背景。
如果要确保类中各种属性在属性屏幕中出现的顺序,则应指定@Property
注释的 order 属性。
如您所见,还有一个@Help
注释用于在填写字段时为用户提供一些指导。您还可以@Help
在类本身上使用注释 - 此信息显示在呈现给用户的属性表的顶部。
下面是进一步阐述的清单MoneyTask
。添加了一个注释字段,您可以看到该节点包含一个图标。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 /**
* @author John Doe
* @version 1
* @since 1.0.0
*/
@Runtime(javaDelegateClass = "org.acme.runtime.AcmeMoneyJavaDelegation")
@Help(displayHelpShort = "Creates a new account", displayHelpLong = "Creates a new account using the account number specified")
public class AcmeMoneyTask extends AbstractCustomServiceTask {
private static final String HELP_ACCOUNT_NUMBER_LONG = "Provide a number that is suitable as an account number.";
@Property(type = PropertyType.TEXT, displayName = "Account Number", required = true)
@Help(displayHelpShort = "Provide an account number", displayHelpLong = HELP_ACCOUNT_NUMBER_LONG)
private String accountNumber;
@Property(type = PropertyType.MULTILINE_TEXT, displayName = "Comments")
@Help(displayHelpShort = "Provide comments", displayHelpLong = "You can add comments to the node to provide a brief description.")
private String comments;
/*
* (non-Javadoc)
*
* @see org.activiti.designer.integration.servicetask.AbstractCustomServiceTask #contributeToPaletteDrawer()
*/
@Override
public String contributeToPaletteDrawer() {
return "Acme Corporation";
}
@Override
public String getName() {
return "Money node";
}
/*
* (non-Javadoc)
*
* @see org.activiti.designer.integration.servicetask.AbstractCustomServiceTask #getSmallIconPath()
*/
@Override
public String getSmallIconPath() {
return "icons/coins.png";
}
}
如果你用这个形状扩展 Activiti Designer,调色板和相应的节点将如下所示:
金钱任务的属性屏幕如下所示。请注意该accountNumber
字段的必填信息。
创建图表时,用户可以在属性字段中输入静态文本或使用使用流程变量的表达式(例如“这只小猪去了${piggyLocation}”)。通常,这适用于用户可以自由输入任何文本的文本字段。如果您希望用户想要使用表达式并将运行时行为应用于您的CustomServiceTask
(using @Runtime
),请确保使用Expression
委托类中的字段,以便在运行时正确解析表达式。有关运行时行为的更多信息,请参见本节。
字段的帮助由每个属性右侧的按钮提供。单击该按钮会显示一个弹出窗口,如下所示。
配置自定义服务任务的运行时执行
将字段设置和扩展应用到 Designer 后,用户可以在对流程进行建模时配置服务任务的属性。在大多数情况下,当 Activiti 执行流程时,您会希望使用这些用户配置的属性。为此,您必须指示 Activiti 在进程到达您的CustomServiceTask
.
有一个特殊的注解用于指定你的运行时特性CustomServiceTask
,@Runtime
注解。以下是如何使用它的示例:
1 @Runtime(javaDelegateClass = "org.acme.runtime.AcmeMoneyJavaDelegation")
您CustomServiceTask
将导致ServiceTask
使用它建模的流程的 BPMN 输出正常。Activiti 支持多种方式来定义ServiceTask
s 的运行时特性。因此,@Runtime
注解可以采用三个属性之一,直接匹配 Activiti 提供的选项,如下所示:
-
javaDelegateClass
映射到activiti:class
BPMN 输出中。指定实现的类的完全限定类名JavaDelegate
。 -
expression
映射到activiti:expression
BPMN 输出中。指定要执行的方法的表达式,例如 Spring Bean 中的方法。使用此选项时,不应在字段上指定任何@Property
注释。有关更多信息,请参见下文。 -
javaDelegateExpression
映射到activiti:delegateExpression
BPMN 输出中。为实现 的类指定表达式JavaDelegate
。
如果您提供类中的成员供 Activiti 注入,用户的属性值将被注入到运行时类中。名称应与您的CustomServiceTask
. 有关更多信息,请参阅用户指南的这一部分。请注意,从 Designer 的 5.11.0 版开始,您可以使用该Expression
界面来获取动态字段值。这意味着 Activiti Designer 中的属性值必须包含一个表达式,然后这个表达式将被注入到实现类中的一个Expression
属性中。JavaDelegate
您可以 |
请注意,运行时类不应该在您的扩展 JAR 中,因为它依赖于 Activiti 库。Activiti 需要能够在运行时找到它,因此它需要位于 Activiti 引擎的类路径中。 |
Designer 源代码树中的示例项目包含配置不同选项的示例@Runtime
。查看 money-tasks 项目的一些起点。这些示例引用了 money-delegates 项目中的委托类示例。
属性类型
CustomServiceTask
本节介绍通过将其类型设置为值可用于 a 的属性类型PropertyType
。
属性类型.TEXT
创建一个单行文本字段,如下所示。可以是必填字段,并将验证消息显示为工具提示。通过将字段的背景更改为浅红色来显示验证失败。
PropertyType.MULTILINE_TEXT
创建如下所示的多行文本字段(高度固定为 80 像素)。可以是必填字段,并将验证消息显示为工具提示。通过将字段的背景更改为浅红色来显示验证失败。
属性类型.PERIOD
通过使用微调器控件编辑每个单位的数量,创建用于指定时间段的结构化编辑器。结果如下所示。可以是必填字段(被解释为并非所有值都可能为 0,因此期间的至少 1 部分必须具有非零值)并将验证消息显示为工具提示。通过将整个字段的背景更改为浅红色来显示验证失败。该字段的值存储为形式为 1y 2mo 3w 4d 5h 6m 7s 的字符串,表示 1 年 2 个月 3 周 4 天 6 分钟 7 秒。始终存储整个字符串,即使部分为 0。
PropertyType.BOOLEAN_CHOICE
为布尔或切换选项创建单个复选框控件。请注意,您可以required
在注释上指定属性Property
,但不会对其进行评估,因为这将使用户无法选择是否选中该框。图中存储的值为 java.lang.Boolean.toString(boolean),其结果为“true”或“false”。
PropertyType.RADIO_CHOICE
创建一组单选按钮,如下所示。选择任何单选按钮与选择任何其他单选按钮是互斥的(即,只允许选择一个)。可以是必填字段,并将验证消息显示为工具提示。通过将组的背景更改为浅红色来显示验证失败。
此属性类型期望您已注释的类成员也具有随附的@PropertyItems
注释(例如,请参见下文)。使用此附加注释,您可以指定应在字符串数组中提供的项目列表。通过为每个项目添加两个数组条目来指定项目:首先,要显示的标签;第二,要存储的值。
1
2
3
4 @Property(type = PropertyType.RADIO_CHOICE, displayName = "Withdrawl limit", required = true)
@Help(displayHelpShort = "The maximum daily withdrawl amount ", displayHelpLong = "Choose the maximum daily amount that can be withdrawn from the account.")
@PropertyItems({ LIMIT_LOW_LABEL, LIMIT_LOW_VALUE, LIMIT_MEDIUM_LABEL, LIMIT_MEDIUM_VALUE, LIMIT_HIGH_LABEL, LIMIT_HIGH_VALUE })
private String withdrawlLimit;
PropertyType.COMBOBOX_CHOICE
创建一个具有固定选项的组合框,如下所示。可以是必填字段,并将验证消息显示为工具提示。通过将组合框的背景更改为浅红色来显示验证失败。
此属性类型期望您已注释的类成员也具有随附的@PropertyItems
注释(例如,请参见下文)。使用此附加注释,您可以指定应在字符串数组中提供的项目列表。通过为每个项目添加两个数组条目来指定项目:首先,要显示的标签;第二,要存储的值。
1
2
3
4
5
6 @Property(type = PropertyType.COMBOBOX_CHOICE, displayName = "Account type", required = true)
@Help(displayHelpShort = "The type of account", displayHelpLong = "Choose a type of account from the list of options")
@PropertyItems({ ACCOUNT_TYPE_SAVINGS_LABEL, ACCOUNT_TYPE_SAVINGS_VALUE, ACCOUNT_TYPE_JUNIOR_LABEL, ACCOUNT_TYPE_JUNIOR_VALUE, ACCOUNT_TYPE_JOINT_LABEL,
ACCOUNT_TYPE_JOINT_VALUE, ACCOUNT_TYPE_TRANSACTIONAL_LABEL, ACCOUNT_TYPE_TRANSACTIONAL_VALUE, ACCOUNT_TYPE_STUDENT_LABEL, ACCOUNT_TYPE_STUDENT_VALUE,
ACCOUNT_TYPE_SENIOR_LABEL, ACCOUNT_TYPE_SENIOR_VALUE })
private String accountType;
PropertyType.DATE_PICKER
创建一个日期选择控件,如下所示。可以是必填字段,并将验证消息显示为工具提示(注意,使用的控件会将选择自动设置为系统上的日期,因此该值很少为空)。通过将控件的背景更改为浅红色来显示验证失败。
此属性类型期望您已注释的类成员也具有随附的@DatePickerProperty
注释(例如,请参见下文)。使用此附加注释,您可以指定用于在图表中存储日期的日期时间模式以及您希望显示的日期选择器的类型。这两个属性都是可选的,并且如果您不指定它们,将使用默认值(这些是DatePickerProperty
注释中的静态变量)。该dateTimePattern
属性应该用于为SimpleDateFormat
类提供模式。使用该属性时,您应该指定一个受的控件swtStyle
支持的整数值,因为这是用于呈现此类属性的控件。SWT
DateTime
1
2
3
4 @Property(type = PropertyType.DATE_PICKER, displayName = "Expiry date", required = true)
@Help(displayHelpShort = "The date the account expires ", displayHelpLong = "Choose the date when the account will expire if no extended before the date.")
@DatePickerProperty(dateTimePattern = "MM-dd-yyyy", swtStyle = 32)
private String expiryDate;
PropertyType.DATA_GRID
创建一个数据网格控件,如下所示。数据网格可用于允许用户输入任意数量的数据行,并为每行中的一组固定列输入值(行和列的每个单独组合称为一个单元格)。可以根据用户认为合适的方式添加和删除行。
此属性类型期望您已注释的类成员也具有随附的@DataGridProperty
注释(例如,请参见下文)。使用此附加注释,您可以指定数据网格的一些特定属性。您需要引用不同的类来确定哪些列进入具有该itemClass
属性的网格。Activiti Designer 期望成员类型为List
. 按照惯例,您可以使用itemClass
属性的类作为其泛型类型。例如,如果您有一个在网格中编辑的购物清单,您将在GroceryListItem
类中定义网格的列。从你的CustomServiceTask
,你会这样引用它:
1
2
3 @Property(type = PropertyType.DATA_GRID, displayName = "Grocery List")
@DataGridProperty(itemClass = GroceryListItem.class)
private List<GroceryListItem> groceryList;
“itemClass”类使用与指定 a 的字段相同的注释CustomServiceTask
,但使用数据网格除外。具体来说,TEXT
和MULTILINE_TEXT
当前PERIOD
受支持。您会注意到网格将为每个字段创建单行文本控件,而不管PropertyType
. 这样做是为了保持网格在图形上的吸引力和可读性。例如,如果您考虑常规显示模式PERIOD
PropertyType
,您可以想象它永远不会正确地适合网格单元格而不会使屏幕混乱。对于MULTILINE_TEXT
和PERIOD
,每个字段都添加了双击机制,为 . 弹出一个更大的编辑器PropertyType
。用户单击“确定”后,该值将存储到该字段中,因此可以在网格中读取。
所需属性的处理方式与常规类型字段类似,TEXT
并且一旦任何字段失去焦点,就会验证整个网格。如果验证失败,数据网格的特定单元格中的文本控件的背景颜色将变为浅红色。
默认情况下,该组件允许用户添加行,但不能确定这些行的顺序。如果您希望允许这样做,您应该将该orderable
属性设置为 true,这使得每行末尾的按钮能够在网格中向上或向下移动它。
目前,此属性类型未正确注入您的运行时类。 |
禁用调色板中的默认形状
此自定义要求您在实现DefaultPaletteCustomizer
接口的扩展中包含一个类。你不应该直接实现这个接口,而应该继承AbstractDefaultPaletteCustomizer
基类。目前,此类不提供任何功能,但DefaultPaletteCustomizer
接口的未来版本将提供更多功能,此基类将为这些功能提供一些合理的默认值,因此最好进行子类化,以便您的扩展与未来版本兼容。
扩展AbstractDefaultPaletteCustomizer
类需要您实现一个方法,disablePaletteEntries()
,您必须从该方法返回PaletteEntry
值列表。对于每个默认形状,您可以通过将其相应PaletteEntry
值添加到列表中来禁用它。请注意,如果您从默认设置中删除形状并且特定抽屉中没有剩余形状,则该抽屉将从调色板中全部删除。如果您希望禁用所有默认形状,您只需添加PaletteEntry.ALL
到结果中。例如,下面的代码禁用调色板中的手动任务和脚本任务形状。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 public class MyPaletteCustomizer extends AbstractDefaultPaletteCustomizer {
/*
* (non-Javadoc)
*
* @see org.activiti.designer.integration.palette.DefaultPaletteCustomizer#disablePaletteEntries()
*/
@Override
public List<PaletteEntry> disablePaletteEntries() {
List<PaletteEntry> result = new ArrayList<PaletteEntry>();
result.add(PaletteEntry.MANUAL_TASK);
result.add(PaletteEntry.SCRIPT_TASK);
return result;
}
}
应用此扩展的结果如下图所示。如您所见,Tasks
抽屉中不再提供手动任务和脚本任务形状。
要禁用所有默认形状,您可以使用类似于以下代码的内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 public class MyPaletteCustomizer extends AbstractDefaultPaletteCustomizer {
/*
* (non-Javadoc)
*
* @see org.activiti.designer.integration.palette.DefaultPaletteCustomizer#disablePaletteEntries()
*/
@Override
public List<PaletteEntry> disablePaletteEntries() {
List<PaletteEntry> result = new ArrayList<PaletteEntry>();
result.add(PaletteEntry.ALL);
return result;
}
}
结果将如下所示(请注意,默认形状所在的抽屉不再在调色板中):
12.5.2. 验证图表并导出为自定义输出格式
除了自定义调色板之外,您还可以创建 Activiti Designer 的扩展,它可以执行验证并将图表中的信息保存到 Eclipse 工作区中的自定义资源中。有用于执行此操作的内置扩展点,本节说明如何使用它们。
最近重新引入了 ExportMarshaller 函数。我们仍在研究验证功能。下面的文档详细说明了旧情况,并将在新功能可用时进行更新。 |
Activiti Designer 允许您编写验证图表的扩展。默认情况下,该工具中已经对 BPMN 构造进行了验证,但如果您想要验证其他项目(例如建模约定或 s 的属性中的值),可以添加自己的验证CustomServiceTask
。这些扩展称为Process Validators
.
您还可以在保存图表时将 Activiti Designer 发布为其他格式。这些扩展被Export Marshallers
Activiti Designer 在用户每次保存操作时自动调用和调用。通过在 Eclipse 的首选项对话框中为检测到扩展的每种格式设置首选项,可以启用或禁用此行为。Designer 将确保您ExportMarshaller
在保存图表时被调用,具体取决于用户的偏好。
通常,您会想要结合 aProcessValidator
和 an ExportMarshaller
。假设您有许多CustomServiceTask
正在使用的 s,它们具有您希望在生成的过程中使用的属性。但是,在生成过程之前,您需要先验证其中一些值。结合 aProcessValidator
和ExportMarshaller
是完成此任务的最佳方式,而 Activiti Designer 使您能够将扩展无缝插入到工具中。
要创建 aProcessValidator
或ExportMarshaller
,您需要创建与扩展调色板不同类型的扩展。这样做的原因很简单:从您的代码中,您将需要访问比集成库提供的 API 更多的 API。特别是,您将需要在 Eclipse 本身中可用的类。因此,要开始,您应该创建一个 Eclipse 插件(您可以通过使用 Eclipse 的 PDE 支持来完成)并将其打包到自定义的 Eclipse 产品或功能中。解释开发 Eclipse 插件所涉及的所有细节超出了本用户指南的范围,因此以下说明仅限于扩展 Activiti Designer 的功能。
您的捆绑包应依赖于以下库:
-
org.eclipse.core.runtime
-
org.eclipse.core.resources
-
org.activiti.designer.eclipse
-
org.activiti.designer.libs
-
org.activiti.designer.util
或者,如果您想在扩展中使用 org.apache.commons.lang 包,可以通过 Designer 获得它。
ProcessValidator
s 和s都是ExportMarshaller
通过扩展基类创建的。这些基类从它们的超类类继承了一些有用的方法AbstractDiagramWorker
。使用这些方法,您可以创建显示在 Eclipse 的问题视图中的信息、警告和错误标记,以便用户找出错误或重要的地方。Resources
您可以和的形式获取有关图表的信息InputStreams
。此信息由 提供DiagramWorkerContext
,可从AbstractDiagramWorker
课程中获得。
在 a或 an中调用clearMarkers()
作为您首先要做的事情之一可能是一个好主意;这将清除您的工作人员之前的任何标记(标记会自动链接到工作人员,并且清除一名工作人员的标记会保留其他标记不变)。例如:ProcessValidator
ExportMarshaller
1
2 // Clear markers for this diagram first
clearMarkersForDiagram();
您还应该使用提供的进度监视器(在 中DiagramWorkerContext
)向用户报告您的进度,因为验证和/或编组操作可能会占用一些时间,在此期间用户被迫等待。报告进度需要一些关于如何使用 Eclipse 功能的知识。请查看本文以全面了解概念和用法。
创建 ProcessValidator 扩展
正在审核中! |
org.activiti.designer.eclipse.extension.validation.ProcessValidator
为文件中的扩展点创建一个扩展plugin.xml
。对于此扩展点,您需要对类进行子AbstractProcessValidator
类化。
1
2
3
4
5
6
7
8
9 <?eclipse version="3.6"?>
<plugin>
<extension
point="org.activiti.designer.eclipse.extension.validation.ProcessValidator">
<ProcessValidator
class="org.acme.validation.AcmeProcessValidator">
</ProcessValidator>
</extension>
</plugin>
1
2 public class AcmeProcessValidator extends AbstractProcessValidator {
}
您必须实现许多方法。最重要的是,实施getValidatorId()
这样您就可以为您的验证器返回一个全球唯一的 ID。这将使您能够从 and 调用它ExportMarshaller
,或者让其他人从他们的ExportMarshaller
. 为您的验证器实现getValidatorName()
并返回一个逻辑名称。此名称在对话框中显示给用户。在getFormatName()
中,您可以返回验证器通常验证的图表类型。
验证工作本身是在validateDiagram()
方法中完成的。从此时起,您在此处编码的内容取决于您的特定功能。然而,通常情况下,您会希望从掌握图表流程中的节点开始,这样您就可以遍历它们、收集、比较和验证数据。此代码段向您展示了如何执行此操作:
1
2
3
4
5
6
7 final EList<EObject> contents = getResourceForDiagram(diagram).getContents();
for (final EObject object : contents) {
if (object instanceof StartEvent ) {
// Perform some validations for StartEvents
}
// Other node types and validations
}
在进行验证时不要忘记调用addProblemToDiagram()
和/或addWarningToDiagram()
等。确保在最后返回正确的布尔结果,以指示您认为验证是成功还是失败。这可以被使用并调用ExportMarshaller
来确定下一步的行动方案。
创建 ExportMarshaller 扩展
org.activiti.designer.eclipse.extension.ExportMarshaller
为文件中的扩展点创建一个扩展plugin.xml
。对于此扩展点,您需要对类进行子AbstractExportMarshaller
类化。这个抽象基类在编组为您自己的格式时为您提供了许多有用的方法,但最重要的是,它允许您将资源保存到工作区并调用验证器。
Designer 的示例文件夹中提供了示例实现。这个例子展示了如何使用基类中的方法来完成基础工作,例如访问图表InputStream
、使用图表BpmnModel
以及将资源保存到工作区。
1
2
3
4
5
6
7
8
9 <?eclipse version="3.6"?>
<plugin>
<extension
point="org.activiti.designer.eclipse.extension.ExportMarshaller">
<ExportMarshaller
class="org.acme.export.AcmeExportMarshaller">
</ExportMarshaller>
</extension>
</plugin>
1
2 public class AcmeExportMarshaller extends AbstractExportMarshaller {
}
您需要实现一些方法,例如getMarshallerName()
和getFormatName()
。这些方法用于向用户显示选项并显示正在进行的对话框中的信息,因此请确保您返回的描述反映了您正在实现的功能。
您的大部分工作都是在该doMarshallDiagram()
方法中执行的。
如果您想先执行某个验证,可以直接从编组器调用验证器。您从验证器收到布尔结果,因此您知道验证是否成功。在大多数情况下,如果图表无效,您将不想继续编组图表,但您可能会选择继续进行,甚至在验证失败时创建不同的资源。
获得所需的所有数据后,您应该调用该saveResource()
方法来创建包含数据的文件。您可以saveResource()
从单个 ExportMarshaller 调用任意多次;因此,编组器可用于创建多个输出文件。
您可以使用类saveResource()
中的方法为输出资源构造文件名AbstractDiagramWorker
。您可以解析几个有用的变量,允许您创建文件名,例如 _original-filename__my-format-name.xml。这些变量在 Javadocs 中描述并由ExportMarshaller
接口定义。resolvePlaceholders()
如果您想自己解析占位符,也可以在字符串(例如路径)上使用。getURIRelativeToDiagram()
将为您调用它。
您应该使用提供的进度监视器向用户报告您的进度。本文介绍了如何执行此操作。
13. REST API
13.1. 一般 Activiti REST 原则
13.1.1. 安装和认证
Activiti 包含一个到 Activiti Engine 的 REST API,可以通过将 activiti-rest.war 文件部署到像 Apache Tomcat 这样的 servlet 容器来安装它。但是,它也可以通过在您的应用程序中包含 servlet 和它的映射并将所有 activiti-rest 依赖项添加到类路径来在另一个 Web 应用程序中使用。
默认情况下,Activiti 引擎将连接到内存中的 H2 数据库。您可以在 WEB-INF/classes 文件夹中的 db.properties 文件中更改数据库设置。REST API 使用 JSON 格式 ( http://www.json.org ) 并基于 Spring MVC ( http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc .html)。
默认情况下,所有 REST 资源都需要有效的 Activiti 用户进行身份验证。使用了基本的 HTTP 访问身份验证,因此在执行请求时应始终包含Authorization: Basic …==
HTTP 标头或在请求 URL 中包含用户名和密码(例如http://username:password@localhost…;
)。
建议将基本身份验证与 HTTPS 结合使用。
13.1.2. 配置
Activiti REST Web 应用程序使用 Spring Java 配置来启动 Activiti 引擎,使用 Spring 安全性定义基本身份验证安全性,并为特定变量处理定义变量转换器。可以通过更改 WEB-INF/classes 文件夹中的 engine.properties 文件来定义少量属性。如果您需要更高级的配置选项,可以在 activiti-custom-context.xml 文件中覆盖 XML 中的默认 Spring beans,您也可以在 WEB-INF/classes 文件夹中找到。此文件的注释中已包含示例配置。这也是通过定义一个名为 restResponsefactory 的新 Spring bean 并使用您的自定义实现类来覆盖默认 RestResponseFactory 的地方。
13.1.3. 在 Tomcat 中的使用
由于Tomcat 上的默认安全属性,默认情况下不允许转义的正斜杠 (%2F
和)(返回 400-result)。%5C
这可能会对部署资源及其数据 URL 产生影响,因为 URL 可能包含转义的正斜杠。
当遇到意外的 400 结果问题时,请设置以下系统属性:-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true.
在下面描述的 HTTP 请求上,始终将Accept和Content-Type(在发布/放置 JSON 的情况下)标头设置为application/json是最佳实践。
13.1.4. 方法和返回码
方法 | 运营 |
---|---|
|
获取单个资源或获取资源集合。 |
|
创建新资源。也用于执行具有过于复杂的请求结构以适应 GET 请求的查询 URL 的资源查询。 |
|
更新现有资源的属性。也用于对现有资源调用操作。 |
|
删除现有资源。 |
回复 | 描述 |
---|---|
|
操作成功并返回响应( |
|
操作成功,实体已创建并在 response-body ( |
|
操作成功并且实体已被删除,因此没有返回响应正文( |
|
操作失败。该操作需要设置 Authentication 标头。如果请求中存在此信息,则提供的凭据无效或用户无权执行此操作。 |
|
该操作被禁止,不应重新尝试。这并不意味着身份验证而不是授权问题,这是不允许的操作。示例:无论用户或进程/任务状态如何,都不允许删除属于正在运行的进程的一部分的任务,并且永远不会被允许。 |
|
操作失败。未找到请求的资源。 |
|
操作失败。此资源不允许使用使用的方法。例如,尝试更新(PUT)部署资源将导致 |
|
操作失败。该操作导致对已被另一个操作更新的资源进行更新,从而使更新不再有效。还可以指示正在集合中创建的资源,其中具有该标识符的资源已经存在。 |
|
操作失败。请求正文包含不受支持的媒体类型。当请求正文 JSON 包含不具有要接受的正确格式/类型的未知属性或值时,也会发生这种情况。 |
|
操作失败。执行操作时发生意外异常。响应正文包含有关错误的详细信息。 |
application/json
除非请求二进制内容(例如部署资源数据),否则HTTP 响应的媒体类型总是使用内容的媒体类型。
13.1.5. 错误响应正文
当发生错误(客户端和服务器,4XX 和 5XX 状态码)时,响应正文包含一个描述发生错误的对象。未找到任务时的 404 状态示例:
1
2
3
4 {
"statusCode" : 404,
"errorMessage" : "Could not find a task with id '444'."
}
13.1.6. 请求参数
网址片段
作为 url 一部分的参数(例如 中的 deploymentId 参数http://host/actviti-rest/service/repository/deployments/{deploymentId}
)需要正确转义(请参阅URL-encoding 或 Percent-encoding),以防段包含特殊字符。大多数框架都内置了这个功能,但应该考虑到它。特别是对于可以包含正斜杠的段(例如部署资源),这是必需的。
休息网址查询参数
在 URL 中作为查询字符串添加的参数(例如,在 中使用的名称参数http://host/activiti-rest/service/deployments?name=Deployment
)可以具有以下类型,并在相应的 REST-API 文档中有所提及:
类型 | 格式 |
---|---|
细绳 |
纯文本参数。可以包含 URL 中允许的任何有效字符。如果是 |
整数 |
表示整数值的参数。只能包含介于 -2.147.483.648 和 2.147.483.647 之间的数字非十进制值。 |
长 |
表示长值的参数。只能包含介于 -9.223.372.036.854.775.808 和 9.223.372.036.854.775.807 之间的数字非十进制值。 |
布尔值 |
表示布尔值的参数。可以是 |
日期 |
表示日期值的参数。使用 ISO-8601 日期格式(参见wikipedia 上的 ISO-8601)同时使用时间和日期组件(例如 |
JSON 正文参数
类型 | 格式 |
---|---|
细绳 |
纯文本参数。如果是 |
整数 |
表示整数值的参数,使用 JSON 数字。只能包含介于 -2.147.483.648 和 2.147.483.647 之间的数字非十进制值。 |
长 |
表示长值的参数,使用 JSON 数字。只能包含介于 -9.223.372.036.854.775.808 和 9.223.372.036.854.775.807 之间的数字非十进制值。 |
日期 |
表示日期值的参数,使用 JSON 文本。使用 ISO-8601 日期格式(参见wikipedia 上的 ISO-8601)同时使用时间和日期组件(例如 |
分页和排序
分页和订单参数可以作为查询字符串添加到 URL 中(例如,在 中使用的名称参数http://host/activiti-rest/service/deployments?sort=name
)。
范围 | 默认值 | 描述 |
---|---|---|
种类 |
每个查询实现不同 |
排序键的名称,每个查询实现的默认值和允许值不同。 |
命令 |
升序 |
排序顺序可以是asc或desc。 |
开始 |
0 |
允许对结果进行分页的参数。默认情况下,结果将从 0 开始。 |
尺寸 |
10 |
允许对结果进行分页的参数。默认情况下,大小为 10。 |
JSON查询变量格式
1
2
3
4
5
6 {
"name" : "variableName",
"value" : "variableValue",
"operation" : "equals",
"type" : "string"
}
范围 | 必需的 | 描述 |
---|---|---|
名称 |
不 |
要包含在查询中的变量的名称。 |
价值 |
是的 |
查询中包含的变量的值应包含给定类型的正确格式。 |
操作员 |
是的 |
查询中使用的运算符,可以有以下值: |
类型 |
不 |
要使用的变量类型。省略时,将从 |
类型名称 | 描述 |
---|---|
细绳 |
值被线程化并转换为 |
短的 |
值被线程化并转换为 |
整数 |
值被线程化并转换为 |
长 |
值被线程化并转换为 |
双倍的 |
值被线程化并转换为 |
布尔值 |
值被线程化并转换为 |
日期 |
值被视为并转换为 a |
变量表示
在处理变量(执行/流程和任务)时,REST-api 使用一些通用原则和 JSON 格式进行读写。变量的 JSON 表示形式如下所示:
1
2
3
4
5
6
7 {
"name" : "variableName",
"value" : "variableValue",
"valueUrl" : "http://...",
"scope" : "local",
"type" : "string"
}
范围 | 必需的 | 描述 |
---|---|---|
名称 |
是的 |
变量的名称。 |
价值 |
不 |
变量的值。写入变量并 |
价值网址 |
不 |
|
范围 |
不 |
变量的范围。如果 |
类型 |
不 |
变量的类型。有关类型的更多信息,请参见下表。写入变量且省略此值时,类型将从原始 JSON 属性请求类型中扣除,并且仅限于 |
类型名称 | 描述 |
---|---|
细绳 |
值作为 |
整数 |
值作为 |
短的 |
值作为 |
长 |
值作为 |
双倍的 |
值作为 |
布尔值 |
值作为 |
日期 |
值被视为 |
二进制 |
二进制变量,被视为字节数组。该 |
可序列化 |
可序列化 Java 对象的序列化表示。与 |
可以使用自定义 JSON 表示(简单值或复杂/嵌套 JSON 对象)支持其他变量类型。通过扩展initializeVariableConverters()
on 方法org.activiti.rest.service.api.RestResponseFactory
,您可以添加额外org.activiti.rest.service.api.engine.variable.RestVariableConverter
的类来支持将您的 POJO 转换为适合通过 REST 传输的格式并将 REST 值转换回您的 POJO。到 JSON 的实际转换是由 Jackson 完成的。
13.2. 部署
使用 tomcat 时,请阅读Tomcat 中的用法。
13.2.1. 部署列表
获取存储库/部署
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
名称 |
不 |
细绳 |
仅返回具有给定名称的部署。 |
名字喜欢 |
不 |
细绳 |
仅返回名称与给定名称类似的部署。 |
类别 |
不 |
细绳 |
仅返回具有给定类别的部署。 |
类别不等于 |
不 |
细绳 |
仅返回没有给定类别的部署。 |
租户 ID |
不 |
细绳 |
仅返回具有给定租户 ID 的部署。 |
租户IdLike |
不 |
细绳 |
仅返回具有给定值的tenantId 的部署。 |
没有租户 ID |
不 |
布尔值 |
如果 |
种类 |
不 |
id(默认)、name、deploytime或tenantId |
要排序的属性,与order一起使用。 |
响应代码 | 描述 |
---|---|
200 |
表示请求成功。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 {
"data": [
{
"id": "10",
"name": "activiti-examples.bar",
"deploymentTime": "2010-10-13T14:54:26.750+02:00",
"category": "examples",
"url": "http://localhost:8081/service/repository/deployments/10",
"tenantId": null
}
],
"total": 1,
"start": 0,
"sort": "id",
"order": "asc",
"size": 1
}
13.2.2. 获取部署
获取存储库/部署/{deploymentId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
部署Id |
是的 |
细绳 |
要获取的部署的 id。 |
响应代码 | 描述 |
---|---|
200 |
指示部署已找到并返回。 |
404 |
表示未找到请求的部署。 |
成功响应正文:
1
2
3
4
5
6
7
8 {
"id": "10",
"name": "activiti-examples.bar",
"deploymentTime": "2010-10-13T14:54:26.750+02:00",
"category": "examples",
"url": "http://localhost:8081/service/repository/deployments/10",
"tenantId" : null
}
13.2.3. 创建新部署
POST 存储库/部署
请求正文:
请求正文应包含multipart/form-data类型的数据。请求中应该只有一个文件,任何其他文件都将被忽略。部署名称是传入的文件字段的名称。如果需要在单个部署中部署多个资源,请将资源压缩为 zip 并确保文件名以.bar
or结尾.zip
。
可以在带有 name 的请求正文中传递一个附加参数(表单字段)tenantId
。此字段的值将用作完成此部署的租户的 ID。
响应代码 | 描述 |
---|---|
201 |
表示已创建部署。 |
400 |
表示请求正文中不存在任何内容,或者不支持部署内容 mime 类型。状态描述包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8 {
"id": "10",
"name": "activiti-examples.bar",
"deploymentTime": "2010-10-13T14:54:26.750+02:00",
"category": null,
"url": "http://localhost:8081/service/repository/deployments/10",
"tenantId" : "myTenant"
}
13.2.4. 删除部署
删除存储库/部署/{deploymentId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
部署Id |
是的 |
细绳 |
要删除的部署的 ID。 |
响应代码 | 描述 |
---|---|
204 |
指示部署已找到并已被删除。响应体故意为空。 |
404 |
表示未找到请求的部署。 |
13.2.5. 列出部署中的资源
获取存储库/部署/{deploymentId}/resources
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
部署Id |
是的 |
细绳 |
要获取资源的部署的 ID。 |
响应代码 | 描述 |
---|---|
200 |
表示已找到部署并已返回资源列表。 |
404 |
表示未找到请求的部署。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 [
{
"id": "diagrams/my-process.bpmn20.xml",
"url": "http://localhost:8081/activiti-rest/service/repository/deployments/10/resources/diagrams%2Fmy-process.bpmn20.xml",
"contentUrl": "http://localhost:8081/activiti-rest/service/repository/deployments/10/resourcedata/diagrams%2Fmy-process.bpmn20.xml",
"mediaType": "text/xml",
"type": "processDefinition"
},
{
"id": "image.png",
"url": "http://localhost:8081/activiti-rest/service/repository/deployments/10/resources/image.png",
"contentUrl": "http://localhost:8081/activiti-rest/service/repository/deployments/10/resourcedata/image.png",
"mediaType": "image/png",
"type": "resource"
}
]
-
mediaType
:包含资源具有的媒体类型。这是使用 (pluggable) 解决的MediaTypeResolver
,默认情况下,它包含有限数量的 mime 类型映射。 -
type
:资源类型,可能的值: -
resource
: 普通的旧资源。 -
processDefinition
:包含一个或多个流程定义的资源。该资源由部署者获取。 -
processImage
:表示已部署流程定义的图形布局的资源。
单个资源的结果 JSON 中的 contentUrl 属性包含用于检索二进制资源的实际 URL。
13.2.6. 获取部署资源
获取存储库/部署/{deploymentId}/resources/{resourceId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
部署Id |
是的 |
细绳 |
请求的资源所属的部署的 ID。 |
资源 ID |
是的 |
细绳 |
要获取的资源的 id。确保对 resourceId 进行 URL 编码,以防它包含正斜杠。例如:使用diagrams%2Fmy-process.bpmn20.xml而不是diagrams/Fmy-process.bpmn20.xml。 |
响应代码 | 描述 |
---|---|
200 |
表示已找到部署和资源并且已返回资源。 |
404 |
表示未找到请求的部署或部署中不存在具有给定 ID 的资源。状态描述包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7 {
"id": "diagrams/my-process.bpmn20.xml",
"url": "http://localhost:8081/activiti-rest/service/repository/deployments/10/resources/diagrams%2Fmy-process.bpmn20.xml",
"contentUrl": "http://localhost:8081/activiti-rest/service/repository/deployments/10/resourcedata/diagrams%2Fmy-process.bpmn20.xml",
"mediaType": "text/xml",
"type": "processDefinition"
}
-
mediaType
:包含资源具有的媒体类型。这是使用 (pluggable) 解决的MediaTypeResolver
,默认情况下,它包含有限数量的 mime 类型映射。 -
type
:资源类型,可能的值: -
resource
: 普通的旧资源。 -
processDefinition
:包含一个或多个流程定义的资源。该资源由部署者获取。 -
processImage
:表示已部署流程定义的图形布局的资源。
13.2.7. 获取部署资源内容
获取存储库/部署/{deploymentId}/resourcedata/{resourceId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
部署Id |
是的 |
细绳 |
请求的资源所属的部署的 ID。 |
资源 ID |
是的 |
细绳 |
要为其获取数据的资源的 ID。确保对 resourceId 进行 URL 编码,以防它包含正斜杠。例如:使用diagrams%2Fmy-process.bpmn20.xml而不是diagrams/Fmy-process.bpmn20.xml。 |
.获取部署资源内容 - 响应码
响应代码 | 描述 |
---|---|
200 |
表示已找到部署和资源,并已返回资源数据。 |
404 |
表示未找到请求的部署或部署中不存在具有给定 ID 的资源。状态描述包含附加信息。 |
成功响应正文:
响应正文将包含所请求资源的二进制资源内容。响应内容类型将与资源mimeType属性中返回的类型相同。此外,还设置了 content-disposition 标头,允许浏览器下载文件而不是显示它。
13.3. 流程定义
13.3.1. 流程定义列表
获取存储库/流程定义
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
版本 |
不 |
整数 |
只返回给定版本的流程定义。 |
名称 |
不 |
细绳 |
仅返回具有给定名称的流程定义。 |
名字喜欢 |
不 |
细绳 |
仅返回名称与给定名称类似的流程定义。 |
钥匙 |
不 |
细绳 |
仅返回具有给定键的流程定义。 |
键像 |
不 |
细绳 |
仅返回名称与给定键类似的流程定义。 |
资源名称 |
不 |
细绳 |
仅返回具有给定资源名称的流程定义。 |
资源名称Like |
不 |
细绳 |
仅返回名称与给定资源名称类似的流程定义。 |
类别 |
不 |
细绳 |
只返回给定类别的流程定义。 |
类别喜欢 |
不 |
细绳 |
只返回与给定名称类似的类别的流程定义。 |
类别不等于 |
不 |
细绳 |
只返回没有给定类别的流程定义。 |
部署Id |
不 |
细绳 |
仅返回属于具有给定 ID 的部署的一部分的流程定义。 |
用户可启动 |
不 |
细绳 |
仅返回可以由给定用户启动的流程定义。 |
最新的 |
不 |
布尔值 |
只返回最新的流程定义版本。只能与key和keyLike参数一起使用,使用任何其他参数都会导致 400 响应。 |
暂停 |
不 |
布尔值 |
如果 |
种类 |
不 |
名称(默认)、id、key、category、deploymentId和version |
要排序的属性,与order一起使用。 |
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回流程定义 |
400 |
表示参数以错误的格式传递,或者latest与key和keyLike以外的其他参数一起使用。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 {
"data": [
{
"id" : "oneTaskProcess:1:4",
"url" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"version" : 1,
"key" : "oneTaskProcess",
"category" : "Examples",
"suspended" : false,
"name" : "The One Task Process",
"description" : "This is a process for testing purposes",
"deploymentId" : "2",
"deploymentUrl" : "http://localhost:8081/repository/deployments/2",
"graphicalNotationDefined" : true,
"resource" : "http://localhost:8182/repository/deployments/2/resources/testProcess.xml",
"diagramResource" : "http://localhost:8182/repository/deployments/2/resources/testProcess.png",
"startFormDefined" : false
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
-
graphicalNotationDefined
:表示流程定义包含图形信息(BPMN DI)。 -
resource
:包含实际部署的 BPMN 2.0 xml。 -
diagramResource
:包含过程的图形表示,当没有可用的图表时为空。
13.3.2. 获取流程定义
获取存储库/流程定义/{processDefinitionId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
流程定义 ID |
是的 |
细绳 |
要获取的流程定义的 id。 |
响应代码 | 描述 |
---|---|
200 |
表示找到并返回了流程定义。 |
404 |
表示未找到请求的流程定义。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 {
"id" : "oneTaskProcess:1:4",
"url" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"version" : 1,
"key" : "oneTaskProcess",
"category" : "Examples",
"suspended" : false,
"name" : "The One Task Process",
"description" : "This is a process for testing purposes",
"deploymentId" : "2",
"deploymentUrl" : "http://localhost:8081/repository/deployments/2",
"graphicalNotationDefined" : true,
"resource" : "http://localhost:8182/repository/deployments/2/resources/testProcess.xml",
"diagramResource" : "http://localhost:8182/repository/deployments/2/resources/testProcess.png",
"startFormDefined" : false
}
-
graphicalNotationDefined
:表示流程定义包含图形信息(BPMN DI)。 -
resource
:包含实际部署的 BPMN 2.0 xml。 -
diagramResource
:包含过程的图形表示,当没有可用的图表时为空。
13.3.3. 更新流程定义的类别
PUT 存储库/流程定义/{processDefinitionId}
正文 JSON:
1
2
3 {
"category" : "updatedcategory"
}
响应代码 | 描述 |
---|---|
200 |
表示进程的类别已更改。 |
400 |
表示请求正文中未定义类别。 |
404 |
表示未找到请求的流程定义。 |
成功响应正文:请参阅 的响应repository/process-definitions/{processDefinitionId}
。
13.3.4. 获取流程定义资源内容
获取存储库/流程定义/{processDefinitionId}/resourcedata
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
流程定义 ID |
是的 |
细绳 |
要为其获取资源数据的流程定义的 ID。 |
回复:
与 .完全相同的响应代码/男孩GET repository/deployment/{deploymentId}/resourcedata/{resourceId}
。
13.3.5. 获取流程定义 BPMN 模型
获取存储库/流程定义/{processDefinitionId}/模型
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
流程定义 ID |
是的 |
细绳 |
要为其获取模型的流程定义的 ID。 |
响应代码 | 描述 |
---|---|
200 |
表示已找到流程定义并返回模型。 |
404 |
表示未找到请求的流程定义。 |
响应正文:
响应正文是 的 JSON 表示形式org.activiti.bpmn.model.BpmnModel
,包含完整的流程定义模型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 {
"processes":[
{
"id":"oneTaskProcess",
"xmlRowNumber":7,
"xmlColumnNumber":60,
"extensionElements":{
},
"name":"The One Task Process",
"executable":true,
"documentation":"One task process description",
]
}
13.3.6。暂停流程定义
PUT 存储库/流程定义/{processDefinitionId}
正文 JSON:
1
2
3
4
5 {
"action" : "suspend",
"includeProcessInstances" : "false",
"date" : "2013-04-15T00:42:12Z"
}
范围 | 描述 | 必需的 |
---|---|---|
行动 |
要执行的动作。要么 |
是的 |
包括进程实例 |
是否暂停/激活此流程定义的正在运行的流程实例。如果省略,则流程实例将保持其状态。 |
不 |
日期 |
应执行暂停/激活的日期 (ISO-8601)。如果省略,暂停/激活立即生效。 |
不 |
响应代码 | 描述 |
---|---|
200 |
表示进程已挂起。 |
404 |
表示未找到请求的流程定义。 |
409 |
表示请求的流程定义已经挂起。 |
成功响应正文:请参阅 的响应repository/process-definitions/{processDefinitionId}
。
13.3.7. 激活流程定义
PUT 存储库/流程定义/{processDefinitionId}
正文 JSON:
1
2
3
4
5 {
"action" : "activate",
"includeProcessInstances" : "true",
"date" : "2013-04-15T00:42:12Z"
}
请参阅挂起流程定义JSON 正文参数。
响应代码 | 描述 |
---|---|
200 |
表示进程已激活。 |
404 |
表示未找到请求的流程定义。 |
409 |
指示请求的流程定义已经处于活动状态。 |
成功响应正文:请参阅 的响应repository/process-definitions/{processDefinitionId}
。
13.3.8. 获取流程定义的所有候选启动器
获取存储库/流程定义/{processDefinitionId}/identitylinks
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
流程定义 ID |
是的 |
细绳 |
要获取其身份链接的流程定义的 ID。 |
响应代码 | 描述 |
---|---|
200 |
表示找到了流程定义并返回了请求的身份链接。 |
404 |
表示未找到请求的流程定义。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 [
{
"url":"http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4/identitylinks/groups/admin",
"user":null,
"group":"admin",
"type":"candidate"
},
{
"url":"http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4/identitylinks/users/kermit",
"user":"kermit",
"group":null,
"type":"candidate"
}
]
13.3.9。将候选启动器添加到流程定义
POST 存储库/流程定义/{processDefinitionId}/identitylinks
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
流程定义 ID |
是的 |
细绳 |
流程定义的 id。 |
请求正文(用户):
1
2
3 {
"user" : "kermit"
}
请求正文(组):
1
2
3 {
"groupId" : "sales"
}
响应代码 | 描述 |
---|---|
201 |
表示找到了流程定义并创建了身份链接。 |
404 |
表示未找到请求的流程定义。 |
成功响应正文:
1
2
3
4
5
6 {
"url":"http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4/identitylinks/users/kermit",
"user":"kermit",
"group":null,
"type":"candidate"
}
13.3.10。从流程定义中删除候选起始者
删除存储库/流程定义/{processDefinitionId}/identitylinks/{family}/{identityId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
流程定义 ID |
是的 |
细绳 |
流程定义的 id。 |
家庭 |
是的 |
细绳 |
要么 要么 |
身份标识 |
是的 |
细绳 |
要作为候选起始者移除的身份的 userId 或 groupId。 |
响应代码 | 描述 |
---|---|
204 |
表示已找到流程定义并删除了身份链接。响应正文故意为空。 |
404 |
表示未找到请求的流程定义或流程定义没有与 url 匹配的身份链接。 |
成功响应正文:
1
2
3
4
5
6 {
"url":"http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4/identitylinks/users/kermit",
"user":"kermit",
"group":null,
"type":"candidate"
}
13.3.11。从流程定义中获取候选启动器
获取存储库/流程定义/{processDefinitionId}/identitylinks/{family}/{identityId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
流程定义 ID |
是的 |
细绳 |
流程定义的 id。 |
家庭 |
是的 |
细绳 |
要么 要么 |
身份标识 |
是的 |
细绳 |
要作为候选起始者的身份的 userId 或 groupId。 |
响应代码 | 描述 |
---|---|
200 |
表示找到了流程定义并返回了身份链接。 |
404 |
表示未找到请求的流程定义或流程定义没有与 url 匹配的身份链接。 |
成功响应正文:
1
2
3
4
5
6 {
"url":"http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4/identitylinks/users/kermit",
"user":"kermit",
"group":null,
"type":"candidate"
}
13.4. 楷模
13.4.1. 获取模型列表
获取存储库/模型
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
ID |
不 |
细绳 |
仅返回具有给定 id 的模型。 |
类别 |
不 |
细绳 |
仅返回具有给定类别的模型。 |
类别喜欢 |
不 |
细绳 |
仅返回具有与给定值类似的类别的模型。使用 |
类别不等于 |
不 |
细绳 |
只返回没有给定类别的模型。 |
名称 |
不 |
细绳 |
仅返回具有给定名称的模型。 |
名字喜欢 |
不 |
细绳 |
仅返回名称与给定值类似的模型。使用 |
钥匙 |
不 |
细绳 |
仅返回具有给定键的模型。 |
部署Id |
不 |
细绳 |
仅返回在给定部署中部署的模型。 |
版本 |
不 |
整数 |
仅返回具有给定版本的模型。 |
最新版本 |
不 |
布尔值 |
如果 |
部署 |
不 |
布尔值 |
如果 |
租户 ID |
不 |
细绳 |
仅返回具有给定租户 ID 的模型。 |
租户IdLike |
不 |
细绳 |
仅返回具有给定值的tenantId 的模型。 |
没有租户 ID |
不 |
布尔值 |
如果 |
种类 |
不 |
id(默认)、category、createTime、key、lastUpdateTime、name、version或tenantId |
要排序的属性,与order一起使用。 |
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回模型 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 {
"data":[
{
"name":"Model name",
"key":"Model key",
"category":"Model category",
"version":2,
"metaInfo":"Model metainfo",
"deploymentId":"7",
"id":"10",
"url":"http://localhost:8182/repository/models/10",
"createTime":"2013-06-12T14:31:08.612+0000",
"lastUpdateTime":"2013-06-12T14:31:08.612+0000",
"deploymentUrl":"http://localhost:8182/repository/deployments/7",
"tenantId":null
},
...
],
"total":2,
"start":0,
"sort":"id",
"order":"asc",
"size":2
}
13.4.2. 获取模型
获取存储库/模型/{modelId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
型号ID |
是的 |
细绳 |
要获取的模型的 id。 |
响应代码 | 描述 |
---|---|
200 |
表示模型已找到并返回。 |
404 |
表示未找到请求的模型。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 {
"id":"5",
"url":"http://localhost:8182/repository/models/5",
"name":"Model name",
"key":"Model key",
"category":"Model category",
"version":2,
"metaInfo":"Model metainfo",
"deploymentId":"2",
"deploymentUrl":"http://localhost:8182/repository/deployments/2",
"createTime":"2013-06-12T12:31:19.861+0000",
"lastUpdateTime":"2013-06-12T12:31:19.861+0000",
"tenantId":null
}
13.4.3. 更新模型
PUT 存储库/模型/{modelId}
请求正文:
1
2
3
4
5
6
7
8
9 {
"name":"Model name",
"key":"Model key",
"category":"Model category",
"version":2,
"metaInfo":"Model metainfo",
"deploymentId":"2",
"tenantId":"updatedTenant"
}
所有请求值都是可选的。例如,您可以只在请求体 JSON-object 中包含name属性,只更新模型的名称,不影响所有其他字段。当一个属性被显式包含并设置为 null 时,模型值将更新为 null。示例:{"metaInfo" : null}
将清除模型的元信息)。
响应代码 | 描述 |
---|---|
200 |
表示已找到并更新模型。 |
404 |
表示未找到请求的模型。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 {
"id":"5",
"url":"http://localhost:8182/repository/models/5",
"name":"Model name",
"key":"Model key",
"category":"Model category",
"version":2,
"metaInfo":"Model metainfo",
"deploymentId":"2",
"deploymentUrl":"http://localhost:8182/repository/deployments/2",
"createTime":"2013-06-12T12:31:19.861+0000",
"lastUpdateTime":"2013-06-12T12:31:19.861+0000",
"tenantId":""updatedTenant"
}
13.4.4. 创建模型
POST 存储库/模型
请求正文:
1
2
3
4
5
6
7
8
9 {
"name":"Model name",
"key":"Model key",
"category":"Model category",
"version":1,
"metaInfo":"Model metainfo",
"deploymentId":"2",
"tenantId":"tenant"
}
所有请求值都是可选的。例如,可以只在请求体 JSON-object 中包含name属性,只设置模型的名称,其他字段为空。
响应代码 | 描述 |
---|---|
201 |
表示模型已创建。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 {
"id":"5",
"url":"http://localhost:8182/repository/models/5",
"name":"Model name",
"key":"Model key",
"category":"Model category",
"version":1,
"metaInfo":"Model metainfo",
"deploymentId":"2",
"deploymentUrl":"http://localhost:8182/repository/deployments/2",
"createTime":"2013-06-12T12:31:19.861+0000",
"lastUpdateTime":"2013-06-12T12:31:19.861+0000",
"tenantId":"tenant"
}
13.4.5. 删除模型
删除存储库/模型/{modelId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
型号ID |
是的 |
细绳 |
要删除的模型的 ID。 |
响应代码 | 描述 |
---|---|
204 |
表示模型已找到并已被删除。响应体故意为空。 |
404 |
表示未找到请求的模型。 |
13.4.6. 获取模型的编辑器源
获取存储库/模型/{modelId}/源
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
型号ID |
是的 |
细绳 |
模型的 ID。 |
响应代码 | 描述 |
---|---|
200 |
表示找到模型并返回源。 |
404 |
表示未找到请求的模型。 |
成功响应正文:
响应正文包含模型的原始编辑器源。application/octet-stream
无论源的内容如何,响应的内容类型都设置为。
13.4.7. 设置模型的编辑器源
PUT 存储库/models/{modelId}/source
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
型号ID |
是的 |
细绳 |
模型的 ID。 |
请求正文:
请求应该是类型multipart/form-data
。源的二进制值中应该包含一个文件部分。
响应代码 | 描述 |
---|---|
200 |
表示已找到模型并且源已更新。 |
404 |
表示未找到请求的模型。 |
成功响应正文:
响应正文包含模型的原始编辑器源。application/octet-stream
无论源的内容如何,响应的内容类型都设置为。
13.4.8. 获取模型的额外编辑器源
获取存储库/模型/{modelId}/source-extra
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
型号ID |
是的 |
细绳 |
模型的 ID。 |
响应代码 | 描述 |
---|---|
200 |
表示找到模型并返回源。 |
404 |
表示未找到请求的模型。 |
成功响应正文:
响应正文包含模型的原始额外编辑器源。application/octet-stream
无论额外源的内容如何,响应的内容类型都设置为。
13.4.9。为模型设置额外的编辑器源
PUT 存储库/模型/{modelId}/source-extra
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
型号ID |
是的 |
细绳 |
模型的 ID。 |
请求正文:
请求应该是类型multipart/form-data
。额外源的二进制值中应该包含一个文件部分。
响应代码 | 描述 |
---|---|
200 |
表示已找到模型并且已更新额外源。 |
404 |
表示未找到请求的模型。 |
成功响应正文:
响应正文包含模型的原始编辑器源。application/octet-stream
无论源的内容如何,响应的内容类型都设置为。
13.5。流程实例
13.5.1. 获取流程实例
获取运行时/流程实例/{processInstanceId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
要获取的流程实例的 id。 |
响应代码 | 描述 |
---|---|
200 |
表示找到并返回了流程实例。 |
404 |
表示未找到请求的流程实例。 |
成功响应正文:
1
2
3
4
5
6
7
8
9 {
"id":"7",
"url":"http://localhost:8182/runtime/process-instances/7",
"businessKey":"myBusinessKey",
"suspended":false,
"processDefinitionUrl":"http://localhost:8182/repository/process-definitions/processOne%3A1%3A4",
"activityId":"processTask",
"tenantId": null
}
13.5.2. 删除流程实例
删除运行时/进程实例/{processInstanceId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
要删除的流程实例的 ID。 |
响应代码 | 描述 |
---|---|
204 |
表示找到并删除了流程实例。响应主体故意留空。 |
404 |
表示未找到请求的流程实例。 |
13.5.3. 激活或挂起流程实例
PUT 运行时/进程实例/{processInstanceId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
要激活/挂起的流程实例的 ID。 |
请求响应正文(暂停):
1
2
3 {
"action":"suspend"
}
请求响应正文(激活):
1
2
3 {
"action":"activate"
}
响应代码 | 描述 |
---|---|
200 |
表示找到了流程实例并执行了操作。 |
400 |
表示提供了无效操作。 |
404 |
表示未找到请求的流程实例。 |
409 |
指示请求的流程实例操作无法执行,因为流程实例已被激活/暂停。 |
13.5.4. 启动流程实例
POST 运行时/进程实例
请求正文(从流程定义 id 开始):
1
2
3
4
5
6
7
8
9
10 {
"processDefinitionId":"oneTaskProcess:1:158",
"businessKey":"myBusinessKey",
"variables": [
{
"name":"myVar",
"value":"This is a variable",
}
]
}
请求正文(由流程定义键开始):
1
2
3
4
5
6
7
8
9
10
11 {
"processDefinitionKey":"oneTaskProcess",
"businessKey":"myBusinessKey",
"tenantId": "tenant1",
"variables": [
{
"name":"myVar",
"value":"This is a variable",
}
]
}
请求正文(以消息开头):
1
2
3
4
5
6
7
8
9
10
11 {
"message":"newOrderMessage",
"businessKey":"myBusinessKey",
"tenantId": "tenant1",
"variables": [
{
"name":"myVar",
"value":"This is a variable",
}
]
}
请注意,transientVariables属性也被接受为此 json 的一部分,它遵循与variables属性相同的结构。
processDefinitionId
请求正文中只能使用processDefinitionKey
或之一。message
参数businessKey
,variables
和tenantId
是可选的。如果tenantId
省略,将使用默认租户。有关变量格式的更多信息,请参见REST 变量部分。请注意,提供的变量范围被忽略,过程变量始终为local
.
响应代码 | 描述 |
---|---|
201 |
表示已创建流程实例。 |
400 |
表示未找到进程定义(基于 id 或键),没有通过发送给定消息启动进程或传递了无效变量。状态描述包含有关错误的附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9 {
"id":"7",
"url":"http://localhost:8182/runtime/process-instances/7",
"businessKey":"myBusinessKey",
"suspended":false,
"processDefinitionUrl":"http://localhost:8182/repository/process-definitions/processOne%3A1%3A4",
"activityId":"processTask",
"tenantId" : null
}
13.5.5. 流程实例列表
获取运行时/进程实例
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
ID |
不 |
细绳 |
仅返回具有给定 id 的流程实例。 |
进程定义键 |
不 |
细绳 |
仅返回具有给定流程定义键的流程实例。 |
流程定义 ID |
不 |
细绳 |
仅返回具有给定流程定义 ID 的流程实例。 |
业务密钥 |
不 |
细绳 |
仅返回具有给定业务密钥的流程实例。 |
涉及用户 |
不 |
细绳 |
仅返回涉及给定用户的流程实例。 |
暂停 |
不 |
布尔值 |
如果 |
superProcessInstanceId |
不 |
细绳 |
仅返回具有给定超级流程实例 ID 的流程实例(对于具有调用活动的流程)。 |
subProcessInstanceId |
不 |
细绳 |
仅返回具有给定子流程实例 id 的流程实例(对于作为调用活动启动的流程)。 |
排除子进程 |
不 |
布尔值 |
仅返回不是子流程的流程实例。 |
包含进程变量 |
不 |
布尔值 |
指示在结果中包含过程变量。 |
租户 ID |
不 |
细绳 |
仅返回具有给定tenantId 的流程实例。 |
租户IdLike |
不 |
细绳 |
只返回与给定值一样的租户 ID 的流程实例。 |
没有租户 ID |
不 |
布尔值 |
如果 |
种类 |
不 |
细绳 |
排序字段,应为 |
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回流程实例 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 {
"data":[
{
"id":"7",
"url":"http://localhost:8182/runtime/process-instances/7",
"businessKey":"myBusinessKey",
"suspended":false,
"processDefinitionUrl":"http://localhost:8182/repository/process-definitions/processOne%3A1%3A4",
"activityId":"processTask",
"tenantId" : null
}
],
"total":2,
"start":0,
"sort":"id",
"order":"asc",
"size":2
}
13.5.6。查询流程实例
POST 查询/处理实例
请求正文:
1
2
3
4
5
6
7
8
9
10
11
12 {
"processDefinitionKey":"oneTaskProcess",
"variables":
[
{
"name" : "myVariable",
"value" : 1234,
"operation" : "equals",
"type" : "long"
}
]
}
通用分页和排序查询参数可用于此 URL。
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回流程实例 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 {
"data":[
{
"id":"7",
"url":"http://localhost:8182/runtime/process-instances/7",
"businessKey":"myBusinessKey",
"suspended":false,
"processDefinitionUrl":"http://localhost:8182/repository/process-definitions/processOne%3A1%3A4",
"activityId":"processTask",
"tenantId" : null
}
],
"total":2,
"start":0,
"sort":"id",
"order":"asc",
"size":2
}
13.5.7. 获取流程实例的图表
获取运行时/流程实例/{processInstanceId}/图表
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
要获取图表的流程实例的 ID。 |
响应代码 | 描述 |
---|---|
200 |
表示找到了流程实例并返回了图表。 |
400 |
表示未找到请求的流程实例,但流程不包含任何图形信息 (BPMN:DI),并且无法创建图表。 |
404 |
表示未找到请求的流程实例。 |
成功响应正文:
1
2
3
4
5
6
7
8 {
"id":"7",
"url":"http://localhost:8182/runtime/process-instances/7",
"businessKey":"myBusinessKey",
"suspended":false,
"processDefinitionUrl":"http://localhost:8182/repository/process-definitions/processOne%3A1%3A4",
"activityId":"processTask"
}
13.5.8. 让相关人员参与流程实例
获取运行时/流程实例/{processInstanceId}/identitylinks
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
指向链接的流程实例的 ID。 |
响应代码 | 描述 |
---|---|
200 |
表示已找到流程实例并返回链接。 |
404 |
表示未找到请求的流程实例。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 [
{
"url":"http://localhost:8182/runtime/process-instances/5/identitylinks/users/john/customType",
"user":"john",
"group":null,
"type":"customType"
},
{
"url":"http://localhost:8182/runtime/process-instances/5/identitylinks/users/paul/candidate",
"user":"paul",
"group":null,
"type":"candidate"
}
]
请注意,groupId
将始终为空,因为它只能让用户参与流程实例。
13.5.9。将涉及的用户添加到流程实例
POST 运行时/进程实例/{processInstanceId}/identitylinks
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
指向链接的流程实例的 ID。 |
请求正文:
1
2
3
4 {
"userId":"kermit",
"type":"participant"
}
两者都是必需的userId
。type
响应代码 | 描述 |
---|---|
201 |
表示找到了流程实例并创建了链接。 |
400 |
指示请求的正文不包含 userId 或类型。 |
404 |
表示未找到请求的流程实例。 |
成功响应正文:
1
2
3
4
5
6 {
"url":"http://localhost:8182/runtime/process-instances/5/identitylinks/users/john/customType",
"user":"john",
"group":null,
"type":"customType"
}
请注意,groupId
将始终为空,因为它只能让用户参与流程实例。
13.5.10。从流程实例中删除涉及的用户
删除运行时/进程实例/{processInstanceId}/identitylinks/users/{userId}/{type}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
流程实例的 ID。 |
用户身份 |
是的 |
细绳 |
要为其删除链接的用户的 ID。 |
类型 |
是的 |
细绳 |
要删除的链接类型。 |
响应代码 | 描述 |
---|---|
204 |
表示已找到流程实例并且链接已被删除。响应主体故意留空。 |
404 |
表示未找到请求的流程实例或要删除的链接不存在。响应状态包含有关错误的附加信息。 |
成功响应正文:
1
2
3
4
5
6 {
"url":"http://localhost:8182/runtime/process-instances/5/identitylinks/users/john/customType",
"user":"john",
"group":null,
"type":"customType"
}
请注意,groupId
将始终为空,因为它只能让用户参与流程实例。
13.5.11。流程实例的变量列表
获取运行时/流程实例/{processInstanceId}/变量
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
流程实例的 id 到变量中。 |
响应代码 | 描述 |
---|---|
200 |
表示已找到流程实例并返回变量。 |
404 |
表示未找到请求的流程实例。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 [
{
"name":"intProcVar",
"type":"integer",
"value":123,
"scope":"local"
},
{
"name":"byteArrayProcVar",
"type":"binary",
"value":null,
"valueUrl":"http://localhost:8182/runtime/process-instances/5/variables/byteArrayProcVar/data",
"scope":"local"
}
]
如果变量是二进制变量或可序列化变量,则valueUrl
指向 URL 以获取原始值。如果它是一个普通变量,则该值存在于响应中。请注意,仅local
返回作用域变量,因为过程实例变量没有global
作用域。
13.5.12。获取流程实例的变量
获取运行时/流程实例/{processInstanceId}/变量/{variableName}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
流程实例的 id 到变量中。 |
变量名称 |
是的 |
细绳 |
要获取的变量的名称。 |
响应代码 | 描述 |
---|---|
200 |
表示找到了流程实例和变量并返回了变量。 |
400 |
表示请求正文不完整或包含非法值。状态描述包含有关错误的附加信息。 |
404 |
表示未找到请求的流程实例或流程实例没有具有给定名称的变量。状态描述包含有关错误的附加信息。 |
成功响应正文:
1
2
3
4
5
6 {
"name":"intProcVar",
"type":"integer",
"value":123,
"scope":"local"
}
如果变量是二进制变量或可序列化变量,则valueUrl
指向 URL 以获取原始值。如果它是一个普通变量,则该值存在于响应中。请注意,仅local
返回作用域变量,因为过程实例变量没有global
作用域。
13.5.13。在流程实例上创建(或更新)变量
POST 运行时/进程实例/{processInstanceId}/变量
PUT 运行时/进程实例/{processInstanceId}/变量
使用时POST
,会创建所有传递的变量。如果流程实例上已经存在变量之一,则请求会导致错误 (409 - CONFLICT)。使用时PUT
,将在流程实例上创建不存在的变量,并覆盖现有的变量而不会出现任何错误。
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
流程实例的 id 到变量中。 |
请求正文:
[ { “名称”:“intProcVar” “类型”:“整数” “价值”:123 }, ... ]
可以将任意数量的变量传递到请求正文数组中。有关变量格式的更多信息,请参见REST 变量部分。请注意,范围被忽略,只能local
在流程实例中设置变量。
响应代码 | 描述 |
---|---|
201 |
表示找到了流程实例并创建了变量。 |
400 |
表示请求正文不完整或包含非法值。状态描述包含有关错误的附加信息。 |
404 |
表示未找到请求的流程实例。 |
409 |
表示找到流程实例但已包含具有给定名称的变量(仅在使用 POST 方法时抛出)。请改用更新方法。 |
成功响应正文:
[ { “名称”:“intProcVar”, “类型”:“整数”, “价值”:123, “范围”:“本地” }, ... ]
13.5.14。更新流程实例上的单个变量
PUT runtime/process-instances/{processInstanceId}/variables/{variableName}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
流程实例的 id 到变量中。 |
变量名称 |
是的 |
细绳 |
要获取的变量的名称。 |
请求正文:
1
2
3
4
5 {
"name":"intProcVar"
"type":"integer"
"value":123
}
有关变量格式的更多信息,请参见REST 变量部分。请注意,范围被忽略,只能local
在流程实例中设置变量。
响应代码 | 描述 |
---|---|
200 |
表示找到了流程实例和变量,并且更新了变量。 |
404 |
表示未找到请求的流程实例或流程实例没有具有给定名称的变量。状态描述包含有关错误的附加信息。 |
成功响应正文:
1
2
3
4
5
6 {
"name":"intProcVar",
"type":"integer",
"value":123,
"scope":"local"
}
如果变量是二进制变量或可序列化变量,则valueUrl
指向 URL 以获取原始值。如果它是一个普通变量,则该值存在于响应中。请注意,仅local
返回作用域变量,因为过程实例变量没有global
作用域。
13.5.15。在流程实例上创建一个新的二进制变量
POST 运行时/进程实例/{processInstanceId}/变量
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
要为其创建新变量的流程实例的 ID。 |
请求正文:
请求应该是类型multipart/form-data
。变量的二进制值应该包含一个文件部分。最重要的是,可以存在以下附加表单字段:
-
name
:变量的必需名称。 -
type
:创建的变量的类型。如果省略,binary
则假定请求中的二进制数据将存储为字节数组。
成功响应正文:
1
2
3
4
5
6
7 {
"name" : "binaryVariable",
"scope" : "local",
"type" : "binary",
"value" : null,
"valueUrl" : "http://.../runtime/process-instances/123/variables/binaryVariable/data"
}
响应代码 | 描述 |
---|---|
201 |
表示已创建变量并返回结果。 |
400 |
表示缺少要创建的变量的名称。状态消息提供附加信息。 |
404 |
表示未找到请求的流程实例。 |
409 |
表示流程实例已经有一个具有给定名称的变量。请改用 PUT 方法更新任务变量。 |
415 |
表示可序列化数据包含一个对象,该对象在运行 Activiti 引擎的 JVM 中不存在任何类,因此无法反序列化。 |
13.5.16。更新流程实例上的现有二进制变量
PUT 运行时/进程实例/{processInstanceId}/变量
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
要为其创建新变量的流程实例的 ID。 |
请求正文:
请求的类型应为multipart/form-data
。变量的二进制值应该包含一个文件部分。最重要的是,可以存在以下附加表单字段:
-
name
:变量的必需名称。 -
type
:创建的变量的类型。如果省略,binary
则假定请求中的二进制数据将存储为字节数组。
成功响应正文:
1
2
3
4
5
6
7 {
"name" : "binaryVariable",
"scope" : "local",
"type" : "binary",
"value" : null,
"valueUrl" : "http://.../runtime/process-instances/123/variables/binaryVariable/data"
}
响应代码 | 描述 |
---|---|
200 |
表示变量已更新并返回结果。 |
400 |
表示缺少要更新的变量的名称。状态消息提供附加信息。 |
404 |
表示未找到请求的流程实例或流程实例没有具有给定名称的变量。 |
415 |
表示可序列化数据包含一个对象,该对象在运行 Activiti 引擎的 JVM 中不存在任何类,因此无法反序列化。 |
13.6。处决
13.6.1. 获得执行
获取运行时/执行/{executionId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
执行 ID |
是的 |
细绳 |
要获取的执行的 id。 |
响应代码 | 描述 |
---|---|
200 |
表示找到并返回了执行。 |
404 |
表示未找到执行。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11 {
"id":"5",
"url":"http://localhost:8182/runtime/executions/5",
"parentId":null,
"parentUrl":null,
"processInstanceId":"5",
"processInstanceUrl":"http://localhost:8182/runtime/process-instances/5",
"suspended":false,
"activityId":null,
"tenantId": null
}
13.6.2. 对执行执行操作
PUT 运行时/执行/{executionId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
执行 ID |
是的 |
细绳 |
要对其执行操作的执行 ID。 |
请求正文(发出执行信号):
1
2
3 {
"action":"signal"
}
变量和瞬态变量属性都接受以下结构:
1
2
3
4
5
6
7
8
9 {
"action":"signal",
"variables" : [
{
"name": "myVar",
"value": "someValue"
}
]
}
请求正文(收到执行的信号事件):
1
2
3
4
5 {
"action":"signalEventReceived",
"signalName":"mySignal"
"variables": [ ]
}
通知执行已收到信号事件,需要一个signalName
参数。variables
可以传递在执行操作之前在执行中设置的可选参数。
请求正文(收到执行的信号事件):
1
2
3
4
5 {
"action":"messageEventReceived",
"messageName":"myMessage"
"variables": [ ]
}
通知执行已收到消息事件,需要一个messageName
参数。variables
可以传递在执行操作之前在执行中设置的可选参数。
响应代码 | 描述 |
---|---|
200 |
表示找到执行并执行操作。 |
204 |
表示找到了执行,执行了操作,并且该操作导致执行结束。 |
400 |
表示请求了非法操作,请求正文中缺少必需的参数或传入了非法变量。状态描述包含有关错误的附加信息。 |
404 |
表示未找到执行。 |
成功响应正文(如果由于操作未结束执行):
1
2
3
4
5
6
7
8
9
10
11 {
"id":"5",
"url":"http://localhost:8182/runtime/executions/5",
"parentId":null,
"parentUrl":null,
"processInstanceId":"5",
"processInstanceUrl":"http://localhost:8182/runtime/process-instances/5",
"suspended":false,
"activityId":null,
"tenantId" : null
}
13.6.3. 在执行中获取活跃的活动
获取运行时/执行/{executionId}/活动
如果有的话,返回在执行和所有子执行(及其子执行,递归)中处于活动状态的所有活动。
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
执行 ID |
是的 |
细绳 |
要为其获取活动的执行的 ID。 |
响应代码 | 描述 |
---|---|
200 |
表示已找到执行并返回活动。 |
404 |
表示未找到执行。 |
成功响应正文:
1
2
3
4 [
"userTaskForManager",
"receiveTask"
]
13.6.4. 处决名单
GET 运行时/执行
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
ID |
不 |
细绳 |
仅返回具有给定 ID 的执行。 |
活动 ID |
不 |
细绳 |
仅返回具有给定活动 ID 的执行。 |
进程定义键 |
不 |
细绳 |
仅返回具有给定流程定义键的执行。 |
流程定义 ID |
不 |
细绳 |
仅返回具有给定流程定义 ID 的执行。 |
进程实例 ID |
不 |
细绳 |
仅返回属于具有给定 id 的流程实例的一部分的执行。 |
消息事件订阅名称 |
不 |
细绳 |
仅返回订阅具有给定名称的消息的执行。 |
信号事件订阅名称 |
不 |
细绳 |
仅返回订阅具有给定名称的信号的执行。 |
父母身份 |
不 |
细绳 |
仅返回作为给定执行的直接子级的执行。 |
租户 ID |
不 |
细绳 |
仅返回具有给定租户 ID 的执行。 |
租户IdLike |
不 |
细绳 |
仅返回具有给定值的tenantId 的执行。 |
没有租户 ID |
不 |
布尔值 |
如果 |
种类 |
不 |
细绳 |
排序字段,应为 |
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回执行 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 {
"data":[
{
"id":"5",
"url":"http://localhost:8182/runtime/executions/5",
"parentId":null,
"parentUrl":null,
"processInstanceId":"5",
"processInstanceUrl":"http://localhost:8182/runtime/process-instances/5",
"suspended":false,
"activityId":null,
"tenantId":null
},
{
"id":"7",
"url":"http://localhost:8182/runtime/executions/7",
"parentId":"5",
"parentUrl":"http://localhost:8182/runtime/executions/5",
"processInstanceId":"5",
"processInstanceUrl":"http://localhost:8182/runtime/process-instances/5",
"suspended":false,
"activityId":"processTask",
"tenantId":null
}
],
"total":2,
"start":0,
"sort":"processInstanceId",
"order":"asc",
"size":2
}
13.6.5. 查询执行
POST 查询/执行
请求正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 {
"processDefinitionKey":"oneTaskProcess",
"variables":
[
{
"name" : "myVariable",
"value" : 1234,
"operation" : "equals",
"type" : "long"
}
],
"processInstanceVariables":
[
{
"name" : "processVariable",
"value" : "some string",
"operation" : "equals",
"type" : "string"
}
]
}
请求正文可以包含可以在List executions URL 查询中使用的所有可能的过滤器。在这些之上,可以提供一个数组variables
并processInstanceVariables
包含在查询中,它们的格式在此处描述。
通用分页和排序查询参数可用于此 URL。
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回执行 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 {
"data":[
{
"id":"5",
"url":"http://localhost:8182/runtime/executions/5",
"parentId":null,
"parentUrl":null,
"processInstanceId":"5",
"processInstanceUrl":"http://localhost:8182/runtime/process-instances/5",
"suspended":false,
"activityId":null,
"tenantId":null
},
{
"id":"7",
"url":"http://localhost:8182/runtime/executions/7",
"parentId":"5",
"parentUrl":"http://localhost:8182/runtime/executions/5",
"processInstanceId":"5",
"processInstanceUrl":"http://localhost:8182/runtime/process-instances/5",
"suspended":false,
"activityId":"processTask",
"tenantId":null
}
],
"total":2,
"start":0,
"sort":"processInstanceId",
"order":"asc",
"size":2
}
13.6.6。执行的变量列表
获取运行时/执行/{executionId}/variables?scope={scope}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
执行 ID |
是的 |
细绳 |
执行到变量的 id。 |
范围 |
不 |
细绳 |
要么 |
响应代码 | 描述 |
---|---|
200 |
表示找到执行并返回变量。 |
404 |
表示未找到请求的执行。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 [
{
"name":"intProcVar",
"type":"integer",
"value":123,
"scope":"global"
},
{
"name":"byteArrayProcVar",
"type":"binary",
"value":null,
"valueUrl":"http://localhost:8182/runtime/process-instances/5/variables/byteArrayProcVar/data",
"scope":"local"
}
]
如果变量是二进制变量或可序列化变量,则valueUrl
指向 URL 以获取原始值。如果它是一个普通变量,则该值存在于响应中。
13.6.7. 获取执行的变量
获取运行时/执行/{executionId}/variables/{variableName}?scope={scope}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
执行 ID |
是的 |
细绳 |
执行到变量的 id。 |
变量名称 |
是的 |
细绳 |
要获取的变量的名称。 |
范围 |
不 |
细绳 |
要么 |
响应代码 | 描述 |
---|---|
200 |
表示找到了执行和变量并返回了变量。 |
400 |
表示请求正文不完整或包含非法值。状态描述包含有关错误的附加信息。 |
404 |
指示未找到请求的执行或执行在请求的范围内没有具有给定名称的变量(如果省略了范围查询参数,则变量在本地和全局范围内不存在)。状态描述包含有关错误的附加信息。 |
成功响应正文:
1
2
3
4
5
6 {
"name":"intProcVar",
"type":"integer",
"value":123,
"scope":"local"
}
如果变量是二进制变量或可序列化变量,则valueUrl
指向 URL 以获取原始值。如果它是一个普通变量,则该值存在于响应中。
13.6.8. 在执行时创建(或更新)变量
POST 运行时/执行/{executionId}/变量
PUT 运行时/执行/{executionId}/变量
使用时POST
,会创建所有传递的变量。如果在请求的范围内执行时已经存在变量之一,则请求会导致错误 (409 - CONFLICT)。使用时PUT
,将在执行时创建不存在的变量,并覆盖现有变量而不会出现任何错误。
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
执行 ID |
是的 |
细绳 |
执行到变量的 id。 |
请求正文:
1
2
3
4
5
6
7
8
9
10
11 [
{
"name":"intProcVar"
"type":"integer"
"value":123,
"scope":"local"
}
]
*请注意,您只能提供具有相同范围的变量。如果请求正文数组包含来自混合范围的变量,则请求会导致错误 (400 - BAD REQUEST)。*可以将任意数量的变量传递到请求正文数组中。有关变量格式的更多信息,请参见REST 变量部分。请注意,范围被忽略,只能local
在流程实例中设置变量。
响应代码 | 描述 |
---|---|
201 |
表示找到了执行并创建了变量。 |
400 |
表示请求正文不完整或包含非法值。状态描述包含有关错误的附加信息。 |
404 |
表示未找到请求的执行。 |
409 |
表示已找到执行但已包含具有给定名称的变量(仅在使用 POST 方法时抛出)。请改用更新方法。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11 [
{
"name":"intProcVar",
"type":"integer",
"value":123,
"scope":"local"
}
]
13.6.9。在执行时更新变量
PUT 运行时/执行/{executionId}/variables/{variableName}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
执行 ID |
是的 |
细绳 |
要为其更新变量的执行 ID。 |
变量名称 |
是的 |
细绳 |
要更新的变量的名称。 |
请求正文:
1
2
3
4
5
6 {
"name":"intProcVar"
"type":"integer"
"value":123,
"scope":"global"
}
有关变量格式的更多信息,请参见REST 变量部分。
响应代码 | 描述 |
---|---|
200 |
表示找到了流程实例和变量,并且更新了变量。 |
404 |
表示未找到请求的流程实例或流程实例没有具有给定名称的变量。状态描述包含有关错误的附加信息。 |
成功响应正文:
1
2
3
4
5
6 {
"name":"intProcVar",
"type":"integer",
"value":123,
"scope":"global"
}
如果变量是二进制变量或可序列化变量,则valueUrl
指向 URL 以获取原始值。如果它是一个普通变量,则该值存在于响应中。
13.6.10。在执行时创建一个新的二进制变量
POST 运行时/执行/{executionId}/变量
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
执行 ID |
是的 |
细绳 |
要为其创建新变量的执行 ID。 |
请求正文:
请求应该是类型multipart/form-data
。变量的二进制值应该包含一个文件部分。最重要的是,可以存在以下附加表单字段:
-
name
:变量的必需名称。 -
type
:创建的变量的类型。如果省略,binary
则假定请求中的二进制数据将存储为字节数组。 -
scope
:创建的变量的范围。如果省略,local
则假定为。
成功响应正文:
1
2
3
4
5
6
7 {
"name" : "binaryVariable",
"scope" : "local",
"type" : "binary",
"value" : null,
"valueUrl" : "http://.../runtime/executions/123/variables/binaryVariable/data"
}
响应代码 | 描述 |
---|---|
201 |
表示已创建变量并返回结果。 |
400 |
表示缺少要创建的变量的名称。状态消息提供附加信息。 |
404 |
表示未找到请求的执行。 |
409 |
表示执行已经有一个具有给定名称的变量。请改用 PUT 方法更新任务变量。 |
415 |
表示可序列化数据包含一个对象,该对象在运行 Activiti 引擎的 JVM 中不存在任何类,因此无法反序列化。 |
13.6.11。更新流程实例上的现有二进制变量
PUT 运行时/执行/{executionId}/variables/{variableName}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
执行 ID |
是的 |
细绳 |
要为其创建新变量的执行 ID。 |
变量名称 |
是的 |
细绳 |
要更新的变量的名称。 |
请求正文:
请求的类型应为multipart/form-data
。变量的二进制值应该包含一个文件部分。最重要的是,可以存在以下附加表单字段:
-
name
:变量的必需名称。 -
type
:创建的变量的类型。如果省略,binary
则假定请求中的二进制数据将存储为字节数组。 -
scope
:创建的变量的范围。如果省略,local
则假定为。
成功响应正文:
1
2
3
4
5
6
7 {
"name" : "binaryVariable",
"scope" : "local",
"type" : "binary",
"value" : null,
"valueUrl" : "http://.../runtime/executions/123/variables/binaryVariable/data"
}
响应代码 | 描述 |
---|---|
200 |
表示变量已更新并返回结果。 |
400 |
表示缺少要更新的变量的名称。状态消息提供附加信息。 |
404 |
表示未找到请求的执行或执行没有具有给定名称的变量。 |
415 |
表示可序列化数据包含一个对象,该对象在运行 Activiti 引擎的 JVM 中不存在任何类,因此无法反序列化。 |
13.7。任务
13.7.1. 得到一个任务
获取运行时/任务/{taskId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要获取的任务的 id。 |
响应代码 | 描述 |
---|---|
200 |
表示已找到并返回任务。 |
404 |
表示未找到请求的任务。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 {
"assignee" : "kermit",
"createTime" : "2013-04-17T10:17:43.902+0000",
"delegationState" : "pending",
"description" : "Task description",
"dueDate" : "2013-04-17T10:17:43.902+0000",
"execution" : "http://localhost:8182/runtime/executions/5",
"id" : "8",
"name" : "My task",
"owner" : "owner",
"parentTask" : "http://localhost:8182/runtime/tasks/9",
"priority" : 50,
"processDefinition" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"processInstance" : "http://localhost:8182/runtime/process-instances/5",
"suspended" : false,
"taskDefinitionKey" : "theTask",
"url" : "http://localhost:8182/runtime/tasks/8",
"tenantId" : null
}
-
delegationState
:任务的委托状态,可以是null
,"pending"
或"resolved".
13.7.2. 任务清单
获取运行时/任务
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
名称 |
不 |
细绳 |
仅返回具有给定名称的任务。 |
名字喜欢 |
不 |
细绳 |
仅返回名称与给定名称类似的任务。 |
描述 |
不 |
细绳 |
仅返回具有给定描述的任务。 |
优先事项 |
不 |
整数 |
仅返回具有给定优先级的任务。 |
最低优先级 |
不 |
整数 |
只返回优先级大于给定值的任务。 |
最大优先级 |
不 |
整数 |
只返回优先级低于给定值的任务。 |
受让人 |
不 |
细绳 |
仅返回分配给给定用户的任务。 |
受让人喜欢 |
不 |
细绳 |
仅返回分配给指定值的受让人的任务。 |
所有者 |
不 |
细绳 |
仅返回给定用户拥有的任务。 |
所有者喜欢 |
不 |
细绳 |
仅返回分配给所有者的任务,例如给定值。 |
未分配 |
不 |
布尔值 |
只返回未分配给任何人的任务。如果 |
委托国 |
不 |
细绳 |
仅返回具有给定委托状态的任务。可能的值为 |
候选用户 |
不 |
细绳 |
仅返回可以由给定用户声明的任务。这包括用户是明确候选者的任务和用户所属的组可声明的任务。 |
候选组 |
不 |
细绳 |
仅返回可以由给定组中的用户声明的任务。 |
候选组 |
不 |
细绳 |
仅返回可以由给定组中的用户声明的任务。值以逗号分隔。 |
涉及用户 |
不 |
细绳 |
仅返回涉及给定用户的任务。 |
任务定义键 |
不 |
细绳 |
仅返回具有给定任务定义 ID 的任务。 |
taskDefinitionKeyLike |
不 |
细绳 |
仅返回具有给定任务定义 id 的任务,如给定值。 |
进程实例 ID |
不 |
细绳 |
仅返回属于具有给定 ID 的流程实例的一部分的任务。 |
流程实例业务密钥 |
不 |
细绳 |
仅返回属于具有给定业务密钥的流程实例的一部分的任务。 |
processInstanceBusinessKeyLike |
不 |
细绳 |
仅返回属于流程实例的一部分的任务,这些任务具有给定值之类的业务键。 |
流程定义 ID |
不 |
细绳 |
仅返回属于具有给定 id 的流程定义的流程实例的一部分的任务。 |
进程定义键 |
不 |
细绳 |
仅返回属于具有给定键的流程定义的流程实例的一部分的任务。 |
processDefinitionKeyLike |
不 |
细绳 |
仅返回属于流程实例的一部分的任务,该流程实例的流程定义具有与给定值类似的键。 |
流程定义名称 |
不 |
细绳 |
仅返回属于具有给定名称的流程定义的流程实例的一部分的任务。 |
流程定义名称Like |
不 |
细绳 |
仅返回属于流程实例的一部分的任务,该流程实例的流程定义的名称与给定值类似。 |
执行 ID |
不 |
细绳 |
仅返回属于具有给定 ID 的执行的一部分的任务。 |
创建于 |
不 |
ISO 日期 |
仅返回在给定日期创建的任务。 |
之前创建 |
不 |
ISO 日期 |
仅返回在给定日期之前创建的任务。 |
创建后 |
不 |
ISO 日期 |
仅返回在给定日期之后创建的任务。 |
由于上 |
不 |
ISO 日期 |
仅返回在给定日期到期的任务。 |
到期前 |
不 |
ISO 日期 |
仅返回在给定日期之前到期的任务。 |
到期后 |
不 |
ISO 日期 |
仅返回在给定日期之后到期的任务。 |
没有到期日 |
不 |
布尔值 |
只返回没有截止日期的任务。如果值为 ,则忽略该属性 |
没有到期日 |
不 |
布尔值 |
只返回没有截止日期的任务。如果值为 ,则忽略该属性 |
没有到期日 |
不 |
布尔值 |
只返回没有截止日期的任务。如果值为 ,则忽略该属性 |
排除子任务 |
不 |
布尔值 |
只返回不是另一个任务的子任务的任务。 |
积极的 |
不 |
布尔值 |
如果 |
包括TaskLocalVariables |
不 |
布尔值 |
指示在结果中包含任务局部变量。 |
包含进程变量 |
不 |
布尔值 |
指示在结果中包含过程变量。 |
租户 ID |
不 |
细绳 |
仅返回具有给定tenantId 的任务。 |
租户IdLike |
不 |
细绳 |
仅返回具有给定值的tenantId 的任务。 |
没有租户 ID |
不 |
布尔值 |
如果 |
候选人或已分配 |
不 |
细绳 |
选择已申领或分配给用户或等待用户申领的任务(候选用户或组)。 |
类别 |
不 |
细绳 |
选择具有给定类别的任务。请注意,这是任务类别,而不是流程定义的类别(BPMN Xml 中的命名空间)。 |
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回任务 |
400 |
表示参数以错误的格式传递,或者delegationState的值无效(除了pending和resolved)。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 {
"data": [
{
"assignee" : "kermit",
"createTime" : "2013-04-17T10:17:43.902+0000",
"delegationState" : "pending",
"description" : "Task description",
"dueDate" : "2013-04-17T10:17:43.902+0000",
"execution" : "http://localhost:8182/runtime/executions/5",
"id" : "8",
"name" : "My task",
"owner" : "owner",
"parentTask" : "http://localhost:8182/runtime/tasks/9",
"priority" : 50,
"processDefinition" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"processInstance" : "http://localhost:8182/runtime/process-instances/5",
"suspended" : false,
"taskDefinitionKey" : "theTask",
"url" : "http://localhost:8182/runtime/tasks/8",
"tenantId" : null
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.7.3. 查询任务
POST 查询/任务
请求正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 {
"name" : "My task",
"description" : "The task description",
...
"taskVariables" : [
{
"name" : "myVariable",
"value" : 1234,
"operation" : "equals",
"type" : "long"
}
],
"processInstanceVariables" : [
{
...
}
]
]
}
允许的所有受支持的 JSON 参数字段与为获取任务集合找到的参数完全相同(candidateGroupIn 除外,它仅在此 POST 任务查询 REST 服务中可用),但作为 JSON 正文参数而不是 URL 参数传入允许更高级的查询并防止请求 uri 太长的错误。最重要的是,查询允许基于任务和流程变量进行过滤。和都是 JSON 数组,包含具有此处所述格式的taskVariables
对象。processInstanceVariables
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回任务 |
400 |
表示参数以错误的格式传递,或者delegationState的值无效(除了pending和resolved)。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 {
"data": [
{
"assignee" : "kermit",
"createTime" : "2013-04-17T10:17:43.902+0000",
"delegationState" : "pending",
"description" : "Task description",
"dueDate" : "2013-04-17T10:17:43.902+0000",
"execution" : "http://localhost:8182/runtime/executions/5",
"id" : "8",
"name" : "My task",
"owner" : "owner",
"parentTask" : "http://localhost:8182/runtime/tasks/9",
"priority" : 50,
"processDefinition" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"processInstance" : "http://localhost:8182/runtime/process-instances/5",
"suspended" : false,
"taskDefinitionKey" : "theTask",
"url" : "http://localhost:8182/runtime/tasks/8",
"tenantId" : null
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.7.4. 更新任务
PUT 运行时/任务/{taskId}
正文 JSON:
1
2
3
4
5
6
7
8
9
10 {
"assignee" : "assignee",
"delegationState" : "resolved",
"description" : "New task description",
"dueDate" : "2013-04-17T13:06:02.438+02:00",
"name" : "New task name",
"owner" : "owner",
"parentTaskId" : "3",
"priority" : 20
}
所有请求值都是可选的。例如,您只能在请求体 JSON-object 中包含assignee属性,只更新任务的受理人,其他所有字段不受影响。当一个属性被显式包含并设置为空时,任务值将被更新为空。示例:{"dueDate" : null}
将清除任务的截止日期)。
响应代码 | 描述 |
---|---|
200 |
表示任务已更新。 |
404 |
表示未找到请求的任务。 |
409 |
表示请求的任务已同时更新。 |
成功响应正文:请参阅 的响应runtime/tasks/{taskId}
。
13.7.5。任务动作
POST 运行时/任务/{taskId}
完成一项任务 - 正文 JSON:
1
2
3
4 {
"action" : "complete",
"variables" : []
}
完成任务。可以使用该variables
属性传入可选变量数组。有关变量格式的更多信息,请参见REST 变量部分。请注意,提供的变量范围被忽略,并且变量被设置在父范围中,除非变量存在于本地范围中,在这种情况下会被覆盖。TaskService.completeTask(taskId, variables)
这与调用的行为相同。
请注意,transientVariables属性也被接受为此 json 的一部分,它遵循与variables属性相同的结构。
声明任务 - 正文 JSON:
1
2
3
4 {
"action" : "claim",
"assignee" : "userWhoClaims"
}
由给定的受理人声明任务。如果受让人是null
,则任务被分配给任何人,可再次申请。
委派任务 - 正文 JSON:
1
2
3
4 {
"action" : "delegate",
"assignee" : "userToDelegateTo"
}
将任务委派给给定的受让人。受让人是必需的。
解决一个任务 - 正文 JSON:
1
2
3 {
"action" : "resolve"
}
解决任务委派。任务将分配回任务所有者(如果有)。
响应代码 | 描述 |
---|---|
200 |
表示操作已执行。 |
400 |
当正文包含无效值或操作需要时缺少受让人时。 |
404 |
表示未找到请求的任务。 |
409 |
表示由于冲突而无法执行操作。任务是同时更新或任务被另一个用户认领,以防发生 |
成功响应正文:请参阅 的响应runtime/tasks/{taskId}
。
13.7.6。删除任务
删除运行时/任务/{taskId}?cascadeHistory={cascadeHistory}&deleteReason={deleteReason}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要删除的任务的 ID。 |
级联历史 |
错误的 |
布尔值 |
删除任务时是否删除 HistoricTask 实例(如果适用)。如果未提供,则此值默认为 false。 |
删除原因 |
错误的 |
细绳 |
删除任务的原因。为真时忽略此值 |
响应代码 | 描述 |
---|---|
204 |
表示任务已找到并已被删除。响应体故意为空。 |
403 |
指示无法删除请求的任务,因为它是工作流的一部分。 |
404 |
表示未找到请求的任务。 |
13.7.7. 获取任务的所有变量
获取运行时/任务/{taskId}/variables?scope={scope}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其获取变量的任务的 ID。 |
范围 |
错误的 |
细绳 |
要返回的变量范围。当 时 |
响应代码 | 描述 |
---|---|
200 |
表示已找到任务并返回请求的变量。 |
404 |
表示未找到请求的任务。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 [
{
"name" : "doubleTaskVar",
"scope" : "local",
"type" : "double",
"value" : 99.99
},
{
"name" : "stringProcVar",
"scope" : "global",
"type" : "string",
"value" : "This is a ProcVariable"
}
]
变量作为 JSON 数组返回。完整的响应描述可以在通用REST-variables 部分中找到。
13.7.8。从任务中获取变量
获取运行时/tasks/{taskId}/variables/{variableName}?scope={scope}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其获取变量的任务的 ID。 |
变量名称 |
是的 |
细绳 |
要获取的变量的名称。 |
范围 |
错误的 |
细绳 |
要返回的变量范围。当 时 |
响应代码 | 描述 |
---|---|
200 |
表示已找到任务并返回请求的变量。 |
404 |
表示未找到请求的任务或任务没有具有给定名称的变量(在给定范围内)。状态消息提供附加信息。 |
成功响应正文:
1
2
3
4
5
6 {
"name" : "myTaskVariable",
"scope" : "local",
"type" : "string",
"value" : "Hello my friend"
}
完整的响应正文描述可以在通用REST-variables 部分中找到。
13.7.9。获取变量的二进制数据
获取运行时/tasks/{taskId}/variables/{variableName}/data?scope={scope}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其获取变量数据的任务的 ID。 |
变量名称 |
是的 |
细绳 |
要为其获取数据的变量的名称。 |
范围 |
错误的 |
细绳 |
要返回的变量范围。当 时 |
响应代码 | 描述 |
---|---|
200 |
表示已找到任务并返回请求的变量。 |
404 |
表示未找到请求的任务或任务没有具有给定名称的变量(在给定范围内)或变量没有可用的二进制流。状态消息提供附加信息。 |
成功响应正文:
响应正文包含变量的二进制值。当变量为 typebinary
时,响应的 content-type 设置为application/octet-stream
,而不管变量的内容或请求的 accept-type 标头。在 的情况下serializable
,application/x-java-serialized-object
用作内容类型。
13.7.10。在任务上创建新变量
POST 运行时/任务/{taskId}/变量
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其创建新变量的任务的 ID。 |
创建简单(非二进制)变量的请求正文:
1
2
3
4
5
6
7
8
9
10
11 [
{
"name" : "myTaskVariable",
"scope" : "local",
"type" : "string",
"value" : "Hello my friend"
},
{
}
]
请求正文应该是一个数组,其中包含一个或多个 JSON 对象,表示应创建的变量。
-
name
:变量的必需名称 -
scope
:创建的变量的范围。如果省略,local
则假定为。 -
type
:创建的变量的类型。如果省略,则恢复为原始 JSON 值类型(字符串、布尔值、整数或双精度)。 -
value
: 变量值。
有关变量格式的更多信息,请参见REST 变量部分。
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11 [
{
"name" : "myTaskVariable",
"scope" : "local",
"type" : "string",
"value" : "Hello my friend"
},
{
}
]
响应代码 | 描述 |
---|---|
201 |
表示已创建变量并返回结果。 |
400 |
表示缺少要创建的变量的名称,或者尝试在具有作用域的独立任务(没有关联的进程)上创建变量, |
404 |
表示未找到请求的任务。 |
409 |
指示任务已经有一个具有给定名称的变量。请改用 PUT 方法更新任务变量。 |
13.7.11。在任务上创建一个新的二进制变量
POST 运行时/任务/{taskId}/变量
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其创建新变量的任务的 ID。 |
请求正文:
请求应该是类型multipart/form-data
。变量的二进制值应该包含一个文件部分。最重要的是,可以存在以下附加表单字段:
-
name
:变量的必需名称。 -
scope
:创建的变量的范围。如果省略,local
则假定为。 -
type
:创建的变量的类型。如果省略,binary
则假定请求中的二进制数据将存储为字节数组。
成功响应正文:
1
2
3
4
5
6
7 {
"name" : "binaryVariable",
"scope" : "local",
"type" : "binary",
"value" : null,
"valueUrl" : "http://.../runtime/tasks/123/variables/binaryVariable/data"
}
响应代码 | 描述 |
---|---|
201 |
表示已创建变量并返回结果。 |
400 |
表示缺少要创建的变量的名称,或者尝试在具有 scope 的独立任务(没有关联的进程)上创建变量 |
404 |
表示未找到请求的任务。 |
409 |
指示任务已经有一个具有给定名称的变量。请改用 PUT 方法更新任务变量。 |
415 |
表示可序列化数据包含一个对象,该对象在运行 Activiti 引擎的 JVM 中不存在任何类,因此无法反序列化。 |
13.7.12。更新任务上的现有变量
PUT 运行时/tasks/{taskId}/variables/{variableName}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其更新变量的任务的 ID。 |
变量名称 |
是的 |
细绳 |
要更新的变量的名称。 |
更新简单(非二进制)变量的请求正文:
1
2
3
4
5
6 {
"name" : "myTaskVariable",
"scope" : "local",
"type" : "string",
"value" : "Hello my friend"
}
-
name
:变量的必需名称 -
scope
:更新的变量范围。如果省略,local
则假定为。 -
type
:更新的变量类型。如果省略,则恢复为原始 JSON 值类型(字符串、布尔值、整数或双精度)。 -
value
: 变量值。
有关变量格式的更多信息,请参见REST 变量部分。
成功响应正文:
1
2
3
4
5
6 {
"name" : "myTaskVariable",
"scope" : "local",
"type" : "string",
"value" : "Hello my friend"
}
响应代码 | 描述 |
---|---|
200 |
表示变量已更新并返回结果。 |
400 |
表示缺少要更新的变量的名称,或者尝试在具有 scope 的独立任务(没有关联的进程)上更新变量 |
404 |
表示未找到请求的任务或任务在给定范围内没有具有给定名称的变量。状态消息包含有关错误的附加信息。 |
13.7.13。更新任务的二进制变量
PUT 运行时/tasks/{taskId}/variables/{variableName}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其更新变量的任务的 ID。 |
变量名称 |
是的 |
细绳 |
要更新的变量的名称。 |
请求正文:
请求应该是类型multipart/form-data
。变量的二进制值应该包含一个文件部分。最重要的是,可以存在以下附加表单字段:
-
name
:变量的必需名称。 -
scope
:更新的变量范围。如果省略,local
则假定为。 -
type
:更新的变量类型。如果省略,binary
则假定请求中的二进制数据将存储为字节数组。
成功响应正文:
1
2
3
4
5
6
7 {
"name" : "binaryVariable",
"scope" : "local",
"type" : "binary",
"value" : null,
"valueUrl" : "http://.../runtime/tasks/123/variables/binaryVariable/data"
}
响应代码 | 描述 |
---|---|
200 |
表示变量已更新并返回结果。 |
400 |
表示缺少要更新的变量的名称,或者尝试在具有 scope 的独立任务(没有关联的进程)上更新变量 |
404 |
表示未找到请求的任务或给定范围内的给定任务不存在要更新的变量。 |
415 |
表示可序列化数据包含一个对象,该对象在运行 Activiti 引擎的 JVM 中不存在任何类,因此无法反序列化。 |
13.7.14。删除任务中的变量
删除运行时/tasks/{taskId}/variables/{variableName}?scope={scope}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要删除的变量所属的任务的 ID。 |
变量名称 |
是的 |
细绳 |
要删除的变量的名称。 |
范围 |
不 |
细绳 |
要删除的变量范围。可以是 |
响应代码 | 描述 |
---|---|
204 |
指示任务变量已找到并已被删除。响应体故意为空。 |
404 |
表示未找到请求的任务或任务没有具有给定名称的变量。状态消息包含有关错误的附加信息。 |
13.7.15。删除任务上的所有局部变量
删除运行时/任务/{taskId}/变量
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要删除的变量所属的任务的 ID。 |
响应代码 | 描述 |
---|---|
204 |
表示已删除所有本地任务变量。响应体故意为空。 |
404 |
表示未找到请求的任务。 |
13.7.16。获取任务的所有身份链接
获取运行时/任务/{taskId}/identitylinks
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要获取其身份链接的任务的 ID。 |
响应代码 | 描述 |
---|---|
200 |
表示已找到任务并返回请求的身份链接。 |
404 |
表示未找到请求的任务。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 [
{
"userId" : "kermit",
"groupId" : null,
"type" : "candidate",
"url" : "http://localhost:8081/activiti-rest/service/runtime/tasks/100/identitylinks/users/kermit/candidate"
},
{
"userId" : null,
"groupId" : "sales",
"type" : "candidate",
"url" : "http://localhost:8081/activiti-rest/service/runtime/tasks/100/identitylinks/groups/sales/candidate"
},
...
]
13.7.17。获取组或用户的任务的所有身份链接
获取运行时/任务/{taskId}/identitylinks/users 获取运行时/任务/{taskId}/identitylinks/groups
仅返回针对用户或组的身份链接。响应正文和状态码与获取任务的完整身份链接列表时完全相同。
13.7.18。获取任务的单个身份链接
获取运行时/任务/{taskId}/identitylinks/{family}/{identityId}/{type}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
任务的 ID。 |
家庭 |
是的 |
细绳 |
要么 要么 |
身份标识 |
是的 |
细绳 |
身份的 id。 |
类型 |
是的 |
细绳 |
身份链接的类型。 |
响应代码 | 描述 |
---|---|
200 |
表示找到并返回了任务和身份链接。 |
404 |
表示未找到请求的任务或该任务没有请求的 identityLink。状态包含有关此错误的其他信息。 |
成功响应正文:
1
2
3
4
5
6 {
"userId" : null,
"groupId" : "sales",
"type" : "candidate",
"url" : "http://localhost:8081/activiti-rest/service/runtime/tasks/100/identitylinks/groups/sales/candidate"
}
13.7.19。在任务上创建身份链接
POST 运行时/tasks/{taskId}/identitylinks
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
任务的 ID。 |
请求正文(用户):
1
2
3
4 {
"userId" : "kermit",
"type" : "candidate",
}
请求正文(组):
1
2
3
4 {
"groupId" : "sales",
"type" : "candidate",
}
响应代码 | 描述 |
---|---|
201 |
表示已找到任务并创建了身份链接。 |
404 |
表示未找到请求的任务或该任务没有请求的 identityLink。状态包含有关此错误的其他信息。 |
成功响应正文:
1
2
3
4
5
6 {
"userId" : null,
"groupId" : "sales",
"type" : "candidate",
"url" : "http://localhost:8081/activiti-rest/service/runtime/tasks/100/identitylinks/groups/sales/candidate"
}
13.7.20。删除任务上的身份链接
删除运行时/tasks/{taskId}/identitylinks/{family}/{identityId}/{type}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
任务的 ID。 |
家庭 |
是的 |
细绳 |
要么 要么 |
身份标识 |
是的 |
细绳 |
身份的 id。 |
类型 |
是的 |
细绳 |
身份链接的类型。 |
响应代码 | 描述 |
---|---|
204 |
表示已找到任务和身份链接,并且该链接已被删除。响应体故意为空。 |
404 |
表示未找到请求的任务或该任务没有请求的 identityLink。状态包含有关此错误的其他信息。 |
13.7.21。为任务创建新评论
POST 运行时/tasks/{taskId}/comments
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其创建评论的任务的 ID。 |
请求正文:
1
2
3
4 {
"message" : "This is a comment on the task.",
"saveProcessInstanceId" : true
}
参数saveProcessInstanceId
是可选的,如果true
保存带有注释的任务的流程实例ID。
成功响应正文:
1
2
3
4
5
6
7
8
9
10 {
"id" : "123",
"taskUrl" : "http://localhost:8081/activiti-rest/service/runtime/tasks/101/comments/123",
"processInstanceUrl" : "http://localhost:8081/activiti-rest/service/history/historic-process-instances/100/comments/123",
"message" : "This is a comment on the task.",
"author" : "kermit",
"time" : "2014-07-13T13:13:52.232+08:00"
"taskId" : "101",
"processInstanceId" : "100"
}
响应代码 | 描述 |
---|---|
201 |
表示评论已创建并返回结果。 |
400 |
表示请求中缺少评论。 |
404 |
表示未找到请求的任务。 |
13.7.22。获取任务的所有评论
获取运行时/任务/{taskId}/评论
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
获取评论的任务的 ID。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 [
{
"id" : "123",
"taskUrl" : "http://localhost:8081/activiti-rest/service/runtime/tasks/101/comments/123",
"processInstanceUrl" : "http://localhost:8081/activiti-rest/service/history/historic-process-instances/100/comments/123",
"message" : "This is a comment on the task.",
"author" : "kermit"
"time" : "2014-07-13T13:13:52.232+08:00"
"taskId" : "101",
"processInstanceId" : "100"
},
{
"id" : "456",
"taskUrl" : "http://localhost:8081/activiti-rest/service/runtime/tasks/101/comments/456",
"processInstanceUrl" : "http://localhost:8081/activiti-rest/service/history/historic-process-instances/100/comments/456",
"message" : "This is another comment on the task.",
"author" : "gonzo",
"time" : "2014-07-13T13:13:52.232+08:00"
"taskId" : "101",
"processInstanceId" : "100"
}
]
响应代码 | 描述 |
---|---|
200 |
表示找到任务并返回评论。 |
404 |
表示未找到请求的任务。 |
13.7.23。获得对任务的评论
获取运行时/任务/{taskId}/comments/{commentId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要获得评论的任务的 ID。 |
评论 ID |
是的 |
细绳 |
评论的 ID。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10 {
"id" : "123",
"taskUrl" : "http://localhost:8081/activiti-rest/service/runtime/tasks/101/comments/123",
"processInstanceUrl" : "http://localhost:8081/activiti-rest/service/history/historic-process-instances/100/comments/123",
"message" : "This is a comment on the task.",
"author" : "kermit",
"time" : "2014-07-13T13:13:52.232+08:00"
"taskId" : "101",
"processInstanceId" : "100"
}
响应代码 | 描述 |
---|---|
200 |
表示找到任务和评论并返回评论。 |
404 |
表示未找到请求的任务或任务没有具有给定 ID 的评论。 |
13.7.24。删除对任务的评论
删除运行时/任务/{taskId}/comments/{commentId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要删除其评论的任务的 ID。 |
评论 ID |
是的 |
细绳 |
评论的 ID。 |
响应代码 | 描述 |
---|---|
204 |
表示已找到任务和评论并删除评论。响应主体故意留空。 |
404 |
表示未找到请求的任务或任务没有具有给定 ID 的评论。 |
13.7.25。获取任务的所有事件
获取运行时/任务/{taskId}/事件
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其获取事件的任务的 ID。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12 [
{
"action" : "AddUserLink",
"id" : "4",
"message" : [ "gonzo", "contributor" ],
"taskUrl" : "http://localhost:8182/runtime/tasks/2",
"time" : "2013-05-17T11:50:50.000+0000",
"url" : "http://localhost:8182/runtime/tasks/2/events/4",
"userId" : null
}
]
响应代码 | 描述 |
---|---|
200 |
表示已找到任务并返回事件。 |
404 |
表示未找到请求的任务。 |
13.7.26。获取有关任务的事件
获取运行时/任务/{taskId}/events/{eventId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其获取事件的任务的 ID。 |
事件ID |
是的 |
细绳 |
事件的 ID。 |
成功响应正文:
1
2
3
4
5
6
7
8
9 {
"action" : "AddUserLink",
"id" : "4",
"message" : [ "gonzo", "contributor" ],
"taskUrl" : "http://localhost:8182/runtime/tasks/2",
"time" : "2013-05-17T11:50:50.000+0000",
"url" : "http://localhost:8182/runtime/tasks/2/events/4",
"userId" : null
}
响应代码 | 描述 |
---|---|
200 |
表示已找到任务和事件并返回事件。 |
404 |
表示未找到请求的任务或任务没有具有给定 ID 的事件。 |
13.7.27。在任务上创建一个新附件,其中包含指向外部资源的链接
POST 运行时/任务/{taskId}/附件
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其创建附件的任务的 ID。 |
请求正文:
1
2
3
4
5
6 {
"name":"Simple attachment",
"description":"Simple attachment description",
"type":"simpleType",
"externalUrl":"http://activiti.org"
}
创建新附件只需要附件名称。
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11 {
"id":"3",
"url":"http://localhost:8182/runtime/tasks/2/attachments/3",
"name":"Simple attachment",
"description":"Simple attachment description",
"type":"simpleType",
"taskUrl":"http://localhost:8182/runtime/tasks/2",
"processInstanceUrl":null,
"externalUrl":"http://activiti.org",
"contentUrl":null
}
响应代码 | 描述 |
---|---|
201 |
表示已创建附件并返回结果。 |
400 |
表示请求中缺少附件名称。 |
404 |
表示未找到请求的任务。 |
13.7.28。在任务上创建一个新附件,附带一个文件
POST 运行时/任务/{taskId}/附件
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其创建附件的任务的 ID。 |
请求正文:
请求应该是类型multipart/form-data
。变量的二进制值应该包含一个文件部分。最重要的是,可以存在以下附加表单字段:
-
name
:变量的必需名称。 -
description
: 附件描述,可选。 -
type
:附件类型,可选。支持任意字符串或有效的 HTTP 内容类型。
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11 {
"id":"5",
"url":"http://localhost:8182/runtime/tasks/2/attachments/5",
"name":"Binary attachment",
"description":"Binary attachment description",
"type":"binaryType",
"taskUrl":"http://localhost:8182/runtime/tasks/2",
"processInstanceUrl":null,
"externalUrl":null,
"contentUrl":"http://localhost:8182/runtime/tasks/2/attachments/5/content"
}
响应代码 | 描述 |
---|---|
201 |
表示已创建附件并返回结果。 |
400 |
表示请求中缺少附件名称或请求中不存在文件。错误消息包含附加信息。 |
404 |
表示未找到请求的任务。 |
13.7.29。获取任务的所有附件
获取运行时/任务/{taskId}/附件
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
获取附件的任务的 ID。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 [
{
"id":"3",
"url":"http://localhost:8182/runtime/tasks/2/attachments/3",
"name":"Simple attachment",
"description":"Simple attachment description",
"type":"simpleType",
"taskUrl":"http://localhost:8182/runtime/tasks/2",
"processInstanceUrl":null,
"externalUrl":"http://activiti.org",
"contentUrl":null
},
{
"id":"5",
"url":"http://localhost:8182/runtime/tasks/2/attachments/5",
"name":"Binary attachment",
"description":"Binary attachment description",
"type":"binaryType",
"taskUrl":"http://localhost:8182/runtime/tasks/2",
"processInstanceUrl":null,
"externalUrl":null,
"contentUrl":"http://localhost:8182/runtime/tasks/2/attachments/5/content"
}
]
响应代码 | 描述 |
---|---|
200 |
表示已找到任务并返回附件。 |
404 |
表示未找到请求的任务。 |
13.7.30。获取任务的附件
获取运行时/任务/{taskId}/attachments/{attachmentId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要获取附件的任务的 ID。 |
附件ID |
是的 |
细绳 |
附件的 ID。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11 {
"id":"5",
"url":"http://localhost:8182/runtime/tasks/2/attachments/5",
"name":"Binary attachment",
"description":"Binary attachment description",
"type":"binaryType",
"taskUrl":"http://localhost:8182/runtime/tasks/2",
"processInstanceUrl":null,
"externalUrl":null,
"contentUrl":"http://localhost:8182/runtime/tasks/2/attachments/5/content"
}
-
externalUrl - contentUrl:
如果附件是指向外部资源的链接,则externalUrl
包含指向外部内容的 URL。如果附件内容存在于 Activiti 引擎中,contentUrl
则将包含一个 URL,可以从该 URL 流式传输二进制内容。 -
type:
可以是任意值。当包含有效的格式化媒体类型(例如 application/xml、text/plain)时,二进制内容 HTTP 响应内容类型将设置为给定值。
响应代码 | 描述 |
---|---|
200 |
表示已找到任务和附件并返回附件。 |
404 |
表示未找到请求的任务或任务没有具有给定 ID 的附件。 |
13.7.31。获取附件的内容
获取运行时/任务/{taskId}/attachment/{attachmentId}/content
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要为其获取变量数据的任务的 ID。 |
附件ID |
是的 |
细绳 |
附件的 id, |
响应代码 | 描述 |
---|---|
200 |
表示已找到任务和附件并返回请求的内容。 |
404 |
表示未找到请求的任务,或者该任务没有具有给定 ID 的附件,或者该附件没有可用的二进制流。状态消息提供附加信息。 |
成功响应正文:
响应正文包含二进制内容。默认情况下,响应的内容类型设置为,application/octet-stream
除非附件类型包含有效的内容类型。
13.7.32。删除任务的附件
删除运行时/任务/{taskId}/attachments/{attachmentId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是的 |
细绳 |
要删除附件的任务的 ID。 |
附件ID |
是的 |
细绳 |
附件的 ID。 |
响应代码 | 描述 |
---|---|
204 |
表示已找到任务和附件并删除附件。响应主体故意留空。 |
404 |
表示未找到请求的任务或任务没有具有给定 ID 的附件。 |
13.8. 历史
13.8.1. 获取历史流程实例
获取历史/历史进程实例/{processInstanceId}
响应代码 | 描述 |
---|---|
200 |
表示可以找到历史流程实例。 |
404 |
表示无法找到历史流程实例。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 {
"data": [
{
"id" : "5",
"businessKey" : "myKey",
"processDefinitionId" : "oneTaskProcess%3A1%3A4",
"processDefinitionUrl" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"startTime" : "2013-04-17T10:17:43.902+0000",
"endTime" : "2013-04-18T14:06:32.715+0000",
"durationInMillis" : 86400056,
"startUserId" : "kermit",
"startActivityId" : "startEvent",
"endActivityId" : "endEvent",
"deleteReason" : null,
"superProcessInstanceId" : "3",
"url" : "http://localhost:8182/history/historic-process-instances/5",
"variables": null,
"tenantId":null
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.8.2. 历史流程实例列表
获取历史/历史进程实例
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
不 |
细绳 |
历史流程实例的 id。 |
进程定义键 |
不 |
细绳 |
历史流程实例的流程定义键。 |
流程定义 ID |
不 |
细绳 |
历史流程实例的流程定义 ID。 |
业务密钥 |
不 |
细绳 |
历史流程实例的业务密钥。 |
涉及用户 |
不 |
细绳 |
历史流程实例的相关用户。 |
完成的 |
不 |
布尔值 |
指示历史流程实例是否已完成。 |
superProcessInstanceId |
不 |
细绳 |
历史流程实例的可选父流程 ID。 |
排除子进程 |
不 |
布尔值 |
仅返回不是子流程的历史流程实例。 |
完成后 |
不 |
日期 |
仅返回在此日期之后完成的历史流程实例。 |
完成之前 |
不 |
日期 |
仅返回在此日期之前完成的历史流程实例。 |
开始之后 |
不 |
日期 |
仅返回在此日期之后启动的历史流程实例。 |
开始之前 |
不 |
日期 |
仅返回在此日期之前启动的历史流程实例。 |
开始于 |
不 |
细绳 |
仅返回由该用户启动的历史流程实例。 |
包含进程变量 |
不 |
布尔值 |
指示是否还应返回历史流程实例变量。 |
租户 ID |
不 |
细绳 |
仅返回具有给定tenantId 的实例。 |
租户IdLike |
不 |
细绳 |
仅返回具有与给定值类似的 tenantId 的实例。 |
没有租户 ID |
不 |
布尔值 |
如果 |
响应代码 | 描述 |
---|---|
200 |
表示可以查询历史流程实例。 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 {
"data": [
{
"id" : "5",
"businessKey" : "myKey",
"processDefinitionId" : "oneTaskProcess%3A1%3A4",
"processDefinitionUrl" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"startTime" : "2013-04-17T10:17:43.902+0000",
"endTime" : "2013-04-18T14:06:32.715+0000",
"durationInMillis" : 86400056,
"startUserId" : "kermit",
"startActivityId" : "startEvent",
"endActivityId" : "endEvent",
"deleteReason" : null,
"superProcessInstanceId" : "3",
"url" : "http://localhost:8182/history/historic-process-instances/5",
"variables": [
{
"name": "test",
"variableScope": "local",
"value": "myTest"
}
],
"tenantId":null
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.8.3. 查询历史流程实例
POST 查询/历史进程实例
请求正文:
1
2
3
4
5
6
7
8
9
10
11
12
13 {
"processDefinitionId" : "oneTaskProcess%3A1%3A4",
"variables" : [
{
"name" : "myVariable",
"value" : 1234,
"operation" : "equals",
"type" : "long"
}
]
}
允许的所有受支持的 JSON 参数字段与为获取历史流程实例集合找到的参数完全相同,但作为 JSON 主体参数而不是 URL 参数传入,以允许更高级的查询并防止 request-uri 的错误太长了。最重要的是,查询允许基于流程变量进行过滤。该variables
属性是一个 JSON 数组,其中包含具有此处所述格式的对象。
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回任务 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 {
"data": [
{
"id" : "5",
"businessKey" : "myKey",
"processDefinitionId" : "oneTaskProcess%3A1%3A4",
"processDefinitionUrl" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"startTime" : "2013-04-17T10:17:43.902+0000",
"endTime" : "2013-04-18T14:06:32.715+0000",
"durationInMillis" : 86400056,
"startUserId" : "kermit",
"startActivityId" : "startEvent",
"endActivityId" : "endEvent",
"deleteReason" : null,
"superProcessInstanceId" : "3",
"url" : "http://localhost:8182/history/historic-process-instances/5",
"variables": [
{
"name": "test",
"variableScope": "local",
"value": "myTest"
}
],
"tenantId":null
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.8.4. 删除历史流程实例
删除历史/历史进程实例/{processInstanceId}
响应代码 | 描述 |
---|---|
200 |
表示历史流程实例已被删除。 |
404 |
表示找不到历史流程实例。 |
13.8.5. 获取历史流程实例的标识链接
获取历史/历史进程实例/{processInstanceId}/identitylinks
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回身份链接 |
404 |
表示找不到流程实例。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11 [
{
"type" : "participant",
"userId" : "kermit",
"groupId" : null,
"taskId" : null,
"taskUrl" : null,
"processInstanceId" : "5",
"processInstanceUrl" : "http://localhost:8182/history/historic-process-instances/5"
}
]
13.8.6。获取历史流程实例变量的二进制数据
获取历史/历史进程实例/{processInstanceId}/variables/{variableName}/data
响应代码 | 描述 |
---|---|
200 |
表示找到了流程实例并返回了请求的变量数据。 |
404 |
表示未找到请求的流程实例,或者流程实例没有具有给定名称的变量,或者该变量没有可用的二进制流。状态消息提供附加信息。 |
成功响应正文:
响应正文包含变量的二进制值。当变量为 typebinary
时,响应的 content-type 设置为application/octet-stream
,而不管变量的内容或请求的 accept-type 标头。在 的情况下serializable
,application/x-java-serialized-object
用作内容类型。
13.8.7. 在历史流程实例上创建新评论
POST 历史/历史进程实例/{processInstanceId}/comments
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
要为其创建评论的流程实例的 ID。 |
请求正文:
1
2
3
4 {
"message" : "This is a comment.",
"saveProcessInstanceId" : true
}
参数saveProcessInstanceId
是可选的,如果true
保存带有注释的任务的流程实例ID。
成功响应正文:
1
2
3
4
5
6
7
8
9
10 {
"id" : "123",
"taskUrl" : "http://localhost:8081/activiti-rest/service/runtime/tasks/101/comments/123",
"processInstanceUrl" : "http://localhost:8081/activiti-rest/service/history/historic-process-instances/100/comments/123",
"message" : "This is a comment on the task.",
"author" : "kermit",
"time" : "2014-07-13T13:13:52.232+08:00",
"taskId" : "101",
"processInstanceId" : "100"
}
响应代码 | 描述 |
---|---|
201 |
表示评论已创建并返回结果。 |
400 |
表示请求中缺少评论。 |
404 |
表示未找到请求的历史流程实例。 |
13.8.8. 获取有关历史流程实例的所有评论
获取历史/历史进程实例/{processInstanceId}/comments
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
要获取评论的流程实例的 ID。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 [
{
"id" : "123",
"processInstanceUrl" : "http://localhost:8081/activiti-rest/service/history/historic-process-instances/100/comments/123",
"message" : "This is a comment on the task.",
"author" : "kermit",
"time" : "2014-07-13T13:13:52.232+08:00",
"processInstanceId" : "100"
},
{
"id" : "456",
"processInstanceUrl" : "http://localhost:8081/activiti-rest/service/history/historic-process-instances/100/comments/456",
"message" : "This is another comment.",
"author" : "gonzo",
"time" : "2014-07-14T15:16:52.232+08:00",
"processInstanceId" : "100"
}
]
响应代码 | 描述 |
---|---|
200 |
表示已找到流程实例并返回注释。 |
404 |
表示未找到请求的任务。 |
13.8.9. 获取对历史流程实例的评论
获取历史/历史进程实例/{processInstanceId}/comments/{commentId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
要获取评论的历史流程实例的 ID。 |
评论 ID |
是的 |
细绳 |
评论的 ID。 |
成功响应正文:
1
2
3
4
5
6
7
8 {
"id" : "123",
"processInstanceUrl" : "http://localhost:8081/activiti-rest/service/history/historic-process-instances/100/comments/456",
"message" : "This is another comment.",
"author" : "gonzo",
"time" : "2014-07-14T15:16:52.232+08:00",
"processInstanceId" : "100"
}
响应代码 | 描述 |
---|---|
200 |
表示找到历史流程实例和注释并返回注释。 |
404 |
指示未找到请求的历史流程实例或历史流程实例没有具有给定 ID 的注释。 |
13.8.10。删除对历史流程实例的评论
删除历史/历史进程实例/{processInstanceId}/comments/{commentId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
是的 |
细绳 |
要删除评论的历史流程实例的 ID。 |
评论 ID |
是的 |
细绳 |
评论的 ID。 |
响应代码 | 描述 |
---|---|
204 |
表示找到了历史流程实例和注释,并删除了注释。响应主体故意留空。 |
404 |
表示未找到请求的任务或历史流程实例没有具有给定 ID 的注释。 |
13.8.11。获取单个历史任务实例
获取历史/历史任务实例/{taskId}
响应代码 | 描述 |
---|---|
200 |
表示可以找到历史任务实例。 |
404 |
表示找不到历史任务实例。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 {
"id" : "5",
"processDefinitionId" : "oneTaskProcess%3A1%3A4",
"processDefinitionUrl" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"processInstanceId" : "3",
"processInstanceUrl" : "http://localhost:8182/history/historic-process-instances/3",
"executionId" : "4",
"name" : "My task name",
"description" : "My task description",
"deleteReason" : null,
"owner" : "kermit",
"assignee" : "fozzie",
"startTime" : "2013-04-17T10:17:43.902+0000",
"endTime" : "2013-04-18T14:06:32.715+0000",
"durationInMillis" : 86400056,
"workTimeInMillis" : 234890,
"claimTime" : "2013-04-18T11:01:54.715+0000",
"taskDefinitionKey" : "taskKey",
"formKey" : null,
"priority" : 50,
"dueDate" : "2013-04-20T12:11:13.134+0000",
"parentTaskId" : null,
"url" : "http://localhost:8182/history/historic-task-instances/5",
"variables": null,
"tenantId":null
}
13.8.12。获取历史任务实例
获取历史/历史任务实例
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
不 |
细绳 |
历史任务实例的 id。 |
进程实例 ID |
不 |
细绳 |
历史任务实例的流程实例 ID。 |
进程定义键 |
不 |
细绳 |
历史任务实例的流程定义键。 |
processDefinitionKeyLike |
不 |
细绳 |
历史任务实例的流程定义键,与给定值匹配。 |
流程定义 ID |
不 |
细绳 |
历史任务实例的流程定义 ID。 |
流程定义名称 |
不 |
细绳 |
历史任务实例的流程定义名称。 |
流程定义名称Like |
不 |
细绳 |
历史任务实例的流程定义名称,与给定值匹配。 |
流程业务密钥 |
不 |
细绳 |
历史任务实例的流程实例业务键。 |
流程BusinessKeyLike |
不 |
细绳 |
与给定值匹配的历史任务实例的流程实例业务键。 |
执行 ID |
不 |
细绳 |
历史任务实例的执行 ID。 |
任务定义键 |
不 |
细绳 |
流程的任务部分的任务定义键 |
任务名称 |
不 |
细绳 |
历史任务实例的任务名称。 |
任务名称喜欢 |
不 |
细绳 |
历史任务实例的带有like运算符的任务名称。 |
任务描述 |
不 |
细绳 |
历史任务实例的任务描述。 |
任务描述喜欢 |
不 |
细绳 |
历史任务实例的带有类似运算符的任务描述。 |
任务定义键 |
不 |
细绳 |
历史任务实例的流程定义中的任务标识符。 |
任务类别 |
不 |
细绳 |
选择具有给定类别的任务。请注意,这是任务类别,而不是流程定义的类别(BPMN Xml 中的命名空间)。 |
任务删除原因 |
不 |
细绳 |
历史任务实例的任务删除原因。 |
任务删除原因喜欢 |
不 |
细绳 |
历史任务实例的带有like运算符的任务删除原因。 |
任务受让人 |
不 |
细绳 |
历史任务实例的受让人。 |
taskAssigneeLike |
不 |
细绳 |
历史任务实例的具有like运算符的受让人。 |
任务所有者 |
不 |
细绳 |
历史任务实例的所有者。 |
任务所有者喜欢 |
不 |
细绳 |
具有历史任务实例的like运算符的所有者。 |
taskInvolvedUser |
不 |
细绳 |
历史任务实例的相关用户。 |
任务优先 |
不 |
细绳 |
历史任务实例的优先级。 |
完成的 |
不 |
布尔值 |
指示历史任务实例是否已完成。 |
处理完成 |
不 |
布尔值 |
指示历史任务实例的流程实例是否已完成。 |
父任务 ID |
不 |
细绳 |
历史任务实例的可选父任务 ID。 |
截止日期 |
不 |
日期 |
仅返回到期日期等于该日期的历史任务实例。 |
到期日期之后 |
不 |
日期 |
仅返回截止日期在此日期之后的历史任务实例。 |
到期日期之前 |
不 |
日期 |
仅返回截止日期早于该日期的历史任务实例。 |
没有到期日 |
不 |
布尔值 |
仅返回没有截止日期的历史任务实例。当 |
taskCompletedOn |
不 |
日期 |
仅返回在此日期已完成的历史任务实例。 |
任务完成后 |
不 |
日期 |
仅返回在此日期之后已完成的历史任务实例。 |
taskCompletedBefore |
不 |
日期 |
仅返回在此日期之前完成的历史任务实例。 |
taskCreatedOn |
不 |
日期 |
仅返回在此日期创建的历史任务实例。 |
任务创建之前 |
不 |
日期 |
仅返回在此日期之前创建的历史任务实例。 |
任务创建后 |
不 |
日期 |
仅返回在此日期之后创建的历史任务实例。 |
包括TaskLocalVariables |
不 |
布尔值 |
是否也应返回历史任务实例局部变量的指示。 |
包含进程变量 |
不 |
布尔值 |
指示是否还应返回历史任务实例全局变量。 |
租户 ID |
不 |
细绳 |
仅返回具有给定租户 ID 的历史任务实例。 |
租户IdLike |
不 |
细绳 |
仅返回具有给定值的tenantId 的历史任务实例。 |
没有租户 ID |
不 |
布尔值 |
如果 |
响应代码 | 描述 |
---|---|
200 |
表示可以查询历史流程实例。 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 {
"data": [
{
"id" : "5",
"processDefinitionId" : "oneTaskProcess%3A1%3A4",
"processDefinitionUrl" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"processInstanceId" : "3",
"processInstanceUrl" : "http://localhost:8182/history/historic-process-instances/3",
"executionId" : "4",
"name" : "My task name",
"description" : "My task description",
"deleteReason" : null,
"owner" : "kermit",
"assignee" : "fozzie",
"startTime" : "2013-04-17T10:17:43.902+0000",
"endTime" : "2013-04-18T14:06:32.715+0000",
"durationInMillis" : 86400056,
"workTimeInMillis" : 234890,
"claimTime" : "2013-04-18T11:01:54.715+0000",
"taskDefinitionKey" : "taskKey",
"formKey" : null,
"priority" : 50,
"dueDate" : "2013-04-20T12:11:13.134+0000",
"parentTaskId" : null,
"url" : "http://localhost:8182/history/historic-task-instances/5",
"taskVariables": [
{
"name": "test",
"variableScope": "local",
"value": "myTest"
}
],
"processVariables": [
{
"name": "processTest",
"variableScope": "global",
"value": "myProcessTest"
}
],
"tenantId":null
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.8.13。查询历史任务实例
POST 查询/历史任务实例
查询历史任务实例 - 请求正文:
1
2
3
4
5
6
7
8
9
10
11
12
13 {
"processDefinitionId" : "oneTaskProcess%3A1%3A4",
...
"variables" : [
{
"name" : "myVariable",
"value" : 1234,
"operation" : "equals",
"type" : "long"
}
]
}
允许的所有受支持的 JSON 参数字段与为获取历史任务实例集合找到的参数完全相同,但作为 JSON 主体参数而不是 URL 参数传入,以允许更高级的查询并防止 request-uri 的错误太长了。最重要的是,查询允许基于流程变量进行过滤。和属性是 JSON 数组,包含具有此处所述格式的taskVariables
对象。processVariables
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回任务 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 {
"data": [
{
"id" : "5",
"processDefinitionId" : "oneTaskProcess%3A1%3A4",
"processDefinitionUrl" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"processInstanceId" : "3",
"processInstanceUrl" : "http://localhost:8182/history/historic-process-instances/3",
"executionId" : "4",
"name" : "My task name",
"description" : "My task description",
"deleteReason" : null,
"owner" : "kermit",
"assignee" : "fozzie",
"startTime" : "2013-04-17T10:17:43.902+0000",
"endTime" : "2013-04-18T14:06:32.715+0000",
"durationInMillis" : 86400056,
"workTimeInMillis" : 234890,
"claimTime" : "2013-04-18T11:01:54.715+0000",
"taskDefinitionKey" : "taskKey",
"formKey" : null,
"priority" : 50,
"dueDate" : "2013-04-20T12:11:13.134+0000",
"parentTaskId" : null,
"url" : "http://localhost:8182/history/historic-task-instances/5",
"taskVariables": [
{
"name": "test",
"variableScope": "local",
"value": "myTest"
}
],
"processVariables": [
{
"name": "processTest",
"variableScope": "global",
"value": "myProcessTest"
}
],
"tenantId":null
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.8.14。删除历史任务实例
删除历史/历史任务实例/{taskId}
响应代码 | 描述 |
---|---|
200 |
表示历史任务实例已被删除。 |
404 |
表示找不到历史任务实例。 |
13.8.15。获取历史任务实例的标识链接
获取历史/历史任务实例/{taskId}/identitylinks
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回身份链接 |
404 |
表示找不到任务实例。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11 [
{
"type" : "assignee",
"userId" : "kermit",
"groupId" : null,
"taskId" : "6",
"taskUrl" : "http://localhost:8182/history/historic-task-instances/5",
"processInstanceId" : null,
"processInstanceUrl" : null
}
]
13.8.16。获取历史任务实例变量的二进制数据
获取历史/历史任务实例/{taskId}/variables/{variableName}/data
响应代码 | 描述 |
---|---|
200 |
表示已找到任务实例并返回请求的变量数据。 |
404 |
表示未找到请求的任务实例,或者流程实例没有具有给定名称的变量,或者该变量没有可用的二进制流。状态消息提供附加信息。 |
成功响应正文:
响应正文包含变量的二进制值。当变量为 typebinary
时,响应的 content-type 设置为application/octet-stream
,而不管变量的内容或请求的 accept-type 标头。在 的情况下serializable
,application/x-java-serialized-object
用作内容类型。
13.8.17。获取历史活动实例
获取历史/历史活动实例
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
活动 ID |
不 |
细绳 |
活动实例的 ID。 |
活动实例 ID |
不 |
细绳 |
历史活动实例的 id。 |
活动名称 |
不 |
细绳 |
历史活动实例的名称。 |
活动类型 |
不 |
细绳 |
历史活动实例的元素类型。 |
执行 ID |
不 |
细绳 |
历史活动实例的执行 ID。 |
完成的 |
不 |
布尔值 |
指示历史活动实例是否已完成。 |
任务受让人 |
不 |
细绳 |
历史活动实例的受让人。 |
进程实例 ID |
不 |
细绳 |
历史活动实例的流程实例 ID。 |
流程定义 ID |
不 |
细绳 |
历史活动实例的流程定义 ID。 |
租户 ID |
不 |
细绳 |
仅返回具有给定tenantId 的实例。 |
租户IdLike |
不 |
细绳 |
仅返回具有与给定值类似的 tenantId 的实例。 |
没有租户 ID |
不 |
布尔值 |
如果 |
响应代码 | 描述 |
---|---|
200 |
表示可以查询历史活动实例。 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 {
"data": [
{
"id" : "5",
"activityId" : "4",
"activityName" : "My user task",
"activityType" : "userTask",
"processDefinitionId" : "oneTaskProcess%3A1%3A4",
"processDefinitionUrl" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"processInstanceId" : "3",
"processInstanceUrl" : "http://localhost:8182/history/historic-process-instances/3",
"executionId" : "4",
"taskId" : "4",
"calledProcessInstanceId" : null,
"assignee" : "fozzie",
"startTime" : "2013-04-17T10:17:43.902+0000",
"endTime" : "2013-04-18T14:06:32.715+0000",
"durationInMillis" : 86400056,
"tenantId":null
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.8.18。查询历史活动实例
POST 查询/历史活动实例
请求正文:
1
2
3 {
"processDefinitionId" : "oneTaskProcess%3A1%3A4"
}
允许的所有受支持的 JSON 参数字段与为获取历史任务实例集合找到的参数完全相同,但作为 JSON 主体参数而不是 URL 参数传入,以允许更高级的查询并防止 request-uri 的错误太长了。
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回活动 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 {
"data": [
{
"id" : "5",
"activityId" : "4",
"activityName" : "My user task",
"activityType" : "userTask",
"processDefinitionId" : "oneTaskProcess%3A1%3A4",
"processDefinitionUrl" : "http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4",
"processInstanceId" : "3",
"processInstanceUrl" : "http://localhost:8182/history/historic-process-instances/3",
"executionId" : "4",
"taskId" : "4",
"calledProcessInstanceId" : null,
"assignee" : "fozzie",
"startTime" : "2013-04-17T10:17:43.902+0000",
"endTime" : "2013-04-18T14:06:32.715+0000",
"durationInMillis" : 86400056,
"tenantId":null
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.8.19。历史变量实例列表
获取历史/历史变量实例
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
进程实例 ID |
不 |
细绳 |
历史变量实例的流程实例 ID。 |
任务标识 |
不 |
细绳 |
历史变量实例的任务 ID。 |
排除任务变量 |
不 |
布尔值 |
从结果中排除任务变量的指示。 |
变量名称 |
不 |
细绳 |
历史变量实例的变量名。 |
变量名Like |
不 |
细绳 |
对历史变量实例使用like运算符的变量名称。 |
响应代码 | 描述 |
---|---|
200 |
表示可以查询历史变量实例。 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 {
"data": [
{
"id" : "14",
"processInstanceId" : "5",
"processInstanceUrl" : "http://localhost:8182/history/historic-process-instances/5",
"taskId" : "6",
"variable" : {
"name" : "myVariable",
"variableScope", "global",
"value" : "test"
}
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.8.20。查询历史变量实例
POST 查询/历史变量实例
请求正文:
1
2
3
4
5
6
7
8
9
10
11
12
13 {
"processDefinitionId" : "oneTaskProcess%3A1%3A4",
...
"variables" : [
{
"name" : "myVariable",
"value" : 1234,
"operation" : "equals",
"type" : "long"
}
]
}
允许的所有受支持的 JSON 参数字段与为获取历史流程实例集合找到的参数完全相同,但作为 JSON 主体参数而不是 URL 参数传入,以允许更高级的查询并防止 request-uri 的错误太长了。最重要的是,查询允许基于流程变量进行过滤。该variables
属性是一个 JSON 数组,其中包含具有此处所述格式的对象。
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回任务 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 {
"data": [
{
"id" : "14",
"processInstanceId" : "5",
"processInstanceUrl" : "http://localhost:8182/history/historic-process-instances/5",
"taskId" : "6",
"variable" : {
"name" : "myVariable",
"variableScope", "global",
"value" : "test"
}
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
====获取历史任务实例变量的二进制数据
获取历史/历史变量实例/{varInstanceId}/data
响应代码 | 描述 |
---|---|
200 |
表示找到了变量实例并返回了请求的变量数据。 |
404 |
表示未找到请求的变量实例,或者变量实例没有具有给定名称的变量,或者该变量没有可用的二进制流。状态消息提供附加信息。 |
成功响应正文:
响应正文包含变量的二进制值。当变量为 typebinary
时,响应的 content-type 设置为application/octet-stream
,而不管变量的内容或请求的 accept-type 标头。在 的情况下serializable
,application/x-java-serialized-object
用作内容类型。
13.8.21。获取历史细节
获取历史/历史详细信息
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
ID |
不 |
细绳 |
历史详细信息的 ID。 |
进程实例 ID |
不 |
细绳 |
历史详细信息的流程实例 ID。 |
执行 ID |
不 |
细绳 |
历史细节的执行 ID。 |
活动实例 ID |
不 |
细绳 |
历史详细信息的活动实例 ID。 |
任务标识 |
不 |
细绳 |
历史详细信息的任务 ID。 |
selectOnlyFormProperties |
不 |
布尔值 |
指示仅在结果中返回表单属性。 |
selectOnlyVariableUpdates |
不 |
布尔值 |
指示仅在结果中返回变量更新。 |
响应代码 | 描述 |
---|---|
200 |
表示可以查询历史明细。 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 {
"data": [
{
"id" : "26",
"processInstanceId" : "5",
"processInstanceUrl" : "http://localhost:8182/history/historic-process-instances/5",
"executionId" : "6",
"activityInstanceId", "10",
"taskId" : "6",
"taskUrl" : "http://localhost:8182/history/historic-task-instances/6",
"time" : "2013-04-17T10:17:43.902+0000",
"detailType" : "variableUpdate",
"revision" : 2,
"variable" : {
"name" : "myVariable",
"variableScope", "global",
"value" : "test"
},
"propertyId": null,
"propertyValue": null
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.8.22。查询历史详情
POST 查询/历史详细信息
请求正文:
{ “processInstanceId”:“5”, }
允许的所有受支持的 JSON 参数字段与为获取历史流程实例集合找到的参数完全相同,但作为 JSON 主体参数而不是 URL 参数传入,以允许更高级的查询并防止 request-uri 的错误太长了。
响应代码 | 描述 |
---|---|
200 |
表示请求成功并返回历史详细信息 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 {
"data": [
{
"id" : "26",
"processInstanceId" : "5",
"processInstanceUrl" : "http://localhost:8182/history/historic-process-instances/5",
"executionId" : "6",
"activityInstanceId", "10",
"taskId" : "6",
"taskUrl" : "http://localhost:8182/history/historic-task-instances/6",
"time" : "2013-04-17T10:17:43.902+0000",
"detailType" : "variableUpdate",
"revision" : 2,
"variable" : {
"name" : "myVariable",
"variableScope", "global",
"value" : "test"
},
"propertyId" : null,
"propertyValue" : null
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.8.23。获取历史细节变量的二进制数据
获取历史/历史详细信息/{detailId}/数据
响应代码 | 描述 |
---|---|
200 |
表示找到了历史细节实例并返回了请求的变量数据。 |
404 |
表示未找到请求的历史详细信息实例,或者历史详细信息实例没有具有给定名称的变量,或者该变量没有可用的二进制流。状态消息提供附加信息。 |
成功响应正文:
响应正文包含变量的二进制值。当变量为 typebinary
时,响应的 content-type 设置为application/octet-stream
,而不管变量的内容或请求的 accept-type 标头。在 的情况下serializable
,application/x-java-serialized-object
用作内容类型。
13.9。形式
13.9.1. 获取表单数据
获取表单/表单数据
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
任务标识 |
是(如果没有 processDefinitionId) |
细绳 |
需要检索的表单数据对应的任务id。 |
流程定义 ID |
是(如果没有 taskId) |
细绳 |
需要检索的开始事件表单数据对应的流程定义id。 |
响应代码 | 描述 |
---|---|
200 |
表示可以查询表单数据。 |
404 |
表示找不到表单数据。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 {
"data": [
{
"formKey" : null,
"deploymentId" : "2",
"processDefinitionId" : "3",
"processDefinitionUrl" : "http://localhost:8182/repository/process-definition/3",
"taskId" : "6",
"taskUrl" : "http://localhost:8182/runtime/task/6",
"formProperties" : [
{
"id" : "room",
"name" : "Room",
"type" : "string",
"value" : null,
"readable" : true,
"writable" : true,
"required" : true,
"datePattern" : null,
"enumValues" : [
{
"id" : "normal",
"name" : "Normal bed"
},
{
"id" : "kingsize",
"name" : "Kingsize bed"
},
]
}
]
}
],
"total": 1,
"start": 0,
"sort": "name",
"order": "asc",
"size": 1
}
13.9.2. 提交任务表单数据
POST 表单/表单数据
任务表单的请求正文:
1
2
3
4
5
6
7
8
9 {
"taskId" : "5",
"properties" : [
{
"id" : "room",
"value" : "normal"
}
]
}
开始事件表单的请求正文:
1
2
3
4
5
6
7
8
9
10 {
"processDefinitionId" : "5",
"businessKey" : "myKey",
"properties" : [
{
"id" : "room",
"value" : "normal"
}
]
}
响应代码 | 描述 |
---|---|
200 |
表示请求成功并提交了表单数据 |
400 |
表示参数以错误的格式传递。状态消息包含附加信息。 |
启动事件表单数据的成功响应正文(任务表单数据无响应):
1
2
3
4
5
6
7
8
9 {
"id" : "5",
"url" : "http://localhost:8182/history/historic-process-instances/5",
"businessKey" : "myKey",
"suspended": false,
"processDefinitionId" : "3",
"processDefinitionUrl" : "http://localhost:8182/repository/process-definition/3",
"activityId" : "myTask"
}
13.10。数据库表
13.10.1。表列表
GET 管理/表格
响应代码 | 描述 |
---|---|
200 |
表示请求成功。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13 [
{
"name":"ACT_RU_VARIABLE",
"url":"http://localhost:8182/management/tables/ACT_RU_VARIABLE",
"count":4528
},
{
"name":"ACT_RU_EVENT_SUBSCR",
"url":"http://localhost:8182/management/tables/ACT_RU_EVENT_SUBSCR",
"count":3
}
]
13.10.2。获取单个表
获取管理/表/{tableName}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
表名 |
是的 |
细绳 |
要获取的表的名称。 |
成功响应正文:
1
2
3
4
5 {
"name":"ACT_RE_PROCDEF",
"url":"http://localhost:8182/management/tables/ACT_RE_PROCDEF",
"count":60
}
响应代码 | 描述 |
---|---|
200 |
表示表存在并返回表计数。 |
404 |
表示请求的表不存在。 |
13.10.3。获取单个表的列信息
获取管理/表/{tableName}/列
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
表名 |
是的 |
细绳 |
要获取的表的名称。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 {
"tableName":"ACT_RU_VARIABLE",
"columnNames":[
"ID_",
"REV_",
"TYPE_",
"NAME_"
],
"columnTypes":[
"VARCHAR",
"INTEGER",
"VARCHAR",
"VARCHAR"
]
}
响应代码 | 描述 |
---|---|
200 |
表示表存在,返回表列信息。 |
404 |
表示请求的表不存在。 |
13.10.4。获取单个表的行数据
获取管理/表/{tableName}/数据
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
表名 |
是的 |
细绳 |
要获取的表的名称。 |
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
开始 |
不 |
整数 |
要获取的第一行的索引。默认为 0。 |
尺寸 |
不 |
整数 |
要获取的行数,从 开始 |
升序列 |
不 |
细绳 |
对结果行进行排序的列的名称,升序。 |
降序列 |
不 |
细绳 |
对结果行进行降序排序的列的名称。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 {
"total":3,
"start":0,
"sort":null,
"order":null,
"size":3,
"data":[
{
"TASK_ID_":"2",
"NAME_":"var1",
"REV_":1,
"TEXT_":"123",
"LONG_":123,
"ID_":"3",
"TYPE_":"integer"
}
]
}
响应代码 | 描述 |
---|---|
200 |
表示表存在并返回表行数据。 |
404 |
表示请求的表不存在。 |
13.11。引擎
13.11.1。获取引擎属性
GET 管理/属性
返回引擎内部使用的属性的只读视图。
成功响应正文:
1
2
3
4
5 {
"next.dbid":"101",
"schema.history":"create(5.15)",
"schema.version":"5.15"
}
响应代码 | 描述 |
---|---|
200 |
表示返回的属性。 |
13.11.2。获取引擎信息
GET 管理/引擎
返回此 REST 服务中使用的引擎的只读视图。
成功响应正文:
1
2
3
4
5
6 {
"name":"default",
"version":"5.15",
"resourceUrl":"file://activiti/activiti.cfg.xml",
"exception":null
}
响应代码 | 描述 |
---|---|
200 |
表示返回引擎信息。 |
13.12。运行
13.12.1。收到信号事件
POST 运行时/信号
通知引擎已收到信号事件,与特定执行没有明确相关。
正文 JSON:
1
2
3
4
5
6
7
8
9 {
"signalName": "My Signal",
"tenantId" : "execute",
"async": true,
"variables": [
{"name": "testVar", "value": "This is a string"}
]
}
范围 | 描述 | 必需的 |
---|---|---|
信号名称 |
信号名称 |
是的 |
租户 ID |
应处理信号事件的租户 ID |
不 |
异步 |
如果 |
不 |
变量 |
用作与信号一起传递的有效负载的变量数组(采用通用变量格式)。 |
不 |
成功响应正文:
响应代码 | 描述 |
---|---|
200 |
指示的信号已被处理并且没有发生错误。 |
202 |
指示信号处理作为作业排队等待执行。 |
400 |
信号未处理。信号名称丢失或变量与异步一起使用,这是不允许的。响应正文包含有关错误的附加信息。 |
13.13。工作
13.13.1. 找一份工作
获取管理/工作/{jobId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
工作编号 |
是的 |
细绳 |
要获取的作业的 ID。 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 {
"id":"8",
"url":"http://localhost:8182/management/jobs/8",
"processInstanceId":"5",
"processInstanceUrl":"http://localhost:8182/runtime/process-instances/5",
"processDefinitionId":"timerProcess:1:4",
"processDefinitionUrl":"http://localhost:8182/repository/process-definitions/timerProcess%3A1%3A4",
"executionId":"7",
"executionUrl":"http://localhost:8182/runtime/executions/7",
"retries":3,
"exceptionMessage":null,
"dueDate":"2013-06-04T22:05:05.474+0000",
"tenantId":null
}
响应代码 | 描述 |
---|---|
200 |
表示作业存在并返回。 |
404 |
表示请求的作业不存在。 |
13.13.2。删除作业
删除管理/工作/{jobId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
工作编号 |
是的 |
细绳 |
要删除的作业的 ID。 |
响应代码 | 描述 |
---|---|
204 |
表示作业已找到并已被删除。响应体故意为空。 |
404 |
表示未找到请求的作业。 |
13.13.3。执行单个作业
POST 管理/工作/{jobId}
正文 JSON:
1
2
3 {
"action" : "execute"
}
范围 | 描述 | 必需的 |
---|---|---|
行动 |
要执行的动作。仅 |
是的 |
响应代码 | 描述 |
---|---|
204 |
表示作业已执行。响应体故意为空。 |
404 |
表示未找到请求的作业。 |
500 |
表示执行作业时发生异常。状态描述包含有关错误的其他详细信息。如果需要,可以稍后获取完整的错误堆栈跟踪。 |
13.13.4。获取作业的异常堆栈跟踪
获取管理/jobs/{jobId}/exception-stacktrace
范围 | 描述 | 必需的 |
---|---|---|
工作编号 |
要获取堆栈跟踪的作业的 ID。 |
是的 |
响应代码 | 描述 |
---|---|
200 |
表示未找到请求的作业并且堆栈跟踪已返回。响应包含原始堆栈跟踪,并且始终具有 Content-type |
404 |
表示未找到请求的作业或作业没有异常堆栈跟踪。状态描述包含有关错误的附加信息。 |
13.13.5。获取工作列表
获取管理/工作
范围 | 描述 | 类型 |
---|---|---|
ID |
仅返回具有给定 ID 的作业 |
细绳 |
进程实例 ID |
仅返回具有给定 ID 的进程的作业部分 |
细绳 |
执行 ID |
仅返回具有给定 id 的执行部分的作业 |
细绳 |
流程定义 ID |
仅返回具有给定流程定义 ID 的作业 |
细绳 |
withRetriesLeft |
如果 |
布尔值 |
可执行的 |
如果 |
布尔值 |
仅限定时器 |
如果 |
布尔值 |
仅消息 |
如果 |
布尔值 |
有异常 |
如果 |
布尔值 |
到期前 |
仅返回应在给定日期之前执行的作业。永远不会使用此参数返回没有到期日期的作业。 |
日期 |
到期后 |
仅返回应在给定日期之后执行的作业。永远不会使用此参数返回没有到期日期的作业。 |
日期 |
异常消息 |
仅返回具有给定异常消息的作业 |
细绳 |
租户 ID |
不 |
细绳 |
仅返回具有给定租户 ID 的作业。 |
租户IdLike |
不 |
细绳 |
仅返回具有给定值的tenantId 的作业。 |
没有租户 ID |
不 |
布尔值 |
如果 |
种类 |
对结果进行排序的字段应为、 |
细绳 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 {
"data":[
{
"id":"13",
"url":"http://localhost:8182/management/jobs/13",
"processInstanceId":"5",
"processInstanceUrl":"http://localhost:8182/runtime/process-instances/5",
"processDefinitionId":"timerProcess:1:4",
"processDefinitionUrl":"http://localhost:8182/repository/process-definitions/timerProcess%3A1%3A4",
"executionId":"12",
"executionUrl":"http://localhost:8182/runtime/executions/12",
"retries":0,
"exceptionMessage":"Can't find scripting engine for 'unexistinglanguage'",
"dueDate":"2013-06-07T10:00:24.653+0000",
"tenantId":null
}
],
"total":2,
"start":0,
"sort":"id",
"order":"asc",
"size":2
}
响应代码 | 描述 |
---|---|
200 |
表示请求的作业已返回。 |
400 |
指示在 url 查询参数或两者中使用了非法值并 |
13.14。用户
13.14.1。获取单个用户
获取身份/用户/{userId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
用户身份 |
是的 |
细绳 |
要获取的用户的 id。 |
成功响应正文:
1
2
3
4
5
6
7 {
"id":"testuser",
"firstName":"Fred",
"lastName":"McDonald",
"url":"http://localhost:8182/identity/users/testuser",
"email":"no-reply@activiti.org"
}
响应代码 | 描述 |
---|---|
200 |
表示用户存在并返回。 |
404 |
表示请求的用户不存在。 |
13.14.2。获取用户列表
获取身份/用户
范围 | 描述 | 类型 |
---|---|---|
ID |
只返回给定 id 的用户 |
细绳 |
名 |
只返回给定名字的用户 |
细绳 |
姓 |
只返回给定姓氏的用户 |
细绳 |
电子邮件 |
仅返回具有给定电子邮件的用户 |
细绳 |
名字喜欢 |
只返回名字与给定值类似的用户。用作 |
细绳 |
姓氏喜欢 |
仅返回姓氏与给定值类似的用户。用作 |
细绳 |
电子邮件喜欢 |
仅返回具有给定值的电子邮件的用户。用作 |
细绳 |
组成员 |
仅返回作为给定组成员的用户。 |
细绳 |
潜在启动器 |
仅返回具有给定 ID 的流程定义的潜在启动者的用户。 |
细绳 |
种类 |
对结果进行排序的字段应为 |
细绳 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 {
"data":[
{
"id":"anotherUser",
"firstName":"Tijs",
"lastName":"Barrez",
"url":"http://localhost:8182/identity/users/anotherUser",
"email":"no-reply@alfresco.org"
},
{
"id":"kermit",
"firstName":"Kermit",
"lastName":"the Frog",
"url":"http://localhost:8182/identity/users/kermit",
"email":null
},
{
"id":"testuser",
"firstName":"Fred",
"lastName":"McDonald",
"url":"http://localhost:8182/identity/users/testuser",
"email":"no-reply@activiti.org"
}
],
"total":3,
"start":0,
"sort":"id",
"order":"asc",
"size":3
}
响应代码 | 描述 |
---|---|
200 |
表示返回了请求的用户。 |
13.14.3。更新用户
PUT 身份/用户/{userId}
正文 JSON:
1
2
3
4
5
6 {
"firstName":"Tijs",
"lastName":"Barrez",
"email":"no-reply@alfresco.org",
"password":"pass123"
}
所有请求值都是可选的。例如,您只能在请求体 JSON-object 中包含firstName属性,只更新用户的 firstName,而其他所有字段不受影响。当一个属性被显式包含并设置为 null 时,用户值将被更新为 null。示例:{"firstName" : null}
将清除用户的名字)。
响应代码 | 描述 |
---|---|
200 |
表示用户已更新。 |
404 |
表示未找到请求的用户。 |
409 |
表示请求的用户已同时更新。 |
成功响应正文:请参阅 的响应identity/users/{userId}
。
13.14.4。创建用户
POST 身份/用户
正文 JSON:
{ "id":"tijs", "firstName":"Tijs", "lastName":"巴雷兹", "电子邮件":"no-reply@alfresco.org", “密码”:“pass123” }
响应代码 | 描述 |
---|---|
201 |
表示用户已创建。 |
400 |
表示用户的 id 丢失。 |
成功响应正文:请参阅 的响应identity/users/{userId}
。
13.14.5。删除用户
删除身份/用户/{userId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
用户身份 |
是的 |
细绳 |
要删除的用户的 ID。 |
响应代码 | 描述 |
---|---|
204 |
表示用户已找到并已被删除。响应体故意为空。 |
404 |
表示未找到请求的用户。 |
13.14.6。获取用户图片
获取身份/用户/{userId}/图片
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
用户身份 |
是的 |
细绳 |
要为其获取图片的用户 ID。 |
回复正文:
响应正文包含原始图片数据,代表用户的图片。响应的 Content-type 对应于创建图片时设置的 mimeType。
响应代码 | 描述 |
---|---|
200 |
表示找到用户并有图片,在正文中返回。 |
404 |
表示未找到请求的用户或用户没有个人资料图片。状态描述包含有关错误的附加信息。 |
13.14.7。更新用户的图片
获取身份/用户/{userId}/图片
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
用户身份 |
是的 |
细绳 |
要为其获取图片的用户 ID。 |
请求正文:
请求应该是类型multipart/form-data
。图片的二进制值中应该包含一个文件部分。最重要的是,可以存在以下附加表单字段:
-
mimeType
:上传图片的可选mime-type。如果省略,则默认的 ofimage/jpeg
用作图片的 mime 类型。
响应代码 | 描述 |
---|---|
200 |
表示已找到用户且图片已更新。响应主体故意留空。 |
404 |
表示未找到请求的用户。 |
13.14.8。列出用户的信息
PUT 身份/用户/{userId}/信息
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
用户身份 |
是的 |
细绳 |
要获取信息的用户的 ID。 |
回复正文:
1
2
3
4
5
6
7
8
9
10 [
{
"key":"key1",
"url":"http://localhost:8182/identity/users/testuser/info/key1"
},
{
"key":"key2",
"url":"http://localhost:8182/identity/users/testuser/info/key2"
}
]
响应代码 | 描述 |
---|---|
200 |
表示已找到用户并返回信息列表(键和 url)。 |
404 |
表示未找到请求的用户。 |
13.14.9。获取用户信息
获取身份/用户/{userId}/info/{key}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
用户身份 |
是的 |
细绳 |
要获取信息的用户的 ID。 |
钥匙 |
是的 |
细绳 |
要获取的用户信息的密钥。 |
回复正文:
1
2
3
4
5 {
"key":"key1",
"value":"Value 1",
"url":"http://localhost:8182/identity/users/testuser/info/key1"
}
响应代码 | 描述 |
---|---|
200 |
表示找到了用户并且用户有给定键的信息.. |
404 |
表示未找到请求的用户或用户没有给定键的信息。状态描述包含有关错误的附加信息。 |
13.14.10。更新用户信息
PUT 身份/用户/{userId}/info/{key}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
用户身份 |
是的 |
细绳 |
要为其更新信息的用户 ID。 |
钥匙 |
是的 |
细绳 |
要更新的用户信息的键。 |
请求正文:
1
2
3 {
"value":"The updated value"
}
回复正文:
1
2
3
4
5 {
"key":"key1",
"value":"The updated value",
"url":"http://localhost:8182/identity/users/testuser/info/key1"
}
响应代码 | 描述 |
---|---|
200 |
表示已找到用户并且信息已更新。 |
400 |
指示请求正文中缺少该值。 |
404 |
表示未找到请求的用户或用户没有给定键的信息。状态描述包含有关错误的附加信息。 |
13.14.11。创建新用户的信息条目
POST 身份/用户/{userId}/信息
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
用户身份 |
是的 |
细绳 |
要为其创建信息的用户 ID。 |
请求正文:
1
2
3
4 {
"key":"key1",
"value":"The value"
}
回复正文:
1
2
3
4
5 {
"key":"key1",
"value":"The value",
"url":"http://localhost:8182/identity/users/testuser/info/key1"
}
响应代码 | 描述 |
---|---|
201 |
表示已找到用户并已创建信息。 |
400 |
表示请求正文中缺少键或值。状态描述包含有关错误的附加信息。 |
404 |
表示未找到请求的用户。 |
409 |
表示已经有一个具有给定键的用户信息条目,更新资源实例 ( |
13.14.12。删除用户信息
删除身份/用户/{userId}/info/{key}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
用户身份 |
是的 |
细绳 |
要删除其信息的用户的 ID。 |
钥匙 |
是的 |
细绳 |
要删除的用户信息的键。 |
响应代码 | 描述 |
---|---|
204 |
表示已找到用户并且给定密钥的信息已被删除。响应主体故意留空。 |
404 |
表示未找到请求的用户或用户没有给定键的信息。状态描述包含有关错误的附加信息。 |
13.15。团体
13.15.1。获取单个组
获取身份/组/{groupId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
组 ID |
是的 |
细绳 |
要获取的组的 id。 |
成功响应正文:
1
2
3
4
5
6 {
"id":"testgroup",
"url":"http://localhost:8182/identity/groups/testgroup",
"name":"Test group",
"type":"Test type"
}
响应代码 | 描述 |
---|---|
200 |
表示组存在并返回。 |
404 |
表示请求的组不存在。 |
13.15.2。获取组列表
获取身份/组
范围 | 描述 | 类型 |
---|---|---|
ID |
仅返回具有给定 ID 的组 |
细绳 |
名称 |
仅返回具有给定名称的组 |
细绳 |
类型 |
只返回给定类型的组 |
细绳 |
名字喜欢 |
仅返回名称与给定值类似的组。用作 |
细绳 |
成员 |
仅返回具有给定用户名的成员的组。 |
细绳 |
潜在启动器 |
仅返回成员是具有给定 id 的流程定义的潜在启动者的组。 |
细绳 |
种类 |
对结果进行排序的字段应该是 |
细绳 |
成功响应正文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 {
"data":[
{
"id":"testgroup",
"url":"http://localhost:8182/identity/groups/testgroup",
"name":"Test group",
"type":"Test type"
}
],
"total":3,
"start":0,
"sort":"id",
"order":"asc",
"size":3
}
响应代码 | 描述 |
---|---|
200 |
表示已返回请求的组。 |
13.15.3。更新组
PUT 身份/组/{groupId}
正文 JSON:
1
2
3
4 {
"name":"Test group",
"type":"Test type"
}
所有请求值都是可选的。例如,您只能在请求体 JSON-object 中包含name属性,只更新组的名称,而其他所有字段不受影响。当一个属性被显式包含并设置为空时,组值将被更新为空。
响应代码 | 描述 |
---|---|
200 |
表示组已更新。 |
404 |
表示未找到请求的组。 |
409 |
表示请求的组已同时更新。 |
成功响应正文:请参阅 的响应identity/groups/{groupId}
。
13.15.4。创建群组
POST 身份/组
正文 JSON:
1
2
3
4
5 {
"id":"testgroup",
"name":"Test group",
"type":"Test type"
}
响应代码 | 描述 |
---|---|
201 |
表示组已创建。 |
400 |
表示缺少组的 id。 |
成功响应正文:请参阅 的响应identity/groups/{groupId}
。
13.15.5。删除组
删除身份/组/{groupId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
组 ID |
是的 |
细绳 |
要删除的组的 ID。 |
响应代码 | 描述 |
---|---|
204 |
表示已找到该组并已被删除。响应体故意为空。 |
404 |
表示未找到请求的组。 |
13.15.6。获取组中的成员
上不允许 GET identity/groups/members
。使用identity/users?memberOfGroup=sales
URL 获取属于特定组的所有用户。
13.15.7。将成员添加到组
POST 身份/组/{groupId}/成员
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
组 ID |
是的 |
细绳 |
要将成员添加到的组的 ID。 |
正文 JSON:
1
2
3 {
"userId":"kermit"
}
响应代码 | 描述 |
---|---|
201 |
表示已找到组并且已添加成员。 |
404 |
指示 userId 未包含在请求正文中。 |
404 |
表示未找到请求的组。 |
409 |
表示请求的用户已经是该组的成员。 |
回复正文:
1
2
3
4
5 {
"userId":"kermit",
"groupId":"sales",
"url":"http://localhost:8182/identity/groups/sales/members/kermit"
}
13.15.8。从组中删除成员
删除身份/组/{groupId}/members/{userId}
范围 | 必需的 | 价值 | 描述 |
---|---|---|---|
组 ID |
是的 |
细绳 |
要从中删除成员的组的 ID。 |
用户身份 |
是的 |
细绳 |
要删除的用户的 ID。 |
响应代码 | 描述 |
---|---|
204 |
表示已找到该组并且该成员已被删除。响应主体故意留空。 |
404 |
表示未找到请求的组或用户不是该组的成员。状态描述包含有关错误的附加信息。 |
回复正文:
1
2
3
4
5 {
"userId":"kermit",
"groupId":"sales",
"url":"http://localhost:8182/identity/groups/sales/members/kermit"
}
14. CDI 集成
activiti-cdi 模块利用了 Activiti 的可配置性和 cdi 的可扩展性。activiti-cdi 最突出的特点是:
-
支持@BusinessProcessScoped bean(其生命周期绑定到流程实例的 Cdi bean),
-
用于从流程中解析 Cdi bean(包括 EJB)的自定义 El-Resolver,
-
使用注释对流程实例进行声明式控制,
-
Activiti 连接到 cdi 事件总线,
-
适用于 Java EE 和 Java SE,适用于 Spring,
-
支持单元测试。
1
2
3
4
5 <dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-cdi</artifactId>
<version>5.x</version>
</dependency>
14.1. 设置activiti-cdi
Activiti cdi 可以在不同的环境中设置。在本节中,我们将简要介绍配置选项。
14.1.1. 查找流程引擎
cdi 扩展需要访问 ProcessEngine。为此,org.activiti.cdi.spi.ProcessEngineLookup
在运行时查找接口的实现。cdi 模块附带一个名为 的默认实现org.activiti.cdi.impl.LocalProcessEngineLookup
,它使用ProcessEngines
-Utility 类来查找 ProcessEngine。在默认配置ProcessEngines#NAME_DEFAULT
中用于查找 ProcessEngine。此类可能被子类化以设置自定义名称。注意:需要activiti.cfg.xml
在类路径上进行配置。
Activiti cdi 使用 java.util.ServiceLoader SPI 来解析org.activiti.cdi.spi.ProcessEngineLookup
. 为了提供接口的自定义实现,我们需要在 META-INF/services/org.activiti.cdi.spi.ProcessEngineLookup
我们的部署中添加一个名为的纯文本文件,我们在其中指定实现的完全限定类名。
如果您不提供自定义 |
14.1.2. 配置流程引擎
配置取决于所选的 ProcessEngineLookup-Strategy(参见上一节)。在这里,我们关注与 LocalProcessEngineLookup 结合可用的配置选项,这需要我们在类路径上提供一个 Spring activiti.cfg.xml 文件。
Activiti 提供不同的 ProcessEngineConfiguration 实现,主要依赖于底层事务管理策略。activiti-cdi 模块不关心事务,这意味着可以使用任何事务管理策略(甚至是 Spring 事务抽象)。为方便起见,cdi-module 提供了两个自定义 ProcessEngineConfiguration 实现:
-
org.activiti.cdi.CdiJtaProcessEngineConfiguration
: activiti JtaProcessEngineConfiguration 的子类,如果 JTA 管理的事务应该用于 Activiti,则可以使用 -
org.activiti.cdi.CdiStandaloneProcessEngineConfiguration
: activiti StandaloneProcessEngineConfiguration 的子类,如果应该为 Activiti 使用普通的 JDBC 事务,则可以使用。以下是 JBoss 7 的示例 activiti.cfg.xml 文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 <?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- lookup the JTA-Transaction manager -->
<bean id="transactionManager" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:jboss/TransactionManager"></property>
<property name="resourceRef" value="true" />
</bean>
<!-- process engine configuration -->
<bean id="processEngineConfiguration"
class="org.activiti.cdi.CdiJtaProcessEngineConfiguration">
<!-- lookup the default Jboss datasource -->
<property name="dataSourceJndiName" value="java:jboss/datasources/ExampleDS" />
<property name="databaseType" value="h2" />
<property name="transactionManager" ref="transactionManager" />
<!-- using externally managed transactions -->
<property name="transactionsExternallyManaged" value="true" />
<property name="databaseSchemaUpdate" value="true" />
</bean>
</beans>
这就是 Glassfish 3.1.1 的样子(假设正确配置了名为 jdbc/activiti 的数据源):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 <?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- lookup the JTA-Transaction manager -->
<bean id="transactionManager" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:appserver/TransactionManager"></property>
<property name="resourceRef" value="true" />
</bean>
<!-- process engine configuration -->
<bean id="processEngineConfiguration"
class="org.activiti.cdi.CdiJtaProcessEngineConfiguration">
<property name="dataSourceJndiName" value="jdbc/activiti" />
<property name="transactionManager" ref="transactionManager" />
<!-- using externally managed transactions -->
<property name="transactionsExternallyManaged" value="true" />
<property name="databaseSchemaUpdate" value="true" />
</bean>
</beans>
注意上面的配置需要“spring-context”模块:
1
2
3
4
5 <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.0.3.RELEASE</version>
</dependency>
Java SE 环境中的配置与创建 ProcessEngine部分中提供的示例完全相同,将“CdiStandaloneProcessEngineConfiguration”替换为“StandaloneProcessEngineConfiguration”。
14.1.3. 部署流程
可以使用标准的 activiti-api ( RepositoryService
) 部署流程。此外,activiti-cdi 提供了自动部署列在processes.xml
类路径顶级文件中的进程的可能性。这是一个示例 processes.xml 文件:
1
2
3
4
5
6 <?xml version="1.0" encoding="utf-8" ?>
<!-- list the processes to be deployed -->
<processes>
<process resource="diagrams/myProcess.bpmn20.xml" />
<process resource="diagrams/myOtherProcess.bpmn20.xml" />
</processes>
===使用 CDI 执行上下文流程
在本节中,我们简要介绍 Activiti cdi 扩展使用的上下文流程执行模型。BPMN 业务流程通常是一个长时间运行的交互,由用户和系统任务组成。在运行时,一个进程被分成一组单独的工作单元,由用户和/或应用程序逻辑执行。在 activiti-cdi 中,一个流程实例可以与一个 cdi 范围相关联,该关联代表一个工作单元。这在工作单元很复杂的情况下特别有用,例如,如果 UserTask 的实现是不同形式的复杂序列,并且在此交互过程中需要保持“非进程范围”状态。
在默认配置中,流程实例与“最广泛”的活动范围相关联,从对话开始,如果对话上下文不活动,则回退到请求。
14.1.4. 将对话与流程实例相关联
在解析 @BusinessProcessScoped bean 或注入流程变量时,我们依赖于活动 cdi 范围和流程实例之间的现有关联。Activiti-cdi 提供了org.activiti.cdi.BusinessProcess
用于控制关联的 bean,最突出的是:
-
startProcessBy (... )方法,镜像 Activiti 公开的相应方法,
RuntimeService
允许启动并随后关联业务流程, -
resumeProcessById(String processInstanceId)
,允许将流程实例与提供的 id 相关联, -
resumeTaskById(String taskId)
,允许将任务与提供的 id 相关联(以及相应的流程实例),
一旦完成了一个工作单元(例如 UserTask),completeTask()
就可以调用该方法来解除会话/请求与流程实例的关联。这向 Activiti 发出当前任务已完成的信号并使流程实例继续进行。
请注意,BusinessProcess
-bean 是一个@Named
bean,这意味着可以使用表达式语言(例如从 JSF 页面)调用公开的方法。以下 JSF2 片段开始一个新对话并将其与用户任务实例相关联,其 id 作为请求参数(例如pageName.jsf?taskId=XX
)传递:
1
2
3
4 <f:metadata>
<f:viewParam name="taskId" />
<f:event type="preRenderView" listener="#{businessProcess.startTask(taskId, true)}" />
</f:metadata>
14.1.5. 声明式控制过程
Activiti-cdi 允许以声明方式启动流程实例并使用注释完成任务。注释允许通过 @org.activiti.cdi.annotation.StartProcess
“key”或“name”启动流程实例。请注意,流程实例是在带注释的方法返回后启动的。例子:
1
2
3
4
5 @StartProcess("authorizeBusinessTripRequest")
public String submitRequest(BusinessTripRequest request) {
// do some work
return "success";
}
根据 Activiti 的配置,被注解的方法的代码和流程实例的启动会合并在同一个事务中。-annotation@org.activiti.cdi.annotation.CompleteTask
的工作方式相同:
1
2
3
4
5 @CompleteTask(endConversation=false)
public String authorizeBusinessTrip() {
// do some work
return "success";
}
@CompleteTask
注释提供了结束当前对话的可能性。默认行为是在对 Activiti 的调用返回后结束对话。如上例所示,可以禁用结束对话。
14.1.6. 从进程中引用 Bean
Activiti-cdi 使用自定义解析器向 Activiti El 公开 CDI bean。这使得从进程中引用 bean 成为可能:
1
2 <userTask id="authorizeBusinessTrip" name="Authorize Business Trip"
activiti:assignee="#{authorizingManager.account.username}" />
其中“authorizingManager”可以是生产者方法提供的 bean:
1
2
3
4
5
6
7
8
9
10 @Inject @ProcessVariable Object businessTripRequesterUsername;
@Produces
@Named
public Employee authorizingManager() {
TypedQuery<Employee> query = entityManager.createQuery("SELECT e FROM Employee e WHERE e.account.username='"
+ businessTripRequesterUsername + "'", Employee.class);
Employee employee = query.getSingleResult();
return employee.getManager();
}
我们可以使用相同的功能在服务任务中调用 EJB 的业务方法,使用activiti:expression="myEjb.method()"
-extension。请注意,这需要在-class@Named
上添加 -annotation 。MyEjb
14.1.7. 使用 @BusinessProcessScoped bean
使用 activiti-cdi,可以将 bean 的生命周期绑定到流程实例。为此,提供了一个自定义上下文实现,即 BusinessProcessContext。BusinessProcessScoped bean 的实例作为流程变量存储在当前流程实例中。BusinessProcessScoped bean 需要是 PassivationCapable(例如 Serializable)。以下是流程范围 bean 的示例:
1
2
3
4
5
6
7
8 @Named
@BusinessProcessScoped
public class BusinessTripRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String startDate;
private String endDate;
// ...
}
有时,我们希望在没有与流程实例关联的情况下使用流程范围的 bean,例如在启动流程之前。如果当前没有流程实例处于活动状态,则 BusinessProcessScoped bean 的实例将临时存储在本地范围(即对话或请求,具体取决于上下文。如果此范围稍后与业务流程实例相关联,则将 bean 实例刷新到流程实例。
14.1.8. 注入过程变量
过程变量可用于注入。Activiti-CDI 支持
-
@BusinessProcessScoped
使用类型安全的 bean注入@Inject \[additional qualifiers\] Type fieldName
-
@ProcessVariable(name?)
使用限定符不安全地注入其他过程变量:
1
2 @Inject @ProcessVariable Object accountNumber;
@Inject @ProcessVariable("accountNumber") Object account
为了使用 EL 引用过程变量,我们有类似的选项:
-
@Named @BusinessProcessScoped
bean 可以直接引用, -
可以使用
ProcessVariables
-bean 引用其他流程变量:
#{processVariables['accountNumber']}
14.1.9. 接收进程事件
Activiti 可以连接到 CDI 事件总线。这允许我们使用标准 CDI 事件机制来通知流程事件。为了启用对 Activiti 的 CDI 事件支持,请在配置中启用相应的解析侦听器:
1
2
3
4
5 <property name="postBpmnParseHandlers">
<list>
<bean class="org.activiti.cdi.impl.event.CdiEventSupportBpmnParseHandler" />
</list>
</property>
现在 Activiti 配置为使用 CDI 事件总线发布事件。下面概述了如何在 CDI bean 中接收流程事件。在 CDI 中,我们可以使用@Observes
-annotation 以声明方式指定事件观察者。事件通知是类型安全的。流程事件的类型是org.activiti.cdi.BusinessProcessEvent
。下面是一个简单的事件观察器方法的例子:
1
2
3 public void onProcessEvent(@Observes BusinessProcessEvent businessProcessEvent) {
// handle event
}
该观察者将被通知所有事件。如果我们想限制观察者接收的事件集,我们可以添加限定符注释:
-
@BusinessProcess
:将事件集限制为某个流程定义。例子:@Observes @BusinessProcess("billingProcess") BusinessProcessEvent evt
-
@StartActivity
:通过某个活动限制事件集。例如:@Observes @StartActivity("shipGoods") BusinessProcessEvent evt
每当输入 ID 为“shipGoods”的活动时调用。 -
@EndActivity
:通过某个活动限制事件集。例如:@Observes @EndActivity("shipGoods") BusinessProcessEvent evt
每当留下一个 id 为“shipGoods”的活动时调用。 -
@TakeTransition
:通过某个转换限制事件集。 -
@CreateTask
:通过特定任务的创建来限制事件集。 -
@DeleteTask
:通过某个任务的删除来限制事件集。 -
@AssignTask
:通过特定任务的分配限制事件集。 -
@CompleteTask
:通过某个任务的完成来限制事件集。
上面提到的限定词可以自由组合。例如,为了接收在“shipmentProcess”中离开“shipGoods”活动时产生的所有事件,我们可以编写以下观察者方法:
1
2
3 public void beforeShippingGoods(@Observes @BusinessProcess("shippingProcess") @EndActivity("shipGoods") BusinessProcessEvent evt) {
// handle event
}
在默认配置中,事件侦听器在同一事务的上下文中同步调用。CDI 事务观察者(仅与 JavaEE / EJB 结合使用)允许控制何时将事件交给观察者方法。例如,使用事务观察者,我们可以确保只有在触发事件的事务成功时才会通知观察者:
1
2
3 public void onShipmentSuceeded(@Observes(during=TransactionPhase.AFTER_SUCCESS) @BusinessProcess("shippingProcess") @EndActivity("shipGoods") BusinessProcessEvent evt) {
// send email to customer.
}
14.1.10。附加功能
-
ProcessEngine 以及服务可用于注入:
@Inject ProcessEngine, RepositoryService, TaskService
,... -
可以注入当前流程实例和任务:
@Inject ProcessInstance, Task
, -
可以注入当前业务密钥:
@Inject @BusinessKey String businessKey
, -
当前流程实例 id 被注入:
@Inject @ProcessInstanceId String pid
,
14.2. 已知限制
尽管 activiti-cdi 是针对 SPI 实现的,并且设计为“便携式扩展”,但它仅使用 Weld 进行测试。
15. LDAP 集成
公司通常已经拥有 LDAP 系统形式的用户和组存储。从 5.14 版本开始,Activiti 提供了一个开箱即用的解决方案,用于轻松配置 Activiti 应如何连接 LDAP 系统。
在 Activiti 5.14 之前,已经可以将 LDAP 与 Activiti 集成。但是,从 5.14 开始,配置已经简化了很多。但是,配置 LDAP 的旧方法仍然有效。更具体地说,简化的配置只是旧基础设施之上的一个包装器。
15.1. 用法
要将 LDAP 集成代码添加到您的项目,只需将以下依赖项添加到您的 pom.xml:
1
2
3
4
5 <dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-ldap</artifactId>
<version>latest.version</version>
</dependency>
15.2. 用例
LDAP 集成目前有两个主要用例:
-
允许通过 IdentityService 进行身份验证。这在通过 IdentityService 执行所有操作时可能很有用。
-
获取用户的组。例如,当查询任务以查看某个用户可以看到哪些任务(即具有候选组的任务)时,这一点很重要。
15.3. 配置
将 LDAP 系统与 Activiti 集成是通过在流程引擎配置部分中注入的实例来完成org.activiti.ldap.LDAPConfigurator
的configurators
。此类具有高度可扩展性:如果默认实现不适合用例,则可以轻松覆盖方法,并且可以插入许多依赖 bean。
这是一个示例配置(注意:当然,在以编程方式创建引擎时,这完全相似)。现在不要担心所有属性,我们将在下一节中详细介绍它们。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 <bean id="processEngineConfiguration" class="...SomeProcessEngineConfigurationClass">
...
<property name="configurators">
<list>
<bean class="org.activiti.ldap.LDAPConfigurator">
<!-- Server connection params -->
<property name="server" value="ldap://localhost" />
<property name="port" value="33389" />
<property name="user" value="uid=admin, ou=users, o=activiti" />
<property name="password" value="pass" />
<!-- Query params -->
<property name="baseDn" value="o=activiti" />
<property name="queryUserByUserId" value="(&(objectClass=inetOrgPerson)(uid={0}))" />
<property name="queryUserByFullNameLike" value="(&(objectClass=inetOrgPerson)(|({0}=*{1}*)({2}=*{3}*)))" />
<property name="queryGroupsForUser" value="(&(objectClass=groupOfUniqueNames)(uniqueMember={0}))" />
<!-- Attribute config -->
<property name="userIdAttribute" value="uid" />
<property name="userFirstNameAttribute" value="cn" />
<property name="userLastNameAttribute" value="sn" />
<property name="userEmailAttribute" value="mail" />
<property name="groupIdAttribute" value="cn" />
<property name="groupNameAttribute" value="cn" />
</bean>
</list>
</property>
</bean>
15.4. 特性
可以设置以下属性org.activiti.ldap.LDAPConfigurator
:
.LDAP 配置属性
属性名称 | 描述 | 类型 | 默认值 |
---|---|---|---|
服务器 |
可以访问 LDAP 系统的服务器。例如ldap://localhost:33389 |
细绳 |
|
港口 |
运行 LDAP 系统的端口 |
整数 |
|
用户 |
用于连接 LDAP 系统的用户 ID |
细绳 |
|
密码 |
用于连接 LDAP 系统的密码 |
细绳 |
|
初始上下文工厂 |
用于连接 LDAP 系统的 InitialContextFactory 名称 |
细绳 |
com.sun.jndi.ldap.LdapCtxFactory |
安全认证 |
用于连接到 LDAP 系统的java.naming.security.authentication属性的值 |
细绳 |
简单的 |
自定义连接参数 |
允许设置所有没有专用设置器的 LDAP 连接参数。有关自定义属性,请参见例如http://docs.oracle.com/javase/tutorial/jndi/ldap/jndi.html。例如,此类属性用于配置连接池、特定安全设置等。所有提供的参数将在创建与 LDAP 系统的连接时提供。 |
地图<字符串,字符串> |
|
基数 |
开始搜索用户和组的基本专有名称(DN) |
细绳 |
|
userBaseDn |
开始搜索用户的基本专有名称(DN)。如果未提供,将使用 baseDn(见上文) |
细绳 |
|
groupBaseDn |
开始搜索组的基本专有名称(DN)。如果未提供,将使用 baseDn(见上文) |
细绳 |
|
搜索时间限制 |
在 LDAP 中进行搜索时使用的超时时间(以毫秒为单位) |
长 |
一小时 |
queryUserByUserId |
通过 userId 搜索用户时执行的查询。例如: (&(objectClass=inetOrgPerson)(uid={0})) 此处将返回LDAP 中所有类inetOrgPerson 且具有匹配uid属性值的对象。如示例所示,用户 id 是使用 {0} 注入的。如果单独设置查询不足以满足您的特定 LDAP 设置,您也可以插入不同的 LDAPQueryBuilder,它允许比仅查询更多的自定义。 |
细绳 |
|
queryUserByFullNameLike |
按全名搜索用户时执行的查询。例如: (& (objectClass=inetOrgPerson) ( |
({0}= {1} )({2}= {3} )) ) 在这里,将返回 LDAP 中具有类inetOrgPerson 并且具有匹配的名字和姓氏值的所有对象。请注意,{0} 注入 firstNameAttribute(如上定义),{1} 和 {3} 注入搜索文本,{2} 注入 lastNameAttribute。如果单独设置查询不足以满足您的特定 LDAP 设置,您也可以插入不同的 LDAPQueryBuilder,它允许比仅查询更多的自定义。 |
细绳 |
queryGroupsForUser |
搜索特定用户的组时执行的查询。例如: (&(objectClass=groupOfUniqueNames)(uniqueMember={0})) 在这里,返回 LDAP 中具有类groupOfUniqueNames 并且提供的 DN(匹配用户的 DN)是uniqueMember的所有对象。如示例中所示,使用 {0} 注入用户 ID 如果单独设置查询不足以满足您的特定 LDAP 设置,您也可以插入不同的 LDAPQueryBuilder,它允许比仅查询更多的自定义。 |
细绳 |
|
用户标识属性 |
与用户 ID 匹配的属性的名称。当查找一个用户对象并且LDAP对象和Activiti用户对象之间的映射完成时使用这个属性。 |
细绳 |
|
用户名属性 |
与用户名匹配的属性的名称。当查找一个用户对象并且LDAP对象和Activiti用户对象之间的映射完成时使用这个属性。 |
细绳 |
|
用户姓氏属性 |
与用户姓氏匹配的属性的名称。当查找一个用户对象并且LDAP对象和Activiti用户对象之间的映射完成时使用这个属性。 |
细绳 |
|
groupId 属性 |
与组 ID 匹配的属性的名称。这个属性在寻找一个 Group 对象并且 LDAP 对象和 Activiti Group 对象之间的映射完成时使用。 |
细绳 |
|
组名属性 |
与组名匹配的属性名称。这个属性在寻找一个 Group 对象并且 LDAP 对象和 Activiti Group 对象之间的映射完成时使用。 |
细绳 |
|
组类型属性 |
与组类型匹配的属性的名称。这个属性在寻找一个 Group 对象并且 LDAP 对象和 Activiti Group 对象之间的映射完成时使用。 |
细绳 |
以下属性是当一个人想要自定义默认行为或引入组缓存时:
属性名称 | 描述 | 类型 | 默认值 |
---|---|---|---|
ldapUserManagerFactory |
如果默认实现不合适,请设置 LDAPUserManagerFactory 的自定义实现。 |
LDAPUserManagerFactory 的实例 |
|
ldapGroupManagerFactory |
如果默认实现不合适,请设置 LDAPGroupManagerFactory 的自定义实现。 |
LDAPGroupManagerFactory 的实例 |
|
ldapMemberShipManagerFactory |
如果默认实现不合适,请设置 LDAPMembershipManagerFactory 的自定义实现。请注意,这不太可能,因为成员资格通常在 LDAP 系统本身中进行管理。 |
LDAPMembershipManagerFactory 的一个实例 |
|
ldapQueryBuilder |
如果默认实现不合适,请设置自定义查询构建器。当 LDAPUserManager 或 LDAPGroupManage} 对 LDAP 系统进行实际查询时,将使用 LDAPQueryBuilder 实例。默认实现使用在此实例上设置的属性,例如 queryGroupsForUser 和 queryUserById |
org.activiti.ldap.LDAPQueryBuilder 的一个实例 |
|
组缓存大小 |
允许设置组缓存的大小。这是一个 LRU 缓存,它为用户缓存组,从而避免每次需要知道用户组时访问 LDAP 系统。 如果值小于零,则不会实例化缓存。默认设置为 -1,因此不进行缓存。 |
整数 |
-1 |
groupCacheExpirationTime |
以毫秒为单位设置组缓存的过期时间。当获取特定用户的组时,如果组缓存存在,则组将在此属性中设置的时间内存储在此缓存中。即,当组在 00:00 提取并且到期时间为 30 分钟时,00:30 之后对该用户的组的任何提取都不会来自缓存,而是从 LDAP 系统再次提取。同样,在 00:00 - 00:30 之间为该用户完成的所有组获取都将来自缓存。 |
长 |
一小时 |
使用 Active Directory 时的注意事项:Activiti 论坛中有人报告说,对于 Activiti Directory,InitialDirContext需要设置为 Context.REFERRAL。如上所述,这可以通过 customConnectionParameters 映射传递。
16.高级
以下部分介绍了 Activiti 的高级用例,这些用例超出了 BPMN 2.0 流程的典型执行范围。因此,建议对 Activiti 有一定的熟练程度和经验,以了解此处描述的主题。
16.1. 异步执行器
在 Activiti 版本 5(从版本 5.17.0 开始)中,除了现有的作业执行器之外,还添加了异步执行器。Activiti 的许多用户和我们的基准测试证明,Async Executor 比旧的作业执行器性能更高。
在 Activiti 6(及更高版本)中,异步执行器是唯一可用的。对于版本 6,异步执行器完全重构以获得最佳性能和可插入性(同时仍与现有 API 兼容)。
16.1.1. 异步执行器设计
存在两种类型的作业:计时器(例如属于用户任务上的边界事件的那些)和异步延续(属于具有activiti:async="true"属性的服务任务)。
计时器是最容易解释的:它们被持久化在 ACT_RU_TIMER_JOB 表中,并具有特定的截止日期。异步执行器中有一个线程定期检查是否有新的计时器触发(即到期日期早于当前时间)。发生这种情况时,将删除计时器并创建并插入异步作业。
在流程实例步骤的执行期间(这意味着在进行某些 API 调用期间),会将异步作业插入数据库中。如果当前 Activiti 引擎的异步执行器处于活动状态,则异步作业实际上已经锁定。这意味着作业条目被插入到 ACT_RU_JOB 表中,并且将有一个锁所有者和一个锁过期时间放。在成功提交 API 调用时触发的事务侦听器会触发同一引擎的异步执行器来执行作业(因此保证数据在数据库中)。为此,异步执行器有一个(可配置的)线程池,一个线程将从该线程池中执行作业并异步继续进程。如果 Activiti 引擎没有启用异步执行器,则异步作业被插入到 ACT_RU_JOB 表中而不被锁定。
与检查新计时器的线程类似,异步执行器有一个获取新异步作业的线程。这些是表中存在且未锁定的作业。该线程将为当前的 Activiti 引擎锁定这些作业并将其传递给异步执行器。
执行作业的线程池使用内存中的队列来获取作业。当此队列已满(这是可配置的)时,作业将被解锁并重新插入其表中。这样,其他异步执行器可以取而代之。
如果在作业执行期间发生异常,异步作业将转换为具有截止日期的计时器作业。它将像常规计时器作业一样被拾取并再次成为异步作业,很快就会重试。当一个作业被重试了(可配置的)次数并且继续失败时,该作业被假定为已死并移动到 ACT_RU_DEADLETTER_JOB。死信概念广泛用于各种其他系统。管理员现在需要检查失败作业的异常并决定最佳操作方案是什么。
流程定义和流程实例可以暂停。与这些定义或实例相关的暂停作业放在 ACT_RU_SUSPENDED_JOB 表中,以确保获取作业的查询在其 where 子句中具有尽可能少的条件。
对于熟悉作业/异步执行器的旧实现的人来说,从上面可以清楚地看出一件事:主要目标是让获取查询尽可能简单。在过去(版本 6 之前),一个表用于所有作业类型/状态,这使得where条件很大,因为它可以满足所有用例。这个问题现在已经解决了,我们的基准测试已经证明这种新设计提供了更好的性能并且更具可扩展性。
16.1.2. 异步执行器配置
异步执行器是一个高度可配置的组件。始终建议查看异步执行器的默认设置并验证它们是否符合您的流程要求。
或者,可以扩展默认实现或使用您自己的实现来实现org.activiti.engine.impl.asyncexecutor.AsyncExecutor接口。
通过 setter 可在流程引擎配置中使用以下属性:
名称 | 默认值 | 描述 |
---|---|---|
asyncExecutorThreadPoolQueueSize |
100 |
要执行的作业在被获取后,在线程池中的线程实际执行之前放置的队列的大小 |
asyncExecutorCorePoolSize |
2 |
在线程池中为作业执行而保持活动状态的最小线程数。 |
asyncExecutorMaxPoolSize |
10 |
在线程池中为作业执行创建的最大线程数。 |
asyncExecutorThreadKeepAliveTime |
5000 |
用于作业执行的线程在销毁之前必须保持活动的时间(以毫秒为单位)。设置 > 0 会占用资源,但在执行许多作业的情况下,它会一直避免创建新线程。如果为 0,线程将在用于作业执行后被销毁。 |
asyncExecutorNumberOfRetries |
3 |
在将作业移至死信表之前重试作业的次数。 |
asyncExecutorMaxTimerJobsPerAcquisition |
1 |
在一次获取查询期间获取的计时器作业数。默认值为 1,因为这降低了乐观锁定异常的可能性。较大的值可以更好地执行,但不同引擎之间发生乐观锁定异常的机会也会变得更大。 |
asyncExecutorMaxAsyncJobsDuePerAcquisition |
1 |
在一次获取查询期间获取的异步作业数。默认值为 1,因为这降低了乐观锁定异常的可能性。较大的值可以更好地执行,但不同引擎之间发生乐观锁定异常的机会也会变得更大。 |
asyncExecutorDefaultTimerJobAcquireWaitTime |
10000 |
定时器获取线程等待执行下一个获取查询的时间(以毫秒为单位)。当没有找到新的计时器作业或获取的计时器作业少于asyncExecutorMaxTimerJobsPerAcquisition中设置的计时器作业时,就会发生这种情况。 |
asyncExecutorDefaultAsyncJobAcquireWaitTime |
10000 |
异步作业获取线程等待执行下一个获取查询的时间(以毫秒为单位)。当未找到新的异步作业或获取的异步作业少于asyncExecutorMaxAsyncJobsDuePerAcquisition中设置的数量时,就会发生这种情况。 |
asyncExecutorDefaultQueueSizeFullWaitTime |
0 |
当内部作业队列已满以执行下一个查询时,异步作业(计时器和异步延续)获取线程将等待的时间(以毫秒为单位)。默认设置为 0(为了向后兼容)。将此属性设置为更高的值允许异步执行器希望清除其队列。 |
asyncExecutorTimerLockTimeInMillis |
5分钟 |
异步执行器获取计时器作业时锁定的时间量(以毫秒为单位)。在这段时间内,没有其他异步执行器会尝试获取并锁定此作业。 |
asyncExecutorAsyncJobLockTimeInMillis |
5分钟 |
异步执行程序获取异步作业时锁定的时间量(以毫秒为单位)。在这段时间内,没有其他异步执行器会尝试获取并锁定此作业。 |
asyncExecutorSecondsToWaitOnShutdown |
60 |
当请求执行器(或流程引擎)关闭时,等待正常关闭用于作业执行的线程池的时间(以秒为单位)。 |
asyncExecutorResetExpiredJobsInterval |
60 秒 |
两次连续检查过期作业之间的时间量(以毫秒为单位)。过期作业是被锁定的作业(某个执行者写入了锁所有者 + 时间,但该作业从未完成)。在这样的检查期间,过期的作业将再次可用,这意味着锁定所有者和锁定时间将被删除。其他执行者现在可以拿起它。如果锁定时间在当前日期之前,则作业被视为过期。 |
asyncExecutorResetExpiredJobsPageSize |
3 |
异步执行器的重置过期线程一次获取的作业数量。 |
16.1.3. 基于消息队列的异步执行器
阅读异步执行器设计部分时,很明显该架构受到消息队列的启发。异步执行器的设计方式使得消息队列可以很容易地用于接管线程池的作业和异步作业的处理。
基准测试表明,使用消息队列在吞吐量方面优于线程池支持的异步执行器。但是,它确实带有一个额外的架构组件,这当然会使设置、维护和监控更加复杂。对于许多用户来说,线程池支持的异步执行器的性能绰绰有余。然而,很高兴知道,如果所需的性能增长,还有另一种选择。
目前,开箱即用支持的唯一选项是 JMS 和 Spring。优先支持 Spring 的原因是因为 Spring 有一些非常好的特性,在线程和处理多个消息消费者时减轻了很多痛苦。但是,集成非常简单,可以轻松移植到任何消息队列实现和/或协议(Stomp、AMPQ 等)。感谢您对下一个实施的反馈。
当引擎创建新的异步作业时,会在包含作业标识符的消息队列(在事务提交的事务侦听器中,因此我们确定作业条目在数据库中)中放置一条消息。然后消息消费者使用此作业标识符来获取作业并执行作业。异步执行器将不再创建线程池。它将从一个单独的线程插入和查询计时器。当计时器触发时,它会被移动到异步作业表,这意味着消息也被发送到消息队列。重置过期线程也将像往常一样解锁作业,因为消息队列也可能失败。现在将重新发送一条消息,而不是解锁作业。异步执行器将不再轮询异步作业。
实现由两个类组成:
-
org.activiti.engine.impl.asyncexecutor.JobManager接口的实现,它将消息放在消息队列中,而不是将其传递到线程池。
-
一个javax.jms.MessageListener实现,它使用消息队列中的消息,使用消息中的作业标识符来获取和执行作业。
首先,在你的项目中添加activiti-jms-spring-executor依赖:
1
2
3
4
5 <dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-jms-spring-executor</artifactId>
<version>${activiti.version}</version>
</dependency>
要启用基于消息队列的异步执行器,在流程引擎配置中,需要执行以下操作:
-
像往常一样,asyncExecutorActivate必须设置为true
-
asyncExecutorMessageQueueMode需要设置为true
-
org.activiti.spring.executor.jms.MessageBasedJobManager必须作为JobManager注入
下面是一个基于 Java 的配置的完整示例,使用ActiveMQ作为消息队列代理。
需要注意的一些事项:
-
MessageBasedJobManager期望注入一个配置有正确connectionFactory的JMSTemplate。
-
我们使用 Spring 的MessageListenerContainer概念,因为这大大简化了线程和多个消费者。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 @Configuration
public class SpringJmsConfig {
@Bean
public DataSource dataSource() {
// Omitted
}
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
@Bean
public SpringProcessEngineConfiguration processEngineConfiguration() {
SpringProcessEngineConfiguration configuration = new SpringProcessEngineConfiguration();
configuration.setDataSource(dataSource());
configuration.setTransactionManager(transactionManager());
configuration.setDatabaseSchemaUpdate(SpringProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
configuration.setAsyncExecutorMessageQueueMode(true);
configuration.setAsyncExecutorActivate(true);
configuration.setJobManager(jobManager());
return configuration;
}
@Bean
public ProcessEngine processEngine() {
return processEngineConfiguration().buildProcessEngine();
}
@Bean
public MessageBasedJobManager jobManager() {
MessageBasedJobManager jobManager = new MessageBasedJobManager();
jobManager.setJmsTemplate(jmsTemplate());
return jobManager;
}
@Bean
public ConnectionFactory connectionFactory() {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
activeMQConnectionFactory.setUseAsyncSend(true);
activeMQConnectionFactory.setAlwaysSessionAsync(true);
return new CachingConnectionFactory(activeMQConnectionFactory);
}
@Bean
public JmsTemplate jmsTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setDefaultDestination(new ActiveMQQueue("activiti-jobs"));
jmsTemplate.setConnectionFactory(connectionFactory());
return jmsTemplate;
}
@Bean
public MessageListenerContainer messageListenerContainer() {
DefaultMessageListenerContainer messageListenerContainer = new DefaultMessageListenerContainer();
messageListenerContainer.setConnectionFactory(connectionFactory());
messageListenerContainer.setDestinationName("activiti-jobs");
messageListenerContainer.setMessageListener(jobMessageListener());
messageListenerContainer.setConcurrentConsumers(2);
messageListenerContainer.start();
return messageListenerContainer;
}
@Bean
public JobMessageListener jobMessageListener() {
JobMessageListener jobMessageListener = new JobMessageListener();
jobMessageListener.setProcessEngineConfiguration(processEngineConfiguration());
return jobMessageListener;
}
}
在上面的代码中,JobMessageListener和MessageBasedJobManager是来自activiti-jms-spring-executor模块的唯一类。所有其他代码都来自 Spring。因此,当想要将此移植到其他队列/协议时,必须移植这些类。
16.2. 挂钩到进程解析
一个 BPMN 2.0 xml 需要被解析为 Activiti 内部模型才能在 Activiti 引擎上执行。此解析发生在进程部署期间或内存中未找到进程时,并且从数据库中获取 xml。
对于这些进程中的每一个,BpmnParser
该类都会创建一个新BpmnParse
实例。此实例将用作解析期间完成的所有事情的容器。对自身的解析非常简单:对于每个 BPMN 2.0 元素,org.activiti.engine.parse.BpmnParseHandler
引擎中都有一个可用的匹配实例。因此,解析器有一个映射,它基本上将 BPMN 2.0 元素类映射到BpmnParseHandler
. 默认情况下,Activiti 有BpmnParseHandler
实例来处理所有支持的元素,并且还使用它来将执行侦听器附加到创建历史的过程的步骤中。
可以将自定义实例添加org.activiti.engine.parse.BpmnParseHandler
到 Activiti 引擎。例如,一个常见的用例是向某些步骤添加执行侦听器,这些步骤将事件触发到某个队列以进行事件处理。历史处理是在 Activiti 内部以这种方式完成的。要添加这样的自定义处理程序,需要调整 Activiti 配置:
1
2
3
4
5
6
7
8
9
10
11
12 <property name="preBpmnParseHandlers">
<list>
<bean class="org.activiti.parsing.MyFirstBpmnParseHandler" />
</list>
</property>
<property name="postBpmnParseHandlers">
<list>
<bean class="org.activiti.parsing.MySecondBpmnParseHandler" />
<bean class="org.activiti.parsing.MyThirdBpmnParseHandler" />
</list>
</property>
在属性中BpmnParseHandler
配置的实例列表添加在任何默认处理程序之前。preBpmnParseHandlers
同样,在postBpmnParseHandlers
这些之后添加。如果事情的顺序对于自定义解析处理程序中包含的逻辑很重要,这可能很重要。
org.activiti.engine.parse.BpmnParseHandler
是一个简单的界面:
1
2
3
4
5
6
7 public interface BpmnParseHandler {
Collection<Class>? extends BaseElement>> getHandledTypes();
void parse(BpmnParse bpmnParse, BaseElement element);
}
该getHandledTypes()
方法返回此解析器处理的所有类型的集合。可能的类型是 的子类BaseElement
,由集合的泛型类型指示。您还可以扩展AbstractBpmnParseHandler
该类并覆盖该getHandledType()
方法,该方法仅返回一个类而不是集合。此类还包含许多默认解析处理程序共享的一些辅助方法。BpmnParseHandler
当解析器遇到此方法返回的任何类型时,将调用该实例。在下面的示例中,每当遇到包含在 BPMN 2.0 xml 中的流程时,它将执行executeParse
方法中的逻辑(这是一种类型转换的方法,替代了接口parse
上的常规方法BpmnParseHandler
)。
1
2
3
4
5
6
7
8
9
10
11 public class TestBPMNParseHandler extends AbstractBpmnParseHandler<Process> {
protected Class<? extends BaseElement> getHandledType() {
return Process.class;
}
protected void executeParse(BpmnParse bpmnParse, Process element) {
..
}
}
重要提示:在编写自定义解析处理程序时,不要使用任何用于解析 BPMN 2.0 结构的内部类。这将导致难以找到错误。实现自定义处理程序的安全方法是实现BpmnParseHandler接口或扩展内部抽象类org.activiti.engine.impl.bpmn.parser.handler.AbstractBpmnParseHandler。
BpmnParseHandler
将负责解析 BPMN 2.0 元素的默认实例替换为内部 Activiti 模型是可能的(但不太常见) 。这可以通过以下逻辑片段来完成:
1
2
3
4
5 <property name="customDefaultBpmnParseHandlers">
<list>
...
</list>
</property>
例如,一个简单的示例可以强制所有服务任务为异步:
1
2
3
4
5
6
7
8
9
10
11
12
13 public class CustomUserTaskBpmnParseHandler extends ServiceTaskParseHandler {
protected void executeParse(BpmnParse bpmnParse, ServiceTask serviceTask) {
// Do the regular stuff
super.executeParse(bpmnParse, serviceTask);
// Make always async
ActivityImpl activity = findActivity(bpmnParse, serviceTask.getId());
activity.setAsync(true);
}
}
16.3. 用于高并发的 UUID id 生成器
在一些(非常)高并发负载情况下,默认的 id 生成器可能会由于无法足够快地获取新的 id 块而导致异常。每个流程引擎都有一个 id 生成器。默认的 id 生成器在数据库中保留一个 id 块,这样其他引擎将无法使用同一块中的 id。在引擎操作期间,当默认的 id 生成器注意到 id 块已用完时,会启动一个新事务以获取新块。在(非常)有限的用例中,当负载非常高时,这可能会导致问题。对于大多数用例,默认的 id 生成器绰绰有余。默认值org.activiti.engine.impl.db.DbIdGenerator
还有一个属性idBlockSize
,可以配置为设置保留的 id 块的大小并调整 id 获取的行为。
默认 id 生成器的替代方案是org.activiti.engine.impl.persistence.StrongUuidGenerator
,它在本地生成唯一的UUID并将其用作所有实体的标识符。由于 UUID 是在不需要数据库访问的情况下生成的,因此它可以更好地应对非常高并发的用例。请注意,性能可能与默认的 id 生成器(正面和负面)不同,具体取决于机器。
UUID生成器可以在activiti配置中进行如下配置:
1
2
3 <property name="idGenerator">
<bean class="org.activiti.engine.impl.persistence.StrongUuidGenerator" />
</property>
UUID id 生成器的使用依赖于以下额外依赖:
1
2
3
4
5 <dependency>
<groupId>com.fasterxml.uuid</groupId>
<artifactId>java-uuid-generator</artifactId>
<version>3.1.3</version>
</dependency>
16.4. 多租户
一般来说,多租户是一个软件能够服务于多个不同组织的概念。关键是数据是分区的,没有组织可以看到其他组织的数据。在这种情况下,这样的组织(或部门、团队或……)被称为租户。
请注意,这与多实例设置根本不同,在多实例设置中,Activiti Process Engine 实例为每个组织单独运行(并且具有不同的数据库模式)。尽管 Activiti 是轻量级的,并且运行流程引擎实例不会占用太多资源,但它确实增加了复杂性和更多的维护。但是,对于某些用例,它可能是正确的解决方案。
Activiti 中的多租户主要是围绕数据分区实现的。重要的是要注意Activiti 不强制执行多租户规则。这意味着它不会在查询和使用数据时验证执行操作的用户是否属于正确的租户。这应该在调用 Activiti 引擎的层中完成。Activiti 确实确保在检索流程数据时可以存储和使用租户信息。
将流程定义部署到 Activiti Process Engine 时,可以传递租户标识符。这是一个字符串(例如 UUID、部门 ID 等),限制为 256 个字符,用于唯一标识租户:
1
2
3
4 repositoryService.createDeployment()
.addClassPathResource(...)
.tenantId("myTenantId")
.deploy();
在部署期间传递租户 ID 具有以下含义:
-
部署中包含的所有流程定义都从该部署继承租户标识符。
-
从这些流程定义开始的所有流程实例都从流程定义继承此租户标识符。
-
执行流程实例时在运行时创建的所有任务都从流程实例继承此租户标识符。独立任务也可以有一个租户标识符。
-
在流程实例执行期间创建的所有执行都从流程实例继承此租户标识符。
-
可以在提供租户标识符的同时触发信号抛出事件(在流程本身或通过 API)。信号只会在租户上下文中执行:即如果有多个同名的信号捕获事件,则实际上只会调用具有正确租户标识符的事件。
-
所有作业(计时器和异步延续)都从流程定义(例如计时器启动事件)或流程实例(在运行时创建作业时,例如异步延续)继承租户标识符。这可能用于在自定义作业执行器中为某些租户提供优先级。
-
所有历史实体(历史流程实例、任务和活动)都从其运行时对应物继承租户标识符。
-
附带说明一下,模型也可以有一个租户标识符(例如,Activiti Modeler 使用模型来存储 BPMN 2.0 模型)。
为了在流程数据上实际使用租户标识符,所有查询 API 都具有过滤租户的能力。例如(并且可以替换为其他实体的相关查询实现):
1
2
3
4
5 runtimeService.createProcessInstanceQuery()
.processInstanceTenantId("myTenantId")
.processDefinitionKey("myProcessDefinitionKey")
.variableValueEquals("myVar", "someValue")
.list()
查询 API 还允许过滤具有相似语义的租户标识符,还可以过滤掉没有租户 ID 的实体。
重要的实现细节:由于数据库的怪癖(更具体地说:唯一约束中的空处理),指示没有租户的默认租户标识符值是空字符串。(流程定义键、流程定义版本、租户标识符)的组合必须是唯一的(并且有一个数据库约束检查这一点)。另请注意,租户标识符不应设置为 null,因为这会影响查询,因为某些数据库 (Oracle) 将空字符串视为 null 值(这就是查询的原因.withoutTenantId的原因对空字符串或 null 进行检查)。这意味着可以为多个租户部署相同的流程定义(具有相同的流程定义密钥),每个租户都有自己的版本控制。不使用租赁时,这不会影响使用。
请注意,以上所有内容都与在集群中运行多个 Activiti 实例不冲突。
[实验性] 可以通过调用repositoryService上的changeDeploymentTenantId(String deploymentId, String newTenantId)方法来更改租户标识符。这将更改之前继承的任何地方的租户标识符。这在从非多租户设置到多租户配置时很有用。有关更多详细信息,请参阅有关该方法的 Javadoc。
16.5。执行自定义 SQL
Activiti API 允许使用高级 API 与数据库进行交互。例如,对于检索数据,Query API 和 Native Query API 的使用非常强大。但是,对于某些用例,它们可能不够灵活。以下部分描述了如何针对 Activiti 数据存储执行完全自定义的 SQL 语句(可能的选择、插入、更新和删除),但完全在配置的流程引擎内(例如,利用事务设置)。
为了定义自定义 SQL 语句,Activiti 引擎利用了其底层框架 MyBatis 的功能。更多信息可以 在 MyBatis 用户指南中阅读。
16.5.1. 基于注解的映射语句
使用基于 Annotation 的 Mapped Statements 时要做的第一件事是创建一个 MyBatis 映射器类。例如,假设对于某些用例,不需要整个任务数据,而只需要其中的一小部分。可以执行此操作的 Mapper 如下所示:
1
2
3
4
5
6 public interface MyTestMapper {
@Select("SELECT ID_ as id, NAME_ as name, CREATE_TIME_ as createTime FROM ACT_RU_TASK")
List<Map<String, Object>> selectTasks();
}
此映射器必须按如下方式提供给流程引擎配置:
1
2
3
4
5
6
7 ...
<property name="customMybatisMappers">
<set>
<value>org.activiti.standalone.cfg.MyTestMapper</value>
</set>
</property>
...
请注意,这是一个接口。底层的 MyBatis 框架将创建一个可以在运行时使用的实例。另请注意,该方法的返回值不是类型化的,而是一个映射列表(对应于具有列值的行列表)。如果需要,可以使用 MyBatis 映射器进行打字。
要执行上面的查询,必须使用managementService.executeCustomSql方法。此方法接受一个CustomSqlExecution实例。这是一个包装器,它隐藏了引擎的内部位,否则需要使其工作。
不幸的是,Java 泛型使它的可读性比它本来的要差。下面的两个泛型类型是映射器类和返回类型类。但是,实际的逻辑只是调用映射器方法并返回其结果(如果适用)。
1
2
3
4
5
6
7
8
9
10 CustomSqlExecution<MyTestMapper, List<Map<String, Object>>> customSqlExecution =
new AbstractCustomSqlExecution<MyTestMapper, List<Map<String, Object>>>(MyTestMapper.class) {
public List<Map<String, Object>> execute(MyTestMapper customMapper) {
return customMapper.selectTasks();
}
};
List<Map<String, Object>> results = managementService.executeCustomSql(customSqlExecution);
在这种情况下,上面列表中的 Map 条目将仅包含id、名称和创建时间,而不是完整的任务对象。
使用上述方法时,任何 SQL 都是可能的。另一个更复杂的例子:
1
2
3
4
5
6 @Select({
"SELECT task.ID_ as taskId, variable.LONG_ as variableValue FROM ACT_RU_VARIABLE variable",
"inner join ACT_RU_TASK task on variable.TASK_ID_ = task.ID_",
"where variable.NAME_ = #{variableName}"
})
List<Map<String, Object>> selectTaskWithSpecificVariable(String variableName);
使用此方法,任务表将与变量表连接。只有变量有一定名字的地方才会保留,返回任务id和对应的数值。
有关使用基于注释的映射语句的工作示例,请检查单元测试org.activiti.standalone.cfg.CustomMybatisMapperTest以及文件夹 src/test/java/org/activiti/standalone/cfg/ 和 src/test/resources 中的其他类和资源/org/activiti/独立/cfg/
16.5.2. 基于 XML 的映射语句
使用基于 XML 的映射语句时,语句在 XML 文件中定义。对于不需要全部任务数据而只需要其中一小部分的用例。XML 文件可能如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13 <mapper namespace="org.activiti.standalone.cfg.TaskMapper">
<resultMap id="customTaskResultMap" type="org.activiti.standalone.cfg.CustomTask">
<id property="id" column="ID_" jdbcType="VARCHAR"/>
<result property="name" column="NAME_" jdbcType="VARCHAR"/>
<result property="createTime" column="CREATE_TIME_" jdbcType="TIMESTAMP" />
</resultMap>
<select id="selectCustomTaskList" resultMap="customTaskResultMap">
select RES.ID_, RES.NAME_, RES.CREATE_TIME_ from ACT_RU_TASK RES
</select>
</mapper>
结果映射到org.activiti.standalone.cfg.CustomTask类的实例,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 public class CustomTask {
protected String id;
protected String name;
protected Date createTime;
public String getId() {
return id;
}
public String getName() {
return name;
}
public Date getCreateTime() {
return createTime;
}
}
映射器 XML 文件必须提供给流程引擎配置,如下所示:
1
2
3
4
5
6
7 ...
<property name="customMybatisXMLMappers">
<set>
<value>org/activiti/standalone/cfg/custom-mappers/CustomTaskMapper.xml</value>
</set>
</property>
...
该语句可以执行如下:
1
2
3
4
5
6
7
8 List<CustomTask> tasks = managementService.executeCommand(new Command<List<CustomTask>>() {
@SuppressWarnings("unchecked")
@Override
public List<CustomTask> execute(CommandContext commandContext) {
return (List<CustomTask>) commandContext.getDbSqlSession().selectList("selectCustomTaskList");
}
});
对于需要更复杂语句的用例,XML 映射语句会很有帮助。由于 Activiti 在内部使用 XML 映射语句,因此可以利用底层功能。
假设对于某些用例,需要根据 id、name、type、userId 等查询附件数据!为了实现用例,可以创建扩展 org.activiti.engine.impl.AbstractQuery的查询类AttachmentQuery ,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 public class AttachmentQuery extends AbstractQuery<AttachmentQuery, Attachment> {
protected String attachmentId;
protected String attachmentName;
protected String attachmentType;
protected String userId;
public AttachmentQuery(ManagementService managementService) {
super(managementService);
}
public AttachmentQuery attachmentId(String attachmentId){
this.attachmentId = attachmentId;
return this;
}
public AttachmentQuery attachmentName(String attachmentName){
this.attachmentName = attachmentName;
return this;
}
public AttachmentQuery attachmentType(String attachmentType){
this.attachmentType = attachmentType;
return this;
}
public AttachmentQuery userId(String userId){
this.userId = userId;
return this;
}
@Override
public long executeCount(CommandContext commandContext) {
return (Long) commandContext.getDbSqlSession()
.selectOne("selectAttachmentCountByQueryCriteria", this);
}
@Override
public List<Attachment> executeList(CommandContext commandContext, Page page) {
return commandContext.getDbSqlSession()
.selectList("selectAttachmentByQueryCriteria", this);
}
请注意,在扩展AbstractQuery扩展类时,应将ManagementService的实例传递给超级构造函数,并且需要实现方法executeCount和executeList以调用映射语句。
包含映射语句的 XML 文件如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 <mapper namespace="org.activiti.standalone.cfg.AttachmentMapper">
<select id="selectAttachmentCountByQueryCriteria" parameterType="org.activiti.standalone.cfg.AttachmentQuery" resultType="long">
select count(distinct RES.ID_)
<include refid="selectAttachmentByQueryCriteriaSql"/>
</select>
<select id="selectAttachmentByQueryCriteria" parameterType="org.activiti.standalone.cfg.AttachmentQuery" resultMap="org.activiti.engine.impl.persistence.entity.AttachmentEntity.attachmentResultMap">
${limitBefore}
select distinct RES.* ${limitBetween}
<include refid="selectAttachmentByQueryCriteriaSql"/>
${orderBy}
${limitAfter}
</select>
<sql id="selectAttachmentByQueryCriteriaSql">
from ${prefix}ACT_HI_ATTACHMENT RES
<where>
<if test="attachmentId != null">
RES.ID_ = #{attachmentId}
</if>
<if test="attachmentName != null">
and RES.NAME_ = #{attachmentName}
</if>
<if test="attachmentType != null">
and RES.TYPE_ = #{attachmentType}
</if>
<if test="userId != null">
and RES.USER_ID_ = #{userId}
</if>
</where>
</sql>
</mapper>
诸如分页、排序、表名前缀等功能可用并且可以在语句中使用(因为 parameterType 是AbstractQuery的子类)。请注意,为了映射结果,可以使用预定义的org.activiti.engine.impl.persistence.entity.AttachmentEntity.attachmentResultMap resultMap。
最后,可以按如下方式使用AttachmentQuery :
1
2
3
4
5
6
7
8
9
10
11
12
13 ....
// Get the total number of attachments
long count = new AttachmentQuery(managementService).count();
// Get attachment with id 10025
Attachment attachment = new AttachmentQuery(managementService).attachmentId("10025").singleResult();
// Get first 10 attachments
List<Attachment> attachments = new AttachmentQuery(managementService).listPage(0, 10);
// Get all attachments uploaded by user kermit
attachments = new AttachmentQuery(managementService).userId("kermit").list();
....
有关使用 XML 映射语句的工作示例,请检查单元测试org.activiti.standalone.cfg.CustomMybatisXMLMapperTest以及文件夹 src/test/java/org/activiti/standalone/cfg/ 和 src/test/resources/org 中的其他类和资源/activiti/独立/cfg/
16.6。使用 ProcessEngineConfigurator 进行高级流程引擎配置
挂钩到流程引擎配置的一种高级方法是使用 ProcessEngineConfigurator。这个想法是 创建org.activiti.engine.cfg.ProcessEngineConfigurator接口的实现并将其注入到流程引擎配置中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 <bean id="processEngineConfiguration" class="...SomeProcessEngineConfigurationClass">
...
<property name="configurators">
<list>
<bean class="com.mycompany.MyConfigurator">
...
</bean>
</list>
</property>
...
</bean>
实现此接口需要两种方法。configure方法,它获取一个ProcessEngineConfiguration实例作为参数。可以通过这种方式添加自定义配置,并且保证在创建流程引擎之前调用该方法,但在完成所有默认配置之后。另一种方法是getPriority方法,它允许在某些配置器相互依赖的情况下对配置器进行排序。
这种配置器的一个示例是LDAP 集成,其中配置器用于将默认用户和组管理器类替换为能够处理 LDAP 用户存储的类。因此,基本上配置器允许对流程引擎进行大量更改或调整,并且适用于非常高级的用例。另一个示例是将流程定义缓存与自定义版本交换:
1
2
3
4
5
6
7
8 public class ProcessDefinitionCacheConfigurator extends AbstractProcessEngineConfigurator {
public void configure(ProcessEngineConfigurationImpl processEngineConfiguration) {
MyCache myCache = new MyCache();
processEngineConfiguration.setProcessDefinitionCache(enterpriseProcessDefinitionCache);
}
}
Process Engine 配置器也可以使用ServiceLoader方法从类路径中自动发现。这意味着必须将具有配置器实现的 jar 放在类路径中,在 jar 的META-INF/services文件夹中包含一个名为org.activiti.engine.cfg.ProcessEngineConfigurator的文件。文件的内容需要是自定义实现的完全限定类名。当流程引擎启动时,日志将显示这些配置器已找到:
INFO org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl - 找到 1 个可自动发现的流程引擎配置器 INFO org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl - 共找到 1 个流程引擎配置器: 信息 org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl - 类 org.activiti.MyCustomConfigurator
请注意,此 ServiceLoader 方法可能不适用于某些环境。可以使用 ProcessEngineConfiguration 的enableConfiguratorServiceLoader属性显式禁用它(默认为 true)。
16.7。高级查询 API:在运行时和历史任务查询之间无缝切换
任何 BPM 用户界面的一个核心组件是任务列表。通常,最终用户处理开放的运行时任务,使用各种设置过滤他们的收件箱。通常还需要在这些列表中显示历史任务,并进行类似的过滤。为了使代码更容易,TaskQuery和HistoricTaskInstanceQuery都有一个共享的父接口,其中包含所有常见的操作(并且大多数操作都是常见的)。
这个通用接口是org.activiti.engine.task.TaskInfoQuery类。org.activiti.engine.task.Task和org.activiti.engine.task.HistoricTaskInstance都有 一个公共超类org.activiti.engine.task.TaskInfo(具有公共属性),它从例如list()方法返回。然而,Java 泛型有时弊大于利:如果你想直接使用TaskInfoQuery类型,它看起来像这样:
1 TaskInfoQuery<? extends TaskInfoQuery<?,?>, ? extends TaskInfo> taskInfoQuery
呃,对。为了解决这个问题,可以使用一个org.activiti.engine.task.TaskInfoQueryWrapper类来避免泛型(以下代码可能来自 REST 代码,它返回一个任务列表,用户可以在其中在打开和完成的任务之间切换):
1
2
3
4
5
6
7
8
9
10
11
12 TaskInfoQueryWrapper taskInfoQueryWrapper = null;
if (runtimeQuery) {
taskInfoQueryWrapper = new TaskInfoQueryWrapper(taskService.createTaskQuery());
} else {
taskInfoQueryWrapper = new TaskInfoQueryWrapper(historyService.createHistoricTaskInstanceQuery());
}
List<? extends TaskInfo> taskInfos = taskInfoQueryWrapper.getTaskInfoQuery().or()
.taskNameLike("%k1%")
.taskDueAfter(new Date(now.getTime() + (3 * 24L * 60L * 60L * 1000L)))
.endOr()
.list();
16.8. 通过覆盖标准 SessionFactory 自定义身份管理
如果您不想像在LDAP 集成中那样使用完整的ProcessEngineConfigurator实现 ,但仍想插入您的自定义身份管理框架,那么您也可以直接在ProcessEngineConfiguration中覆盖SessionFactory类。在 Spring 中,这可以通过在ProcessEngineConfiguration bean 定义中添加以下内容来轻松完成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 <bean id="processEngineConfiguration" class="...SomeProcessEngineConfigurationClass">
...
<property name="customSessionFactories">
<list>
<bean class="com.mycompany.MyGroupManagerFactory"/>
<bean class="com.mycompany.MyUserManagerFactory"/>
</list>
</property>
...
</bean>
MyGroupManagerFactory和MyUserManagerFactory需要实现org.activiti.engine.impl.interceptor.SessionFactory接口。对openSession()的调用返回执行实际身份管理的自定义类实现。对于组,这是一个从org.activiti.engine.impl.persistence.entity.GroupEntityManager继承的类,而对于管理用户,它必须从org.activiti.engine.impl.persistence.entity.UserEntityManager继承。以下代码示例包含组的自定义管理器工厂:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package com.mycompany;
import org.activiti.engine.impl.interceptor.Session;
import org.activiti.engine.impl.interceptor.SessionFactory;
import org.activiti.engine.impl.persistence.entity.GroupIdentityManager;
public class MyGroupManagerFactory implements SessionFactory {
@Override
public Class<?> getSessionType() {
return GroupIdentityManager.class;
}
@Override
public Session openSession() {
return new MyCompanyGroupManager();
}
}
工厂创建的MyCompanyGroupManager正在做实际的工作。不过,您不需要覆盖GroupEntityManager的所有成员,只需覆盖您的用例所需的成员。以下示例说明了它的外观(仅显示了部分成员):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 public class MyCompanyGroupManager extends GroupEntityManager {
private static Logger log = LoggerFactory.getLogger(MyCompanyGroupManager.class);
@Override
public List<Group> findGroupsByUser(String userId) {
log.debug("findGroupByUser called with userId: " + userId);
return super.findGroupsByUser(userId);
}
@Override
public List<Group> findGroupByQueryCriteria(GroupQueryImpl query, Page page) {
log.debug("findGroupByQueryCriteria called, query: " + query + " page: " + page);
return super.findGroupByQueryCriteria(query, page);
}
@Override
public long findGroupCountByQueryCriteria(GroupQueryImpl query) {
log.debug("findGroupCountByQueryCriteria called, query: " + query);
return super.findGroupCountByQueryCriteria(query);
}
@Override
public Group createNewGroup(String groupId) {
throw new UnsupportedOperationException();
}
@Override
public void deleteGroup(String groupId) {
throw new UnsupportedOperationException();
}
}
在适当的方法中添加您自己的实现,以插入您自己的身份管理解决方案。你必须弄清楚基类的哪个成员必须被覆盖。例如以下调用:
1 long potentialOwners = identityService.createUserQuery().memberOfGroup("management").count();
导致对UserIdentityManager接口的以下成员的调用:
1 List<User> findUserByQueryCriteria(UserQueryImpl query, Page page);
LDAP 集成的代码包含如何实现这一点的完整示例。查看 Github 上的代码,特别是以下类 LDAPGroupManager和 LDAPUserManager。
16.9。启用安全的 BPMN 2.0 xml
在大多数情况下,部署到 Activiti 引擎的 BPMN 2.0 流程都处于开发团队的严格控制之下。但是,在某些用例中,可能需要将任意 BPMN 2.0 xml 上传到引擎。在这种情况下,请考虑到恶意用户可能会按照此处所述将服务器关闭。
为了避免上面链接中描述的攻击,可以在流程引擎配置上设置属性enableSafeBpmnXml :
1 <property name="enableSafeBpmnXml" value="true"/>
默认情况下,此功能被禁用!这样做的原因是它依赖于StaxSource类的可用性。不幸的是,在某些平台(例如 JDK 6、JBoss 等)上,该类不可用(由于旧的 xml 解析器实现),因此无法启用安全的 BPMN 2.0 xml 功能。
如果 Activiti 运行的平台确实支持它,请启用此功能。
16.10。事件记录(实验性)
从 Activiti 5.16 版开始,引入了(实验性)事件记录机制。日志机制建立在Activiti 引擎的通用事件机制之上,默认情况下是禁用的。这个想法是捕获来自引擎的事件,并创建一个包含所有事件数据(以及更多)的映射并将其提供给org.activiti.engine.impl.event.logger.EventFlusher它将刷新这些数据到别的地方。默认情况下,使用简单的数据库支持的事件处理程序/刷新器,它使用 Jackson 将所述映射序列化为 JSON,并将其作为EventLogEntryEntity实例存储在数据库中。默认情况下会创建此数据库日志记录所需的表(称为ACT_EVT_LOG)。如果不使用事件记录,则可以删除此表。
要启用数据库记录器:
1 processEngineConfiguration.setEnableDatabaseEventLogging(true);
或在运行时:
1
2 databaseEventLogger = new EventLogger(processEngineConfiguration.getClock());
runtimeService.addEventListener(databaseEventLogger);
EventLogger 类可以子类化。特别是,如果不需要默认数据库日志记录, createEventFlusher()方法需要返回org.activiti.engine.impl.event.logger.EventFlusher接口的实例。managementService.getEventLogEntries(startLogNr, size) ; 可用于通过 Activiti检索EventLogEntryEntity实例。
很容易看出现在如何使用此表数据将 JSON 输入到大数据 NoSQL 存储中,例如 MongoDB、Elastic Search 等。也很容易看到这里使用的类 (org.activiti.engine. impl.event.logger.EventLogger/EventFlusher 和许多 EventHandler 类)是可插入的,并且可以根据您自己的用例进行调整(例如,不将 JSON 存储在数据库中,而是将其直接发送到队列或大数据存储中)。
请注意,这种事件记录机制是对 Activiti传统历史管理器的附加。尽管所有数据都在数据库表中,但它并未针对查询进行优化,也不便于检索。真正的用例是审计跟踪并将其输入大数据存储。
16.11。禁用批量插入
默认情况下,引擎会将同一个数据库表的多个插入语句组合在一个批量插入中,从而提高性能。这已经针对所有支持的数据库进行了测试和实施。
但是,它可能是受支持和测试的数据库的特定版本不允许批量插入(例如,我们有一个 z/OS 上的 DB2 报告,尽管 DB2 通常可以工作),可以在流程引擎上禁用批量插入配置:
1 <property name="bulkInsertEnabled" value="false" />
16.12。安全脚本
实验性:安全脚本功能已作为 Activiti 5.21 版本的一部分添加。
默认情况下,当使用脚本任务时,执行的脚本具有与 Java 委托类似的功能。它可以完全访问 JVM,可以永远运行(由于无限循环)或使用大量内存。但是,Java 委托需要编写并放在 jar 中的类路径中,并且它们具有与流程定义不同的生命周期。最终用户通常不会编写 Java 委托,因为这是开发人员的典型工作。
另一方面,脚本是流程定义的一部分,其生命周期是相同的。脚本任务不需要 jar 部署的额外步骤,但可以从部署流程定义的那一刻开始执行。有时,脚本任务的脚本不是由开发人员编写的。然而,这带来了一个如上所述的问题:脚本具有对 JVM 的完全访问权限,并且在执行脚本时可能会阻塞许多系统资源。因此,允许几乎任何人的脚本不是一个好主意。
为了解决这个问题,可以启用安全脚本功能。目前,此功能仅针对javascript脚本实现。要启用它,请将activiti-secure-javascript依赖项添加到您的项目中。使用 maven 时:
1
2
3
4
5 <dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-secure-javascript</artifactId>
<version>${activiti.version}</version>
</dependency>
添加此依赖项将传递引入 Rhino 依赖项(请参阅https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino)。Rhino 是 JDK 的 javascript 引擎。它曾经包含在 JDK 版本 6 和 7 中,并被 Nashorn 引擎取代。但是,Rhino 项目在被包含在 JDK 之后继续开发。之后添加了许多功能(包括 Activiti 用于实现安全脚本的功能)。在撰写本文时,Nashorn 引擎不具备实现安全脚本功能所需的功能。
这确实意味着脚本之间可能存在(通常很小)差异(例如,importPackage在 Rhino 上工作,但load()必须在 Nashorn 上使用)。这些更改类似于从 JDK 7 切换到 8 脚本。
配置安全脚本是通过一个专用的Configurator对象完成的,该对象在流程引擎被实例化之前传递给流程引擎配置:
1
2
3
4
5
6
7
8 SecureJavascriptConfigurator configurator = new SecureJavascriptConfigurator()
.setWhiteListedClasses(new HashSet<String>(Arrays.asList("java.util.ArrayList")))
.setMaxStackDepth(10)
.setMaxScriptExecutionTime(3000L)
.setMaxMemoryUsed(3145728L)
.setNrOfInstructionsBeforeStateCheckCallback(10);
processEngineConfig.addConfigurator(configurator);
可以进行以下设置:
-
enableClassWhiteListing:当为 true 时,所有类都将被列入黑名单,所有想要使用的类都需要单独列入白名单。这可以严格控制向脚本公开的内容。默认为 false。
-
whiteListedClasses:一组字符串,对应于允许在脚本中使用的类的完全限定类名。例如,要在脚本中公开执行对象,需要将org.activiti.engine.impl.persistence.entity.ExecutionEntityImpl字符串添加到此 Set 中。默认为空。
-
maxStackDepth:在脚本中调用函数时限制堆栈深度。这可用于避免递归调用脚本中定义的方法时发生的 stackoverflow 异常。默认情况下-1(禁用)。
-
maxScriptExecutionTime:允许脚本运行的最长时间。默认情况下-1(禁用)。
-
maxMemoryUsed:允许脚本使用的最大内存(以字节为单位)。请注意,脚本引擎本身也会占用一定数量的内存,此处也计算在内。默认情况下-1(禁用)。
-
nrOfInstructionsBeforeStateCheckCallback:最大脚本执行时间和内存使用量是使用每 x 条脚本指令调用的回调来实现的。请注意,这些不是脚本指令,而是 java 字节码指令(这意味着一个脚本行可能是数百个字节码指令)。默认为 100。
注意: maxMemoryUsed设置只能由支持 com.sun.management.ThreadMXBean#getThreadAllocatedBytes() 方法的 JVM 使用。Oracle JDK 有这个。
还有一个 ScriptExecutionListener 和 ScriptTaskListener 的安全变体:org.activiti.scripting.secure.listener.SecureJavascriptExecutionListener和org.activiti.scripting.secure.listener.SecureJavascriptTaskListener。
它的用法如下:
1
2
3
4
5
6
7
8
9
10 <activiti:executionListener event="start" class="org.activiti.scripting.secure.listener.SecureJavascriptExecutionListener">
<activiti:field name="script">
<activiti:string>
<![CDATA[
execution.setVariable('test');
]]>
</activiti:string>
</activiti:field>
<activiti:field name="language" stringValue="javascript" />
</activiti:executionListener>
有关演示不安全脚本以及如何通过安全脚本功能使其安全的示例,请查看 Github 上的单元测试
17. 工具
17.1. JMX
17.1.1. 介绍
可以使用标准 Java 管理扩展 (JMX) 技术连接到 Activiti 引擎,以获取信息或更改其行为。任何标准的 JMX 客户端都可以用于该目的。启用和禁用 Job Executor、部署新的流程定义文件并删除它们只是使用 JMX 无需编写任何代码即可完成的示例。
17.1.2. 快速开始
默认情况下未启用 JMX。要在其默认配置中启用 JMX,使用 Maven 或通过任何其他方式将 activiti-jmx jar 文件添加到您的类路径就足够了。如果您使用的是 Maven,您可以通过在 pom.xml 中添加以下行来添加适当的依赖项:
1
2
3
4
5 <dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-jmx</artifactId>
<version>latest.version</version>
</dependency>
添加依赖和构建流程引擎后,JMX 连接就可以使用了。只需运行标准 JDK 发行版中可用的 jconsole。在本地进程列表中,您将看到包含 Activiti 的 JVM。如果出于任何原因,正确的 JVM 未在“本地进程”部分中列出,请尝试使用“远程进程”部分中的此 URL 连接到它:
服务:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi/activiti
您可以在日志文件中找到确切的本地 URL。连接后,您可以看到标准的 JVM 统计信息和 MBean。您可以通过选择 MBeans 选项卡并在右侧面板上选择“org.activiti.jmx.Mbeans”来查看 Activiti 特定的 MBean。通过选择任何 MBean,您可以查询信息或更改配置。此快照显示了 jconsole 的外观:
任何不限于 jconsole 的 JMX 客户端都可以用于访问 MBean。大多数数据中心监控工具都有一些连接器,使它们能够连接到 JMX MBean。
17.1.3. 属性和操作
这是目前可用的属性和操作的列表。此列表可能会根据需要在未来的版本中扩展。
MBean | 类型 | 名称 | 描述 |
---|---|---|---|
ProcessDefinitionsMBean |
属性 |
过程定义 |
|
属性 |
部署 |
|
|
方法 |
getProcessDefinitionById(字符串 id) |
|
|
方法 |
删除部署(字符串 id) |
删除给定的部署 |
|
方法 |
suspendProcessDefinitionById(字符串 id) |
挂起给定的流程定义 |
|
方法 |
激活的ProcessDefinitionById(字符串ID) |
使用给定的激活流程定义 |
|
方法 |
suspendProcessDefinitionByKey(字符串 id) |
挂起给定的流程定义 |
|
方法 |
激活的ProcessDefinitionByKey(字符串ID) |
使用给定的激活流程定义 |
|
方法 |
deployProcessDefinition(字符串资源名称,字符串 processDefinitionFile) |
部署流程定义文件 |
|
JobExecutorMBean |
属性 |
isJobExecutor 已激活 |
如果作业执行器被激活,则返回 true,否则返回 false |
方法 |
setJobExecutorActivate(布尔激活) |
根据给定的布尔值激活和停用作业执行器 |
17.1.4. 配置
JMX 使用默认配置,以便使用最常用的配置轻松部署。但是,更改默认配置很容易。您可以通过编程方式或通过配置文件来完成。以下代码摘录显示了如何在配置文件中完成此操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 <bean id="processEngineConfiguration" class="...SomeProcessEngineConfigurationClass">
...
<property name="configurators">
<list>
<bean class="org.activiti.management.jmx.JMXConfigurator">
<property name="connectorPort" value="1912" />
<property name="serviceUrlPath" value="/jmxrmi/activiti" />
...
</bean>
</list>
</property>
</bean>
下表显示了您可以配置的参数及其默认值:
名称 | 默认值 | 描述 |
---|---|---|
禁用 |
错误的 |
如果设置,即使存在依赖项,JMX 也不会启动 |
领域 |
org.activiti.jmx.Mbeans |
MBean 的域 |
创建连接器 |
真的 |
如果为 true,则为启动的 MbeanServer 创建一个连接器 |
MBean域 |
默认域 |
MBean 服务器的域 |
注册端口 |
1099 |
在服务 URL 中显示为注册端口 |
服务网址路径 |
/jmxrmi/activiti |
出现在服务 URL 中 |
连接器端口 |
-1 |
如果大于零,将在服务 URL 中显示为连接器端口 |
17.1.5。JMX 服务 URL
JMX 服务 URL 具有以下格式:
service:jmx:rmi://<hostName>:<connectorPort>/jndi/rmi://<hostName>:<registryPort>/<serviceUrlPath>
hostName
将自动设置为机器的网络名称。
connectorPort
,registryPort
并且serviceUrlPath
可以配置。
如果connectionPort
小于零,则服务 URL 的对应部分将被丢弃,并简化为:
服务:jmx:rmi:///jndi/rmi://:<主机名>:<registryPort>/<serviceUrlPath>
17.2. Maven 原型
17.2.1. 创建测试用例
在开发过程中,有时在实际应用程序中实现它之前创建一个小测试用例来测试一个想法或一个特性是有帮助的。这有助于隔离被测对象。JUnit 测试用例也是交流错误报告和功能请求的首选工具。将测试用例附加到错误报告或功能请求 jira 问题上,可以大大减少其修复时间。
为了便于创建测试用例,可以使用 Maven 原型。通过使用这种原型,可以快速创建标准测试用例。原型应该已经在标准存储库中可用。如果没有,您只需在tooling/archtypes文件夹中键入mvn install即可轻松地将其安装在本地 maven 存储库文件 夹中。
以下命令创建单元测试项目:
mvn archetype:generate \
-DarchetypeGroupId=org.activiti \
-DarchetypeArtifactId=activiti-archetype-unittest \
-DarchetypeVersion=<current version> \
-DgroupId=org.myGroup \
-DartifactId=myArtifact
下表说明了各个参数的作用:
排 | 范围 | 解释 |
---|---|---|
1 |
原型组 ID |
原型的组 id。应该是org.activiti |
2 |
原型ArtifactId |
原型的神器。应该是activiti-archetype-unittest |
3 |
原型版本 |
生成的测试项目中使用的Activiti版本 |
4 |
组 ID |
生成的测试项目的组 id |
5 |
工件 ID |
生成的测试项目的工件 id |
生成项目的目录结构如下:
. ├── pom.xml └── 源 └── 测试 ├── java │ └── org │ └── myGroup │ └── MyUnitTest.java └── 资源 ├── activiti.cfg.xml ├── log4j.properties └── 组织 └── 我的集团 └── my-process.bpmn20.xml
您可以修改 java 单元测试用例及其对应的流程模型,或者添加新的测试用例和流程模型。如果您使用项目来表达错误或功能,则测试用例最初应该失败。然后它应该在修复所需的错误或实现所需的功能后通过。请确保在发送之前通过键入mvn clean来清理项目。