前言

本手册介绍 CUBA 框架业务流程管理扩展组件。

目标读者

该手册是为基于 CUBA 平台构建应用程序的开发者准备的。假设读者熟悉 开发者手册

更多阅读材料

该向导,以及其它 CUBA 平台的文档,可以在 https://www.cuba-platform.com/documentation 访问。

CUBA 业务流程管理扩展组件是基于 Activiti 框架开发的,因此,对于该框架的知识都很有帮助。参阅 http://www.activiti.org 学习更多关于 Activiti 的知识。流程是根据 BPMN 2.0 规范定义的,所以这里假设读者熟悉该标注规范。更多关于 BPMN 2.0 标准可以参阅 http://www.bpmn.org

反馈

如果有关于该手册的任何建议或者意见,可以在 GitHub 上提交 issue。如果发现了拼写或者文字错误、bug 或者不一致的地方,可以直接 fork 我们的 repo 并进行修复。感谢!

1. 快速开始

在本章中,将创建一个小项目来演示如何使用业务流程扩展组件,将以合同审批业务流程作为示例。

通常,审批流程涉及以下步骤:

  • 用户创建 Contract 对象,定义参与者并发起审批流程。

  • 具有 Controller 角色的参与者接收任务并审验附加的合同。

  • 如果审验通过,则合同将传递给分配了 Manager 角色的用户,否则该流程将以 Not valid 状态终止。

  • 在相关管理人员批准或拒绝合同之后,分别以 ApprovedNot approved 状态返回。

1.1. 创建项目

  1. 按照 CUBA Studio 用户向导创建新项目 部分的描述在 CUBA Studio 中创建一个新项目:

    • 项目名称:bpm-demo

    • 项目命名空间:demo

    • 根包:com.company.bpmdemo

  1. 在 CUBA Studio 中打开 Project Properties 编辑器:在主菜单项中点击 CUBA > Project Properties。在 App components 列表中添加 bpm 应用程序组件。当 Studio 建议重新创建 Gradle 脚本时,点击确认。Studio 会下载需要的源码和二进制 工件 。如果项目没有自动同步 Gradle 文件,可以点击 Gradle 工具窗口的 refresh_button 按钮手动同步。

  2. 在本地 HyperSQL 服务创建数据库:主菜单选择 CUBA > Create database。默认数据库名与项目空间名称相同。

  3. 运行应用程序:点击主工具栏中选中 CUBA Application 配置旁边的 run_button 按钮。CUBA 项目树中的 Runs at…​ 部分会显示应用程序的链接,可以用来直接从 Studio 中打开应用程序。

    用户名和密码都是: admin / admin.

    运行的应用程序包含两个主菜单项(AdministrationHelp),以及 安全 和管理子系统的功能。

1.2. 创建数据模型

首先创建 Contract 实体类。

  1. 在 CUBA 项目树的 Data Model 部分点击 New > Entity。会显示 New CUBA Entity 对话框。

  2. Entity name 字段输入实体类名称 – Contract 并点击 OK 按钮。会在工作区显示 Entity Designer 界面。

  3. 使用 Entity Designer 添加属性:

    • number - String 类型

    • date - Date 类型

    • state - String 类型

切换到 Instance name editor 然后在 Name pattern attributes 添加 number 属性。

Contract 实体的创建就完成了。

1.3. 创建标准界面

现在我们为合同创建界面。

在 CUBA 项目树的 Data Model 部分,右键点击 Contract 实体,然后在右键菜单中选择 New > Screen 来创建查看和编辑 Contract 实例的标准界面。点击之后,会显示模板浏览界面。

在可用模板列表中选择 Entity browser and editor screens 然后点击 Next

弹窗中的所有字段都已经填了默认值,不需要修改。点击 Finish

会在 Generic UI 树部分的 Screens 部分显示界面文件:

  • contract-browse.xml – 浏览界面描述文件;

  • ContractBrowse – 浏览界面控制器;

  • contract-edit.xml – 编辑界面描述文件;

  • ContractEdit – 编辑界面控制器。

1.4. 创建 ApprovalHelper Bean

按照 CUBA Studio 用户向导 中的 创建托管Bean 部分描述创建 ApprovalHelper bean。

用下面的代码替换其内容:

package com.company.bpmdemo.core;

import org.springframework.stereotype.Component;
import com.company.bpmdemo.entity.Contract;
import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.Transaction;

import javax.inject.Inject;
import java.util.UUID;

@Component(ApprovalHelper.NAME)
public class ApprovalHelper {
    public static final String NAME = "demo_ApprovalHelper";

    @Inject
    private Persistence persistence;

    public void updateState(UUID entityId, String state) {
        try (Transaction tx = persistence.getTransaction()) {
            Contract contract = persistence.getEntityManager().find(Contract.class, entityId);
            if (contract != null) {
                contract.setState(state);
            }
            tx.commit();
        }
    }
}

将在合同审批流程中调用 ApprovalHelper bean 的 updateState() 方法来设置合同状态。

方法参数:

  • entityId - 合同实体标识符

  • state - 合同状态

1.5. 创建数据库并运行应用程序

在主菜单点击 CUBA > Generate Database Scripts 创建数据库表 。然后,会打开 Database Scripts 界面。

点击 Save and close 按钮保存生成的数据库脚本。

如果需要运行数据库更新脚本,点击 Debug 工具窗旁边的 stop_button 按钮先停止运行的应用程序,然后再点击 CUBA > Update database

现在看看创建的界面在真实的系统中长什么样。在主工具栏点击 run_button 按钮。

在浏览器打开应用程序地址 http://localhost:8080/app 。也可以使用 CUBA 项目树的 Runs At…​ 部分的链接打开项目。

1.6. 创建流程

在本节中,将创建和部署这个合同审批流程。

1.6.1. 创建流程模型

流程模型的最终版本将如下所示:

ProcessFull
Figure 1. 完整流程

我们看看创建模型所需要的步骤。

在应用程序的 web 界面中,打开 BPM > Process Models 界面,然后单击 Create。输入 Contract approval 作为模型名称,然后单击 OKModel Editor 会在新的浏览器标签页中打开。

Tip

在创建或复制流程模型时,在应用程序窗口右下角会显示一个弹窗通知,上面有打开流程模型编辑页面的链接。如果点击 Edit 按钮,会在新的标签页中打开流程模型编辑页面。

在模型属性面板中选择 Process roles 属性。会打开 Process roles 编辑窗口。

ProcessRolesProperty
Figure 2. 流程角色属性

有两种类型的参与者参与此流程:管理人员和操作人员。创建两个角色: ManagerController

ProcessRolesEditor
Figure 3. 流程角色编辑界面

Start event 节点从 Start Events 组拖动到工作区。

我们需要在流程开始时显示一个表单来选择流程参与者。选择开始事件节点。在其属性面板中选择 Start form - 将出现一个表单选择窗口。在 Form name 字段中选择 Standard form。然后添加两个表单参数:

  • procActorsVisible 的值为 true 表示表单上将显示用于选择流程参与人员的表格。

  • attachmentsVisible 的值为 true 表示表单上将显示用于上传附件的表格。

StartForm
Figure 4. 开始表单

Activities 组的 User task 节点添加到模型中,并命名为 Validation

ModelValidationNode
Figure 5. 模型验证节点

选择此节点并将 controller 值分配给属性面板上的 Process role 属性,这样我们就可以定义任务将其被分配给具有 controller 角色的流程参与者。

SelectProcRoleForValidation
Figure 6. 为 Validation 节点选择流程角色

接下来,选择 Task outcomes 属性。将打开任务输出(outcome)编辑窗口。输出定义用户接收任务时可能进行的操作。创建两个输出:ValidNot valid。为两种输出定义 标准表单。为 Not valid 输出添加表单参数 commentRequired=true,因为我们想让用户在选择合同无效时添加意见。

OutcomesForValidation
Figure 7. 验证结果

根据操作人员的决定,我们必须将合同发送给管理人员批准或以 Not valid 状态完成流程。Gateways 组中 Exclusive gateway 节点用于控制流程。将其添加到工作区,然后添加另外两个元素:名称是 Set 'Not valid' stateScript task 和名称是 ApprovalUser task。将 Script task 的流转(flow)命名为 Not valid,将 User task 的流转(flow)命名为 Valid

ModelValidationExclGateway
Figure 8. 模型验证排他网关

选择 Not valid 流。从属性面板中展开 Flow outcome 下拉列表。列表中显示了网关之前的任务输出。选择 Not valid 值。

  1. Not Valid 流输出

NotValidFlowOutcome

现在,如果选择 Not valid 输出,流程将转向这个流。

应将 Valid 流标记为默认流(如果没有其它流条件为 true )。选择 Valid 流并勾选 Default flow 属性。

Warning

对于标记为默认的流,Flow outcome 下拉列表中不能选择任何值。

接下来,选择 Exclusive gateway 并打开其 Flow order 属性编辑界面。确保 Not valid 流位于列表中第一位,必要时请更改流顺序。

ValidationFlowOrder
Figure 9. Validation 流顺序

选中 Set 'Not valid' state 节点。在这个任务中需要将 Contract 实体的 state 属性设置为 Not valid 值。选择节点,将 Script format 属性值设置为 groovy。单击 Script 属性字段 - 将打开脚本编辑界面。复制并粘贴以下代码:

def em = persistence.getEntityManager()
def contract = em.find(com.company.bpmdemo.entity.Contract.class, entityId)
contract.setState('Not valid')

在脚本中可以使用流程变量和 persistence 以及 metadata 平台对象(请参阅 开发人员手册 )。entityId 变量在流程启动时创建并存储关联实体的标识符。

合同状态改变之后,应结束该流程。我们将 End events 组中的 End event 节点添加到工作区并与 Set 'Not valid' state 节点连接。

返回到 Approval 任务。像我们为第一个任务所做的那样为其定义 manager 流程角色。为了将任务同时分配给多个管理人员,请将其 Multi-instance type 属性设置为 Parallel(并行)

ApprovalMutlInstanceType
Figure 10. 多实例批准类型

创建两种任务输出:ApproveRejectTask outcomes 属性)。对这两种输出设置 Standard form 表单,并将 Reject 输出的 commentRequired 参数设置为 true

审批完成后,应根据审批结果为合同分配 ApprovedNot approved 状态。在 Approval 任务 之后添加一个 Exclusive gateway 节点。在排他网关之后添加两个 Service taskSet 'Approved' stateSet 'Not approved' state。它们将与我们之前添加的 Script task 实现同样的功能,但是以不同的方式来实现:通过调用 Spring bean 方法。将 Set 'Approved' state 流命名为 Approved,将 Set 'Not approved' state 流命名为 Not approved

ModelWithApproval
Figure 11. 审批模型

选择 Not approved 流节点并在 Flow outcome 列表中选择 Reject 值。现在,如果有一个管理人员执行了 Reject 操作,流程将转到此流。选择 Approved 流节点并选中 Default flow 复选框。这意味着如果没有启动其它流,则将使用此流。

设置 Exclusive gateway 的流顺序,就像我们前一个做的那样。选择 Exclusive gateway 并打开 Flow order 属性编辑界面。应首先处理 Not approved

ApprovalFlowOrder
Figure 12. Approval 流顺序

我们返回到 Service task。选择 Set 'Approved' state 节点并为其 Expression 属性设置为以下值:

${demo_ApprovalHelper.updateState(entityId, 'Approved')}

将以下脚本应用于 Set 'Not approved' state 节点。

${demo_ApprovalHelper.updateState(entityId, 'Not approved')}

Activiti 引擎已经与 Spring 框架集成,因此我们可以使用名称访问 Spring 托管 bean。entityId 是一个流程变量,用于存储关联到流程的合同的标识符。它的值在流程开始时设置。

使用 End event 连接两种服务类型的任务并单击保存按钮。到此,模型已准备就绪,现在我们可以继续进行模型部署。

ProcessFull
Figure 13. 流程模型

1.6.2. 流程模型部署

模型部署过程包括以下步骤:

  • 生成 BPMN 2.0 格式的流程模型 XML。

  • 将流程部署到 Activiti 引擎内部数据表中。

  • 创建与 Activiti 流程相关的 ProcDefinition 对象。

  • 为模型中定义的流程角色创建 ProcRole 对象。

Process Models 界面上的列表中选择模型。点击 Deploy 按钮。将显示模型部署窗口。由于是首次部署模型,因此选择 Create new process 选项。也可以选择部署模型到现有流程来应用模型的更改。单击 OK,会创建流程定义。

DeployModelScreen

打开界面 BPM > Process Definitions。打开 Contract approval 项进行编辑。Code 字段值是 contractApproval。记住这个值 - 我们将在本章后面用它来识别流程定义。

ProcDefinitionEdit
Figure 14. 流程定义编辑

1.7. 使界面适应流程

在本章中,我们将在合同编辑界面上添加一个使用合同审批流程的功能。

1.7.1. 合同编辑界面布局

在 Studio 中打开 contract-edit.xml 界面,并使用以下代码完全替换其内容:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        caption="msg://editorCaption"
        focusComponent="form"
        messagesPack="com.company.bpmdemo.web.contract">
    <data>
        <instance id="contractDc"
                  class="com.company.bpmdemo.entity.Contract"
                  view="_local">
            <loader id="contractDl"/>
        </instance>
        <collection id="procAttachmentsDc"
                    class="com.haulmont.bpm.entity.ProcAttachment"
                    view="procAttachment-browse">
            <loader id="procAttachmentsDl">
                <query><![CDATA[select e from bpm$ProcAttachment e
                                where e.procInstance.entity.entityId  = :entityId
                                order by e.createTs]]>
                </query>
            </loader>
        </collection>
    </data>
    <dialogMode height="600"
                width="800"/>
    <layout expand="editActions"
            spacing="true">
        <form id="form"
              dataContainer="contractDc">
            <column width="250px">
                <textField id="numberField"
                           property="number"/>
                <dateField id="dateField"
                           property="date"/>
                <textField id="stateField"
                           property="state"/>
            </column>
        </form>
        <groupBox id="procActionsBox"
                  caption="msg://process"
                  spacing="true"
                  width="AUTO"
                  orientation="vertical">
            <fragment id="procActionsFragment"
                      screen="bpm_ProcActionsFragment"/>
        </groupBox>
        <groupBox caption="msg://attachments"
                  height="300px"
                  spacing="true"
                  width="700px">
            <table id="attachmentsTable"
                   dataContainer="procAttachmentsDc"
                   height="100%"
                   width="100%">
                <columns>
                    <column id="file.name"/>
                    <column id="author"/>
                    <column id="type"/>
                    <column id="comment"
                            maxTextLength="50"/>
                </columns>
            </table>
        </groupBox>
        <hbox id="editActions"
              spacing="true">
            <button action="windowCommitAndClose"/>
            <button action="windowClose"/>
        </hbox>
    </layout>
</window>

该界面包含用于合同编辑的一些字段、用于显示流程操作的 fragment 以及显示流程附件的表格。

1.7.2. 合同编辑界面控制器

打开 ContractEdit 界面控制器,并使用以下代码替换其内容:

package com.company.bpmdemo.web.contract;

import com.haulmont.bpm.entity.ProcAttachment;
import com.haulmont.bpm.gui.procactionsfragment.ProcActionsFragment;
import com.haulmont.cuba.gui.app.core.file.FileDownloadHelper;
import com.haulmont.cuba.gui.components.Table;
import com.haulmont.cuba.gui.model.CollectionLoader;
import com.haulmont.cuba.gui.model.InstanceContainer;
import com.haulmont.cuba.gui.model.InstanceLoader;
import com.haulmont.cuba.gui.screen.*;
import com.company.bpmdemo.entity.Contract;

import javax.inject.Inject;

@UiController("demo_Contract.edit")
@UiDescriptor("contract-edit.xml")
@EditedEntityContainer("contractDc")
public class ContractEdit extends StandardEditor<Contract> {
    private static final String PROCESS_CODE = "contractApproval";

    @Inject
    private CollectionLoader<ProcAttachment> procAttachmentsDl;

    @Inject
    private InstanceContainer<Contract> contractDc;

    @Inject
    protected ProcActionsFragment procActionsFragment;

    @Inject
    private Table<ProcAttachment> attachmentsTable;

    @Inject
    private InstanceLoader<Contract> contractDl;

    @Subscribe
    private void onBeforeShow(BeforeShowEvent event) {
        contractDl.load();
        procAttachmentsDl.setParameter("entityId",contractDc.getItem().getId());
        procAttachmentsDl.load();
        procActionsFragment.initializer()
                .standard()
                .init(PROCESS_CODE, getEditedEntity());

        FileDownloadHelper.initGeneratedColumn(attachmentsTable, "file");
    }
}

我们详细了解一下控制器代码。

ProcessActionsFragment 是显示用户当前可用的流程操作的 fragment。在初始化时,fragment 通过两个参数搜索相关的 ProcInstance 对象:流程代码和实体实例。如果未找到 ProcInstance 对象,则创建一个新实例,该界面显示开始流程按钮。如果找到相关的流程实例,则 fragment 将搜索当前用户的未完成的流程任务,并显示流程任务的操作按钮。有关详细信息,请参阅 ProcActionsFragment

在合同编辑界面中,流程操作 fragment 的初始化在 onBeforeShow() 方法中执行。该方法的关键部分是调用 init(PROCESS_CODE, getEditedEntity())PROCESS_CODE 保存了一个流程代码(contractApproval - 我们在模型部署期间看到了这个值,参阅(流程模型部署)。第二个参数 getEditedEntity() 是当前合同实体实例。

standard() 方法返回的初始化器执行以下操作:

  • 初始化标准流程操作断言(predicate),这个断言在流程启动前和流程任务完成之前调用。断言提交实体编辑界面。

  • 初始化标准监听器,用于显示 "流程开始" 或 "任务完成" 等通知,并在流程启动或流程任务完成后刷新 procActionsFragment

1.8. 使用应用程序

默认情况下,Studio 中启用了热部署机制,因此界面中的所有更改都应该已应用于正在运行的应用程序。如果禁用热部署,请使用 CUBA > Restart Application Server 菜单命令重新启动服务。

1.8.1. 创建用户

要演示该流程,请在 Administration > Users 界面中创建三个用户:

  • 用户名: norman, 名字: Tommy, 姓氏: Norman

  • 用户名: roberts, 名字: Casey, 姓氏: Roberts

  • 用户名: pierce, 名字: Walter, 姓氏: Pierce

1.8.2. 创建合同并启动流程

  1. 打开 Application > Contracts 界面并创建新合同。填写 NumberDate 字段,然后单击 Save

  2. 单击 Start process 按钮,应出现启动流程表单。在模型创建过程中,我们为 Start event 节点定义了 Standard form。其属性为 procActorsVisible=trueattachmentsVisible=true。这就是为什么现在我们看到带有流程参与者和附件表格的表单。

  3. 输入意见并添加参与者:操作人员是 norman 、两个管理人员是 pierceroberts

  4. 使用附件表格中的 Upload 按钮添加附件。

    StartProcessForm
    Figure 15. 开始流程表单
  5. 点击 OK ,现在流程开始。

1.8.3. 操作人员验证阶段

norman 登录。

当流程到达 User task 节点时,会创建一个 ProcTask 对象。此对象链接到指定的流程参与者。BPM 扩展组件有一个界面,用于显示用户当前未完成的任务:BPM > Process Tasks

ProcTaskBrowse
Figure 16. 流程任务浏览界面

我们看到用户 norman 有一个未完成的任务:Contract approval 流程的 Validation 任务。选择它并单击 Open entity editor 按钮。将出现合同编辑界面:

ContractEditValidation
Figure 17. 合同编辑验证

当前用户(norman)有一个未完成的任务(ProcTask),因此 procActionsFragment 显示可用的流程操作。在定义 Validation UserTask 节点时,我们设置了两个可能的输出:ValidNot valid。这就是为什么两个按钮被添加到 procActionsFragment

点击 Valid 按钮并在打开的窗口中输入意见:

ValidationCompleteForm
Figure 18. 验证完成表单

点击 OK

成功验证后,合同应进入管理人员并行审批环节。

1.8.4. 管理人员审批阶段

pierce 用户登录。

打开当前用户的任务列表: BPM > Process tasks。可以看到 Approval 任务。

TaskListApproval
Figure 19. 审批任务列表

选择任务,这次单击 Open process instance 按钮。将打开用于处理流程实例的系统界面。

ProcInstanceEditApproval
Figure 20. ProcInstance 编辑审批

在这个界面显示流程的开始时间、发起人、附件列表、参与者和流程实例任务列表等信息。该界面还允许打开关联实体编辑界面并执行流程操作。

注意 Tasks 表格。上一个任务 Validation 已经完成,并且输出为 Valid,同时已经为管理员 pierceroberts 创建了两个新的 Approval 任务。

点击 Approve 按钮审批合同。

然后以 roberts 登录。从 Application > Contracts 的列表中打开合同。

用户 roberts 有一个未完成任务,所以 procActionsFragment 显示 ApproveReject 操作。点击 Reject 按钮。

CompleteApprovalForm
Figure 21. 审批表单

在模型设计器中定义 Reject 输出时,将 commentRequired 表单参数设置为 true,因此会看到任务完成表单中需要输入意见。输入意见并点击 OK

其中一位管理人员拒绝了合同,因此应将 Not approved 状态分配给合同。我们检查一下,打开合同。

ContractEditNotApproved
Figure 22. 不批准

审批流程以 Not approved 状态完成。

2. 数据模型

DataModel
Figure 23. 数据模型
Tip

名称以 act* 前缀开头的属性存储的是 Activiti 中相关记录的标识符。

  • ProcModel - 流程模型。模型属性:

    • name - 模型名称。

    • description - 模型描述。

    • actModelId - Activiti 引擎的模型 ID,存储在 ACT_RE_MODEL 表中。

  • ProcDefinition -流程定义。可以从数据模型获取或者直接从 XML 文件加载。实体属性:

    • name - 流程名称。

    • code - 流程代码。它可用于从应用程序代码中确定实体实例。

    • actId - Activiti 的流程的 ID。如需访问 BPMN 模型,则必须(它会读取 extensionElements)

    • active - 定义当前流程定义是否允许新的流程实例启动。

    • procRoles - 定义流程参与者的对象集合。

    • model - 模型的引用。

  • ProcRole - 流程角色。在流程部署时会自动创建此类型对象。流程角色定义了流程参与者的类型。ProcRole 定义流程角色类型。实体属性:

    • name - 角色名称。

    • code - 角色代码。它可以被应用程序用于角色识别。

    • order - 序号。应用程序可以使用它以适当的顺序显示角色。

    • procDefinition - 流程定义的引用。

  • ProcInstance - 流程实例。可以关联项目实体启动流程实例,也可以不关联项目实体。例如,合同审批流程实例可以与合同实体相关联。实体属性:

    • description - 流程实例的描述。

    • startDate - 流程实例开始日期。

    • endDate - 流程实例结束日期。

    • startedBy - 启动流程的用户。

    • active - 标识流程实例是否处理已启动但尚未完成的状态。

    • cancelled - 标识流程是否已经取消。

    • actProcessInstanceId - Activiti 的相应 ProcessInstance 的标识符。

    • startComment - 流程启动时的意见。

    • cancelComment - 流程取消时的意见。

    • entityName - 链接的实体名称。

    • entityId - 链接的实体 ID。

    • entityEditorName - 用于编辑关联实体的界面名称。

    • procTasks - 流程任务集合。

    • procActors - 流程参与人员集合。

    • procAttachments - 流程附件集合。

  • ProcActor - 流程参与者。该实体为流程实例定义一个具有的特定角色的执行者。实体属性:

    • user - 用户的引用。

    • procInstance - 流程实例的引用。

    • procRole - 流程角色的引用。

    • order - 序号。该字段用于为多用户的有序任务定义参与人员的顺序。

  • ProcTask - 流程任务。当流程到达用户任务节点时,将自动创建此类对象。实体属性:

    • name - 任务名称。

    • startDate - 任务开始日期。

    • claimDate - 要求处理日期。该字段用于任务没有显式的流程参与者的情况。

    • endDate - 任务结束日期。

    • outcome - 任务完成输出。

    • comment - 任务完成意见。

    • procActor - 执行人。

    • actTaskId - Activiti 任务 ID。此字段用于报告 Activiti 引擎任务的完成情况。

    • actExecutionId - Activiti 执行 ID。该字段用于读/写流程变量。

    • actTaskDefinitionKey - 在流程 XML 中,它是 UserTask 元素的 id 属性。用于构建存储任务结果的变量名称([task_id]_result)。请参阅依赖任务输出的流转

    • cancelled - 标识任务是否在以取消的方式完成。

    • candidateUsers - 群组任务的可能的程参与者列表。

    • procInstance - 流程实例的引用。

  • ProcAttachment - 流程附件。实体属性:

    • file - FileDescriptor 的引用。

    • type - 附件类型(ProcAttachmentType)。

    • comment - 意见。

    • author - 附件作者的引用。

    • procInstance - 流程实例的引用。

    • procTask - 流程任务的可选引用。

  • ProcAttachmentType - 附件类型。实体属性:

    • code - 附件类型代码。

    • name - 附件类型名称。

3. 功能

BPM 扩展组件使用 Activiti 引擎来执行业务流程。Activiti Explorer 中的模型编辑器被用于使用 BPMN 2.0 对流程建模。除了 Activiti 框架功能之外,BPM 扩展组件还提供了本节所描述的其它功能。对 Activiti 框架的说明超出了本手册的范围,可以通过 https://www.activiti.org/userguide/ 查看详细说明。

3.1. BpmActivitiListener

创建模型时,BpmActivitiListener 事件监听器会自动被添加到流程中。BpmActivitiListener 实现了 ActivitiEventListener 接口(参阅 http://www.activiti.org/userguide/#eventDispatcher ) 。当某些流程事件发生时(例如,用户任务开始、流程取消、任务完成等),监听器负责创建和修改 BPM 实体。这个监听器创建 ProcTasks 对象,并且为 ProcInstance 设置 endDate 值。

3.2. 流程角色

流程角色定义流程参与者类型,例如 “操作员”或“管理员”。要打开流程角色编辑界面,请在模型属性面板中选择 Process roles 属性。有关角色的信息将在模型部署期间写入流程 XML(process 元素的 extensionElements 部分)。

流程角色定义:

<process id="testProcess" name="Test process">
    <extensionElements>
         <cuba:procRoles>
            <cuba:procRole name="Manager" code="manager"/>
            <cuba:procRole name="Operator" code="operator"/>
        </cuba:procRoles>
    </extensionElements>
</process>

3.3. 启动流程表单

要定义将在流程开始时显示的表单,请使用 Start event 节点的 Start form 属性。在流程表单章节查阅更多有关表单的内容。

启用流程表单定义:

<startEvent id="startEvent">
  <extensionElements>
    <cuba:form name="standardProcForm">
      <cuba:param name="procActorsVisible" value="true"></cuba:param>
    </cuba:form>
  </extensionElements>
</startEvent>

3.4. 用户任务

要定义任务受理人,请在 User Task 节点的 Process role 属性中选择一个流程角色。当流程到达用户任务时,将在所有流程操作员中找到给定角色的流程操作员,并将任务分配给它们。

任务中的流程角色:

<userTask id="managerApproval" name="Manager approval">
    <extensionElements>
        <cuba:procRole>manager</cuba:procRole>
    </extensionElements>
</process>

如果要将任务分配给多个用户,请为 User Task 节点的 Multi-instance type 属性设置 ParallelSequential

还可以在 User Task 节点的 assignee 属性中指定任务受理人。属性值可以是包含一个 CUBA 用户标识符的字符串常量:da8159cc-757f-7f59-6579-7f629ac02f72,也可以是一个包含用户 id 字符串的变量: ${varialbeName},或者一个调用服务的表达式,在服务中返回用户标识: ${someService.getSomeUserId()}。请注意,procRole 属性必须定义。当流程到达此类用户任务时,将搜索指定用户和流程角色的 ProcActor 实例。如果它不存在,则创建新的 ProcActor 对象。要在模型设计界面中指定受理人,请选择 User Task,单击 Show advanced properties 链接,然后点击进入 Assignments 属性编辑界面。将出现一个新对话框,在这个界面填写 Assignee 属性。

如果不希望将任务立即分配给特定用户,而是显示在该组用户的可用任务列表中,请设置 Claim allowed 属性。然后,其中一名候选人将能够领取该任务。任务候选者在具有流程 Process role 属性指定的角色的参与者范围内定义。

没有明确指定执行者的任务:

<userTask id="managerApproval" name="Manager approval">
    <extensionElements>
        <cuba:claimAllowed>true</cuba:claimAllowed>
    </extensionElements>
</process>

3.5. 任务输出

通常,期望用户对任务做出决定,例如,批准或拒绝合同。流程的下一个路线取决于此决定。User task 节点的 Task outcomes 属性用于定义输出列表。可以分别为每个输出定义选择输出时应显示的名称和表单。应该传递给表单的参数也可以进行定义(参阅流程表单)。

任务结果:

<userTask id="managerApproval" name="Manager approval">
    <extensionElements>
        <cuba:outcomes>
            <cuba:outcome name="approve">
                <cuba:form name="standardProcessForm">
                    <cuba:param name="commentRequired">true</cuba:param>
                    <cuba:param name="attachmentsVisible">true</cuba:param>
                </cuba:form>
            </cuba:outcome>
            <cuba:outcome name="reject">
                <cuba:form name="someOtherProcessForm">
                </cuba:form>
            </cuba:outcome>
        </cuba:outcomes>
    </extensionElements>
</process>

3.6. 依赖任务输出的流转

BPMN 2.0 表示法没有为用户任务提供定义多个输出的方法。要使流程在需要的方向上继续,请使用 Exclusive gateway(排它网关)。排它网关的条件中可以使用前一任务的结果。当用户完成任务时,其结果将写入名为 [taskId]_result 的流程变量。变量类型是 ProcTaskResult

ProcTaskResult 类的方法:

  • count(String outcomeName): int - 返回使用给定输出完成任务的用户数。

  • exists(String outcomeName): boolean - 如果有用户使用给定输出完成任务,则返回 true

任务完成结果对象用于网关输出流的 Flow condition(流条件) 表达式。

Example

TaskOutcomesExample
Figure 24. 任务结果示例

假设 approval 任务被并行分配给多个用户。为任务定义了两个输出:approvereject。当所有用户完成任务后,该流程将转到排他网关。我们想要实现以下行为:如果有人选择 reject 选项,那么转到 Rejected 流程; 如果每个人都批准了这项任务,则转到 Approved 流程。

在流程输出字段中定义条件

定义流条件的最简单方法是在流节点的 Flow outcome 属性中选择前一任务输出的名称。如果至少有一个任务使用所选输出完成,则该流将被激活。

为流节点定义复杂条件

如果需要为输出实现更复杂的条件,可以在 Flow condition 字段中定义它。例如,“超过 5 个用户选择拒绝选项”条件如下所示:

${approval_result.count('reject') > 5}

3.6.1. 流顺序

需要注意的是,流顺序必须定义。否则,Activiti 会在具有显式条件的流之前处理默认流。要定义流顺序,请使用 Exclusive gateway 节点的 Flow order 属性。

3.7. 执行脚本

Script task 节点被用于执行脚本。系统会分析 Script 属性值的内容,如果该值是有效的文件路径且该文件存在,则该文件中的脚本被执行,否则 Script 字段值作为脚本执行。

请注意,可以在脚本中使用 persistencemetadata 对象。

3.8. 中间件 Bean 方法调用

Service task 节点用于调用服务方法。Activiti 引擎与 Spring 框架集成,因此可以按名称访问中间件 bean。要调用托管 bean 的方法,请将下面的表达式用于 Expression 字段:

${beanName.methodName(processVarName, 'someStringParam')}

可以使用流程变量作为方法参数,包括在流程启动时自动创建的变量(entityIdbpmProcInstanceId 等,如流程运行时服务中所述)。

3.9. 通过计时器完成任务

要在一定时间间隔后完成任务,应该:

  • Boundary timer event 节点添加到任务节点。

  • 绘制从计时器节点到另一个所需节点的流。

  • 在计时器节点的 Time duration 属性中定义时间间隔的表达式。例如,PT15M 是 15 分钟间隔的表达式。

  • Cancel activity 属性设置为 true。当计时器被触发时,它将取消当前任务。

  • Timer outcome 属性中,定义在计时器完成任务时应使用的任务输出。

TimerEdit
Figure 25. 计时器编辑

定义计时器的输出:

<boundaryEvent id="managerApprovalTimer" cancelActivity="true" attachedToRef="managerApproval">
    <extensionElements>
        <cuba:outcome>approve</cuba:outcome>
    </extensionElements>
</boundaryEvent>
Tip

默认情况下,处理计时器的 Job 执行器是禁用状态。要启用它,请设置应用程序属性 bpm.activiti.asyncExecutorEnabled = true

3.10. 本地化

流程可能包含用于在用户界面中显示任务或输出的本地化消息。

要打开本地化消息编辑界面,请在模型属性面板中选择 Localization 属性。

要本地化任务名称,请创建一个以任务 ID 作为键的记录。

要本地化任务输出名称,请使用类似 TASK_ID.OUTCOME_NAME 的表达式作为键创建记录。

要本地化流程角色名称,请使用角色代码作为键创建记录。

本地化消息:

<process id="testProcess" name="Test process">
    <extensionElements>
        <cuba:localizations>
            <cuba:localization lang="en">
                <cuba:msg key="key1" value="value1"/>
                <cuba:msg key="key2" value="value2"/>
            </cuba:localization>
            <cuba:localization lang="ru">
                <cuba:msg key="key1" value="value1"/>
                <cuba:msg key="key2" value="value2"/>
            </cuba:localization>
      </cuba:localizations>
    </extensionElements>
</process>

3.11. 子模型

Structural 分组的 Sub model 节点允许将现有模型作为新模型的一部分。部署流程时子模型元素被插入到当前模型,并且会根据流程连接的结果生成流程 XML。

3.12. 模型设计器中的自定义元素

BPM 扩展组件允许为流程模型设计器创建自定义元素。基本上,自定义元素就是 ServiceTask,它使开发人员不再需要为方法调用输入很长的表达式,例如 ${app_MyBean.someMethod(argument1, 'argument2')}。下面是自定义元素创建的示例。

假设有一个名为 app_DiscountManager 的中间件 bean。bean 中有一个 makeDiscount(BigDecimal discountPercent, UUID entityId) 方法。该方法通过减去折扣来更新合同金额。

在此示例中,我们会创建一个将调用该方法的自定义模型元素。折扣百分比将作为模型元素的参数被定义。

使用菜单项 BPM > Model Elements Editor 打开模型元素编辑界面。

单击 Add group 按钮并输入组名称 - Discounts

StencilSetAddGroup
Figure 26. 添加组

选择创建的组,然后单击 Add element 按钮。

StencilSetAddStencil
Figure 27. 添加模板

为元素属性输入以下值:

  • 标题: Contract discount

  • 模型 ID: contractDiscount

  • 图标:单击 Upload 按钮并选择图标文件(可选)

  • Bean 名称:选择 app_DiscountManager

  • 方法名称:选择 makeDiscount

Warning

Bean name 仅查找实现了接口的 bean。Method name 仅查找实现的接口方法。

Method arguments 表格包含方法参数。可以更改参数显示名称和参数默认值。

单击 Save 按钮保存设置的元素。

打开流程模型编辑界面(BPM > Process Models)。元素列表中有 Discounts 组和 Contract discount 元素。将新元素拖放到模型中并选中它。将看到折扣百分比字段和存储实体标识符的流程变量名称显示在界面上。

StencilSetModel
Figure 28. 设置模型
Tip

entityId 是流程变量的名称。此流程变量会被自动添加到关联到实体的每个流程。变量存储实体标识符,可以在任何方法调用中使用它。

在流程部署期间,自定义元素将被转换为 serviceTask

<serviceTask id="sid-5C184F22-6071-45CD-AEA9-1792512BBDCE" name="Make discount" activiti:expression="${app_DiscountManager.makeDiscount(10,entityId)}"></serviceTask>

元素可以被导出到 ZIP 存档,然后从存档中还原。这有助于在开发人员的计算机上创建新元素然后导入到生产服务器。使用元素编辑界面上的相应按钮执行导入和导出。

Reset 按钮删除所有自定义组和元素,并将元素的设置恢复为初始状态。

4. 主要服务

本节仅包含服务的一般描述。Java 类文档中提供了详细的服务方法描述。

4.1. 流程仓库服务

它旨在用于流程定义。该服务用于:

  • 从 XML 加载流程定义;

  • 撤销 Activiti 引擎中的流程;

  • 将 JSON 模型转换为 BPMN XML。

要访问中间件的服务功能,请使用 ProcessRepositoryManager bean。

4.2. 流程运行时服务

被设计用于与流程实例一起使用。服务方法允许:

  • 启动一个流程

  • 取消一个流程;

  • 完成一个任务;

  • 将一个任务分配给用户。

当流程启动时,以下流程变量会被自动创建:

  • bpmProcInstanceId - ProcInstance 对象 ID;

  • entityName - 关联的实体名称;

  • entityId - 关联的实体 ID。

要访问中间件的服务功能,请使用 ProcessRuntimeManager bean。

4.3. 流程表单服务

该服务用于提供以下信息:

  • 任务输出;

  • 应该显示输出的表单;

  • 用于流程启动和取消的表单。

要访问中间件的服务功能,请使用 ProcessFormManager bean。

4.4. 流程消息服务

该服务用于访问在流程中定义的本地化消息。

要访问中间件的服务功能,请使用 ProcessMessagesManager bean。

4.5. 模型服务

该服务用于创建和更新 Activiti 内部表中的模型。此外,它可用于处理模型的 JSON 表示。

5. UI 组件

本节包含关于 BPM 扩展组件提供的用户界面组件的介绍。

5.1. ProcActionsFragment

流程操作 Fragment

ProcActionsFragment 用于处理流程操作。fragment 被初始化之后,以下组件会自动显示:

  • 启动流程按钮,在流程尚未启动的情况下显示

  • 处理任务按钮,在流程已启动且当前用户具有活动任务的情况下显示

  • 取消流程按钮

  • 任务信息面板(显示任务的名称和创建日期等信息)

可以将断言分配给每个流程操作以检查操作是否可以被执行(例如,断言提交编辑器,如果提交失败,则不执行流程操作)。还可以定义 post-action 监听器(例如,监听器将关闭编辑器并显示一条通知)。

ProcActionsFragment 必须与 ProcInstance 关联。在 fragment 初始化期间进行关联。

fragment 初始化的一个示例:

procActionsFragment.initializer()
        .setBeforeStartProcessPredicate(() -> {
            getScreenData().getDataContext().commit();
            return true;
        })
        .setAfterStartProcessListener(() -> {
            notifications.create()
                    .withCaption(messageBundle.getMessage("processStarted"))
                    .withType(Notifications.NotificationType.HUMANIZED)
                    .show();
        })
        .init(PROCESS_CODE, getEditedEntity());
  • initializer() 方法返回一个用于初始化 fragment 的对象。

  • setBeforeStartProcessPredicate 方法设置将在流程启动之前被执行的断言。如果断言返回 false,则流程启动会中断。

  • setAfterStartProcessListener 方法定义一个在流程启动操作被执行后将被调用的监听器。

  • init 方法有两个参数:流程代码和实体实例。此方法被调用时,将搜索 ProcInstance 对象,这个对象与实体实例关联,并且具有对给定代码的 ProcDefinition 的引用。如果这个 ProcInstance 对象存在,则将 fragment 关联到它,否则创建一个新的 ProcInstance 对象。

初始化 ProcActionsFragment 的最简单方法是使用 standard() 初始化程序:

procActionsFragment.initializer()
        .standard()
        .init(PROCESS_CODE, getEditedEntity());

标准初始化器执行以下操作:

  • 创建在启动流程之前提交实体编辑器和完成任务操作的断言

  • 创建显示 “流程启动” 或 “任务完成” 等通知并且刷新 ProcActionsFragment 的监听器。

以下是用于自定义 fragment 的方法列表。

流程生命周期
  • initializer() - 返回一个 fragment 初始化器的新实例。

  • init() - 尝试通过指定的流程代码和实体引用来查找流程实例。如果未找到流程实例,则会创建一个新流程实例。然后适用于当前用户和流程实例流程操作 UI 被初始化。

流程配置
  • setStartProcessEnabled() - 定义是否可以启动该流程。

  • setCancelProcessEnabled() - 定义是否可以取消该流程。

  • setCompleteTaskEnabled() - 定义任务是否可以完成。

  • setClaimTaskEnabled() - 定义是否可以自己将任务分配给用户。

  • setTaskInfoEnabled() - 定义是否启用具有本地化任务名称及其开始日期的布局。

  • setButtonWidth() - 设置操作控制按钮的宽度。默认值为 150px。

  • addActionButton() - 允许在自动生成的按钮旁边给 fragment 添加自定义按钮。

断言
  • setBeforeStartProcessPredicate() - 设置在流程启动之前将被执行的断言。如果断言返回 false,则流程启动将被中断。

  • setBeforeCompleteTaskPredicate() - 设置在任务完成之前将被执行的断言。如果断言返回 false,则任务完成将被中断。

  • setBeforeClaimTaskPredicate() - 设置在向用户分配任务之前将被执行的断言。如果断言返回 false,则任务分配将被中断。

  • setBeforeCancelProcessPredicate() - 设置在任务取消之前将被执行的断言。如果断言返回 false,则该任务不会被取消。

流程和任务监听器
  • setAfterStartProcessListener() - 定义将在执行流程启动操作后将被调用的监听器。

  • setAfterCompleteTaskListener() - 定义将在执行任务完成操作后将被调用的监听器。

  • setAfterClaimTaskListener() - 定义将在执行任务分配操作后将被调用的监听器。

  • setAfterCancelProcessListener() - 定义将在执行流程取消操作后将被调用的监听器。

变量和参数提供者
  • setStartProcessActionProcessVariablesSupplier() - 设置流程变量提供者。流程变量提供者返回流程变量字典,必须在流程启动时将其添加到 Activiti 流程实例。

  • setCompleteTaskActionProcessVariablesSupplier() - 设置流程变量提供者。流程变量提供者返回流程变量字典,必须在任务完成时将其添加到 Activiti 流程实例。

  • setStartProcessActionScreenParametersSupplier() - 设置流程表单界面参数提供者。这些界面参数提供者返回一个界面参数字典,该字典将传递给通过 StartProcessAction 显示的流程表单。

  • setCompleteTaskActionScreenParametersSupplier() - 设置流程表单界面参数提供者。这些界面参数提供者返回一个界面参数字典,该字典将传递给通过 CompleteTaskAction 显示的流程表单。



5.2. 流程表单

流程表单接口

在模型编辑界面中声明用户任务输出或启动事件节点时,可以设置将显示给用户的表单。表单类应该实现 ProcForm 接口。

ProcForm 接口的方法:

  • getComment(): String - 如果表单在流程启动时显示,则返回要被写入 ProcTask 对象 comment 字段或 procInstance 对象的 startComment 字段的值。

  • getFormResult(): Map<String, Object> - 返回表单提交后将被添加到流程变量的对象列表。

用于流程模型设计器的表单列表

根据 bpm.formsConfig 应用程序属性中定义的配置文件构建在流程模型设计器中可用的表单列表。要添加新的流程表单,请执行以下操作:

  1. 创建并注册表单的界面。界面控制器必须实现 ProcForm 接口。

  2. 创建一个 XML 文件,例如 app-bpm-forms.xml,它将包含自定义表单的描述,并将其放在 webgui 模块的 src 目录下。例如:

    <?xml version="1.0" encoding="UTF-8"?>
    <forms xmlns="http://schemas.haulmont.com/cuba/bpm-forms.xsd">
        <form name="myCustomForm" default="true">
            <param name="someParam" value="hello"/>
            <param name="otherParam"/>
        </form>
    </forms>

    这里的 myCustomForm 是界面 id。

    上述配置还描述了可用的表单参数及其名称和默认值。

    具有 default="true" 属性的表单将在模型中被用作默认表单。

  3. 覆盖 web-app.properties 文件中的 bpm.formsConfig 属性。

    bpm.formsConfig = bpm-forms.xml app-bpm-forms.xml


6. 示例

6.1. 任务执行示例

此示例演示以下内容:

  • 如何使用 ProcActionsFragment 以编程方式在流程开始时创建流程参与者

  • 如何使用 ProcActionsFragment 将流程变量传递给流程实例

  • 如何获取和修改由 ProcActionsFragment 创建的标准流程操作(例如,更改“启动流程”按钮标题)

  • 如何在没有 ProcActionsFragment 的情况下以编程方式启动流程

  • 每次流程进行一步时如何使用 ActivitiEventListener 自动更新 processState 字段

此示例使用 Task execution - 1 流程模型:

TaskExecution1Model
Figure 29. 任务执行模型

在此示例中,我们不使用 StandardProcForm 来分配流程参与者。我们通过 ProcActionsFragment流程开始前断言 来完成。请参阅 setBeforeStartProcessPredicate() 方法的使用。

TaskEdit.java
@UiController("bpmsamples$Task.edit")
@UiDescriptor("task-edit.xml")
@EditedEntityContainer("taskDc")
@LoadDataBeforeShow
public class TaskEdit extends StandardEditor<Task> {

    public static final String PROCESS_CODE = "taskExecution-1";

    @Inject
    protected ProcActionsFragment procActionsFragment;

    @Inject
    protected BpmEntitiesService bpmEntitiesService;

    @Inject
    protected ProcessRuntimeService processRuntimeService;

    @Inject
    private MessageBundle messageBundle;

    @Inject
    private Notifications notifications;

    @Inject
    private Messages messages;

    @Inject
    private InstanceLoader<Task> taskDl;

        ...

    /** * Method starts the process without {@link ProcActionsFragment} */
    @Subscribe("startProcessProgrammaticallyBtn")
    private void onStartProcessProgrammaticallyBtnClick(Button.ClickEvent event) {

        commitChanges()
                .then(() -> {
            /*The ProcInstanceDetails object is used for describing a ProcInstance to be created with its proc actors*/
            BpmEntitiesService.ProcInstanceDetails procInstanceDetails = new BpmEntitiesService.ProcInstanceDetails(PROCESS_CODE)
                    .addProcActor("initiator", getEditedEntity().getInitiator())
                    .addProcActor("executor", getEditedEntity().getExecutor())
                    .setEntity(getEditedEntity());

            /*The created ProcInstance will have two proc actors. None of the entities is persisted yet.*/
            ProcInstance procInstance = bpmEntitiesService.createProcInstance(procInstanceDetails);

            /*A map with process variables that must be passed to the Activiti process instance when it is started. This variable is used in the model to make a decision for one of gateways.*/
            HashMap<String, Object> processVariables = new HashMap<>();
            processVariables.put("acceptanceRequired", getEditedEntity().getAcceptanceRequired());

            /*Starts the process. The "startProcess" method automatically persists the passed procInstance with its actors*/
            processRuntimeService.startProcess(procInstance, "Process started programmatically", processVariables);
            notifications.create()
                    .withCaption(messageBundle.getMessage("processStarted"))
                    .withType(Notifications.NotificationType.HUMANIZED)
                    .show();

            /*refresh the procActionsFragment to display complete tasks buttons (if a process task appears for the current user after the process is started)*/
            initProcActionsFragment();
        });
    }

    private void initProcActionsFragment() {
        procActionsFragment.initializer()
                .standard()
                .setBeforeStartProcessPredicate(() -> {
                    /*the predicate creates process actors and sets them to the process instance created by the ProcActionsFragment*/
                    if (commitChanges().getStatus() == OperationResult.Status.SUCCESS) {
                        ProcInstance procInstance = procActionsFragment.getProcInstance();
                        ProcActor initiatorProcActor = createProcActor("initiator", procInstance, getEditedEntity().getInitiator());
                        ProcActor executorProcActor = createProcActor("executor", procInstance, getEditedEntity().getExecutor());
                        Set<ProcActor> procActors = new HashSet<>();
                        procActors.add(initiatorProcActor);
                        procActors.add(executorProcActor);
                        procInstance.setProcActors(procActors);
                        return true;
                    }
                    return false;
                })
                .setStartProcessActionProcessVariablesSupplier(() -> {
                    /*the supplier returns a map with process variables that will be used by the Activiti process*/
                    Map<String, Object> processVariables = new HashMap<>();
                    processVariables.put("acceptanceRequired", getEditedEntity().getAcceptanceRequired());
                    return processVariables;
                })
                .setAfterStartProcessListener(() -> {
                    /*custom listener in addition to the standard behavior refreshes the "taskDs", because the process automatically updates the "processState" field of the "Task" entity.*/
                    notifications.create()
                            .withCaption(messages.getMessage(ProcActionsFragment.class,"processStarted"))
                            .withType(Notifications.NotificationType.HUMANIZED)
                            .show();
                    initProcActionsFragment();
                    taskDl.setEntityId(getEditedEntity().getId());
                    taskDl.load();
                })
                .setAfterCompleteTaskListener(() -> {
                    notifications.create()
                            .withCaption(messages.getMessage(ProcActionsFragment.class,"taskCompleted"))
                            .withType(Notifications.NotificationType.HUMANIZED)
                            .show();
                    initProcActionsFragment();
                    taskDl.setEntityId(getEditedEntity().getId());
                    taskDl.load();
                })
                .init(PROCESS_CODE, getEditedEntity());
    }

    /** * Method demonstrates how to get and modify process actions automatically created by the ProcActionsFragment */
    private void changeStartProcessBtnCaption() {
        StartProcessAction startProcessAction = procActionsFragment.getStartProcessAction();
        if (startProcessAction != null) {
            startProcessAction.setCaption("Start process using ProcActionsFragment");
        }
    }
}

请参阅 TaskEdit.java 中的 setStartProcessActionProcessVariablesSupplier() 方法的用法,作为如何在流程启动时使用 ProcActionsFragment 传递流程变量的示例。其中一个流程网关中使用 acceptanceRequired 变量决定是否必须由发起者接受任务,或者流程必须完成。

changeStartProcessBtnCaption() 演示了如何获取和修改 ProcActionsFragment 生成的流程操作。在此方法中,标准按钮标题“启动流程”将由自定义标题替换。

onStartProcessProgrammaticallyBtnClick() 方法演示了如何在没有 ProcActionsFragment 的情况下启动新的流程实例。

UpdateProcessStateListener.javaorg.activiti.engine.delegate.event.ActivitiEventListener 的一个实现。此监听器作为流程级别监听器被注册。它执行以下操作:每次到达新的流程步骤时,相关的 com.company.bpmsamples.entity.Task 实体的 processState 字段将使用当前流程步骤名称进行更新。

UpdateProcessStateListener.java
/** * The listener updates the "processState" field of the {@link HasProcessState} with the name of current BPM process * node. This listener is used in the "taskExecution-1" BPM process */
public class UpdateProcessStateListener implements ActivitiEventListener {

    private static final Logger log = LoggerFactory.getLogger(UpdateProcessStateListener.class);

    private Metadata metadata;

    public UpdateProcessStateListener() {
        metadata = AppBeans.get(Metadata.class);
    }

    @Override
    public void onEvent(ActivitiEvent event) {
        RuntimeService runtimeService = event.getEngineServices().getRuntimeService();
        String executionId = event.getExecutionId();
        UUID entityId = (UUID) runtimeService.getVariable(executionId, "entityId");
        String entityName = (String) runtimeService.getVariable(executionId, "entityName");
        if (entityId == null) {
            log.error("Cannot update process state. entityId variable is null");
            return;
        }
        if (Strings.isNullOrEmpty(entityName)) {
            log.error("Cannot update process state. entityName variable is null");
            return;
        }
        MetaClass metaClass = metadata.getClass(entityName);
        if (metaClass == null) {
            log.error("Cannot update process state. MetaClass {} not found", entityName);
            return;
        }

        if (!HasProcessState.class.isAssignableFrom(metaClass.getJavaClass())) {
            log.error("{} doesn't implement the HasProcessState");
            return;
        }

        switch (event.getType()) {
            case ACTIVITY_STARTED:
                //activityName is the name of the current element taken from the process model
                String activityName = ((ActivitiActivityEvent) event).getActivityName();
                if (!Strings.isNullOrEmpty(activityName)) {
                    updateProcessState(metaClass, entityId, activityName);
                }
                break;
        }
    }

    /** * Method updates the process state of the entity linked with the process instance */
    private void updateProcessState(MetaClass metaClass, UUID entityId, String processState) {
        Persistence persistence = AppBeans.get(Persistence.class);
        try (Transaction tx = persistence.getTransaction()) {
            EntityManager em = persistence.getEntityManager();
            Entity entity = em.find(metaClass.getJavaClass(), entityId);
            if (entity != null) {
                ((HasProcessState) entity).setProcessState(processState);
            } else {
                log.error("Entity {} with id {} not found", metaClass.getName(), entityId);
            }
            tx.commit();
        }
    }

    @Override
    public boolean isFailOnException() {
        return false;
    }
}

这是流程模型中流程级事件监听器配置界面。

TaskExecution1UpdateProcessStateListener
Figure 30. 流程状态监听器

要打开此窗口,请单击建模器中的某个位置,单击 Show advanced properties 链接,然后配置 Event listeners 属性。

Appendix A: 应用程序属性

bpm.activiti.asyncExecutorEnabled

可选值:truefalse。定义是否启用定时器的 Job 执行器和异步任务。默认值为 false

. . .