工作流框架Flowable 簡介

琅琊甲乙木發表於2024-05-20

Flowable基本操作

1.建立ProcessEngine

建立一個基本的maven工程,可以是Eclipse也可以是其他IDEA。然後新增兩個依賴

  • Flowable流程引擎。使我們可以建立一個ProcessEngine流程引擎物件,並訪問Flowable API。
  • 一個是MySQL的資料庫驅動

pom.xml檔案中新增下列行:

<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-engine</artifactId>
    <version>6.3.0</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
</dependency>

然後建立一個普通的Java類,新增對應的main方法,首先要做的是初始化「ProcessEngine」流程引擎例項。這是一個執行緒安全的物件,因此通常只需要在一個應用中初始化一次。 ProcessEngine由「ProcessEngineConfiguration」例項建立。該例項可以配置與調整流程引擎的設定。 通常使用一個配置XML檔案建立ProcessEngineConfiguration,但是(像在這裡做的一樣)也可以程式設計方式建立它。 ProcessEngineConfiguration所需的最小配置,是資料庫JDBC連線:

public static void main(String[] args) {
    ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
        .setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn?serverTimezone=UTC")
        .setJdbcUsername("root")
        .setJdbcPassword("123456")
        .setJdbcDriver("com.mysql.cj.jdbc.Driver")
        .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
    ProcessEngine processEngine = cfg.buildProcessEngine();
}

注意在mysql8.0中執行可能出現如下的錯誤

工作流框架Flowable 簡介

出現這種情況只需要在mysql的連線字串中新增上nullCatalogMeansCurrent=true,設定為只查當前連線的schema庫即可。

    public static void main(String[] args) {
        ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
                .setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn1?serverTimezone=UTC&nullCatalogMeansCurrent=true")
                .setJdbcUsername("root")
                .setJdbcPassword("123456")
                .setJdbcDriver("com.mysql.cj.jdbc.Driver")
                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        ProcessEngine processEngine = cfg.buildProcessEngine();
    }

然後應用執行沒有問題,但也沒有在控制檯提供有用的資訊,只有一條訊息提示日誌沒有正確配置。Flowable使用SLF4J作為內部日誌框架。在這個例子中,我們使用log4j作為SLF4J的實現。因此在pom.xml檔案中新增下列依賴:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.21</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.21</version>
</dependency>

Log4j需要一個配置檔案。在src/main/resources資料夾下新增log4j.properties檔案,並寫入下列內容:

log4j.rootLogger=DEBUG, CA

log4j.appender.CA=org.apache.log4j.ConsoleAppender
log4j.appender.CA.layout=org.apache.log4j.PatternLayout
log4j.appender.CA.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n

重新執行應用。應該可以看到關於引擎啟動與建立資料庫表結構的提示日誌:

工作流框架Flowable 簡介

同時可以看到建立了相關的表結構在資料庫中

工作流框架Flowable 簡介

這樣就得到了一個啟動可用的流程引擎。接下來為它提供一個流程!

2.部署流程定義

接下來我們構建一個非常簡單的請假流程,Flowable引擎需要流程定義為BPMN 2.0格式,這是一個業界廣泛接受的XML標準。 在Flowable術語中,我們將其稱為一個**流程定義(process definition)「。一個流程定義可以啟動多個」流程例項(process instance)**。流程定義可以看做是重複執行流程的藍圖。 在這個例子中,流程定義定義了請假的各個步驟,而一個流程例項對應某個僱員提出的一個請假申請。

BPMN 2.0儲存為XML,幷包含視覺化的部分:使用標準方式定義了每個步驟型別(人工任務,自動服務呼叫,等等)如何呈現,以及如何互相連線。這樣BPMN 2.0標準使技術人員與業務人員能用雙方都能理解的方式交流業務流程。

我們要使用的流程定義為:

工作流框架Flowable 簡介

流程定義說明:

  • 我們假定啟動流程需要提供一些資訊,例如僱員名字、請假時長以及說明。當然,這些可以單獨建模為流程中的第一步。 但是如果將它們作為流程的“輸入資訊”,就能保證只有在實際請求時才會建立一個流程例項。否則(將提交作為流程的第一步),使用者可能在提交之前改變主意並取消,但流程例項已經建立了。 在某些場景中,就可能影響重要的指標(例如啟動了多少申請,但還未完成),取決於業務目標。
  • 左側的圓圈叫做**啟動事件(start event)**。這是一個流程例項的起點。
  • 第一個矩形是一個**使用者任務(user task)**。這是流程中使用者操作的步驟。在這個例子中,經理需要批准或駁回申請
  • 取決於經理的決定,「排他閘道器(exclusive gateway)」 (帶叉的菱形)會將流程例項路由至批准或駁回路徑
  • 如果批准,則需要將申請註冊至某個外部系統,並跟著另一個使用者任務,將經理的決定通知給申請人。當然也可以改為傳送郵件。
  • 如果駁回,則為僱員傳送一封郵件通知他。

一般來說,這樣的流程定義使用視覺化建模工具建立,如Flowable Designer(Eclipse)或Flowable Web Modeler(Web應用)。但在這裡我們直接撰寫XML,以熟悉BPMN 2.0及其概念。

與上面展示的流程圖對應的BPMN 2.0 XML在下面顯示。請注意這隻包含了“流程部分”。如果使用圖形化建模工具,實際的XML檔案還將包含“視覺化部分”,用於描述圖形資訊,如流程定義中各個元素的座標(所有的圖形化資訊包含在XML的BPMNDiagram標籤中,作為definitions標籤的子元素)。

將下面的XML儲存在src/main/resources資料夾下名為holiday-request.bpmn20.xml的檔案中。

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
             xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
             xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
             xmlns:flowable="http://flowable.org/bpmn"
             typeLanguage="http://www.w3.org/2001/XMLSchema"
             expressionLanguage="http://www.w3.org/1999/XPath"
             targetNamespace="http://www.flowable.org/processdef">

    <process id="holidayRequest" name="Holiday Request" isExecutable="true">

        <startEvent id="startEvent"/>
        <sequenceFlow sourceRef="startEvent" targetRef="approveTask"/>

        <userTask id="approveTask" name="Approve or reject request"/>
        <sequenceFlow sourceRef="approveTask" targetRef="decision"/>

        <exclusiveGateway id="decision"/>
        <sequenceFlow sourceRef="decision" targetRef="externalSystemCall">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[
          ${approved}
        ]]>
            </conditionExpression>
        </sequenceFlow>
        <sequenceFlow  sourceRef="decision" targetRef="sendRejectionMail">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[
          ${!approved}
        ]]>
            </conditionExpression>
        </sequenceFlow>

        <serviceTask id="externalSystemCall" name="Enter holidays in external system"
                     flowable:class="org.flowable.CallExternalSystemDelegate"/>
        <sequenceFlow sourceRef="externalSystemCall" targetRef="holidayApprovedTask"/>

        <userTask id="holidayApprovedTask" name="Holiday approved"/>
        <sequenceFlow sourceRef="holidayApprovedTask" targetRef="approveEnd"/>

        <serviceTask id="sendRejectionMail" name="Send out rejection email"
                     flowable:class="org.flowable.SendRejectionMail"/>
        <sequenceFlow sourceRef="sendRejectionMail" targetRef="rejectEnd"/>

        <endEvent id="approveEnd"/>

        <endEvent id="rejectEnd"/>
    </process>

</definitions>

現在我們已經有了流程BPMN 2.0 XML檔案,下來需要將它*部署(deploy)***到引擎中。部署一個流程定義意味著:

  • 流程引擎會將XML檔案儲存在資料庫中,這樣可以在需要的時候獲取它
  • 流程定義轉換為內部的、可執行的物件模型,這樣使用它就可以啟動流程例項

將流程定義部署至Flowable引擎,需要使用RepositoryService,其可以從ProcessEngine物件獲取。使用RepositoryService,可以透過XML檔案的路徑建立一個新的*部署(Deployment),並呼叫deploy()*方法實際執行:

    /**
     * 部署流程
     */
    @Test
    public void testDeploy(){
        // 配置資料庫相關資訊 獲取 ProcessEngineConfiguration
        ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
                .setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&nullCatalogMeansCurrent=true")
                .setJdbcUsername("root")
                .setJdbcPassword("123456")
                .setJdbcDriver("com.mysql.cj.jdbc.Driver")
                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        // 獲取流程引擎物件
        ProcessEngine processEngine = cfg.buildProcessEngine();
        // 部署流程 獲取RepositoryService物件
        RepositoryService repositoryService = processEngine.getRepositoryService();
        Deployment deployment = repositoryService.createDeployment()// 建立Deployment物件
                .addClasspathResource("holiday-request.bpmn20.xml") // 新增流程部署檔案
                .name("請求流程") // 設定部署流程的名稱
                .deploy(); // 執行部署操作
        System.out.println("deployment.getId() = " + deployment.getId());
        System.out.println("deployment.getName() = " + deployment.getName());

    }

然後執行該方法日誌操作成功:

工作流框架Flowable 簡介

在後臺表結構也可以看到相關的資訊

act_re_deployment: 流程定義部署表,每部署一次就增加一條記錄

工作流框架Flowable 簡介

act_re_procdef :流程定義表,部署每個新的流程定義都會在這張表中增加一條記錄

工作流框架Flowable 簡介

act_ge_bytearray :流程資源表,流程部署的 bpmn檔案和png圖片會儲存在該表中

工作流框架Flowable 簡介

我們現在可以透過API查詢驗證流程定義已經部署在引擎中(並學習一些API)。透過RepositoryService建立的ProcessDefinitionQuery物件實現。

/**
     * 檢視流程定義
     */
    @Test
    public void testDeployQuery(){
        // 配置資料庫相關資訊 獲取 ProcessEngineConfiguration
        ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
                .setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&nullCatalogMeansCurrent=true")
                .setJdbcUsername("root")
                .setJdbcPassword("123456")
                .setJdbcDriver("com.mysql.cj.jdbc.Driver")
                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        // 獲取流程引擎物件
        ProcessEngine processEngine = cfg.buildProcessEngine();
        // 部署流程 獲取RepositoryService物件
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 獲取流程定義物件
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId("2501")
                .singleResult();
        System.out.println("processDefinition.getId() = " + processDefinition.getId());
        System.out.println("processDefinition.getName() = " + processDefinition.getName());
        System.out.println("processDefinition.getDeploymentId() = " + processDefinition.getDeploymentId());
        System.out.println("processDefinition.getDescription() = " + processDefinition.getDescription());

    }

輸出結果為:

processDefinition.getId() = holidayRequest:2:2503
processDefinition.getName() = Holiday Request
processDefinition.getDeploymentId() = 2501
processDefinition.getDescription() = null

3.啟動流程例項

現在已經在流程引擎中部署了流程定義,因此可以使用這個流程定義作為“模板”啟動流程例項

工作流框架Flowable 簡介

要啟動流程例項,需要提供一些初始化流程變數。一般來說,可以透過呈現給使用者的表單,或者在流程由其他系統自動觸發時透過REST API,來獲取這些變數。在這個例子裡,我們簡化直接在程式碼中定義了,我們使用RuntimeService啟動一個流程例項

    /**
     * 啟動流程例項
     */
    @Test
    public void testRunProcess(){
        // 配置資料庫相關資訊 獲取 ProcessEngineConfiguration
        ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
                .setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&nullCatalogMeansCurrent=true")
                .setJdbcUsername("root")
                .setJdbcPassword("123456")
                .setJdbcDriver("com.mysql.cj.jdbc.Driver")
                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        // 獲取流程引擎物件
        ProcessEngine processEngine = cfg.buildProcessEngine();
        // 啟動流程例項透過 RuntimeService 物件
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 構建流程變數
        Map<String,Object> variables = new HashMap<>();
        variables.put("employee","張三") ;// 誰申請請假
        variables.put("nrOfHolidays",3); // 請幾天假
        variables.put("description","工作累了,想出去玩玩"); // 請假的原因
        // 啟動流程例項,第一個引數是流程定義的id
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey("holidayRequest", variables);// 啟動流程例項
        // 輸出相關的流程例項資訊
        System.out.println("流程定義的ID:" + processInstance.getProcessDefinitionId());
        System.out.println("流程例項的ID:" + processInstance.getId());
        System.out.println("當前活動的ID:" + processInstance.getActivityId());
    }

啟動成功,輸出結果如下:

流程定義的ID:holidayRequest:2:2503
流程例項的ID:5001
當前活動的ID:null

對應的流程例項ID為:5001

啟動流程例項涉及到的表結構:

  • act_hi_actinst 流程例項執行歷史
  • act_hi_identitylink 流程的參與使用者的歷史資訊
  • act_hi_procinst 流程例項歷史資訊
  • act_hi_taskinst 流程任務歷史資訊
  • act_ru_execution 流程執行資訊
  • act_ru_identitylink 流程的參與使用者資訊
  • act_ru_task 任務資訊

4.檢視任務

上面員工發起了一個請假流程,接下來就會流轉到總經理這兒來處理,之前我們沒有指定經理這的處理人,我們可以加一個

工作流框架Flowable 簡介

然後我們來檢視下lisi的任務

    /**
     * 檢視任務
     */
    @Test
    public void testQueryTask(){
        // 配置資料庫相關資訊 獲取 ProcessEngineConfiguration
        ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
                .setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&nullCatalogMeansCurrent=true")
                .setJdbcUsername("root")
                .setJdbcPassword("123456")
                .setJdbcDriver("com.mysql.cj.jdbc.Driver")
                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        // 獲取流程引擎物件
        ProcessEngine processEngine = cfg.buildProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("holidayRequestNew")
                .taskAssignee("lisi")
                .list();
        for (Task task : list) {
            System.out.println("task.getProcessDefinitionId() = " + task.getProcessDefinitionId());
            System.out.println("task.getId() = " + task.getId());
            System.out.println("task.getAssignee() = " + task.getAssignee());
            System.out.println("task.getName() = " + task.getName());
        }
    }

輸出結果為:

task.getProcessDefinitionId() = holidayRequestNew:1:10003
task.getId() = 12508
task.getAssignee() = lisi
task.getName() = Approve or reject request

5.完成任務

現在李四這個角色可以來完成當前的任務了

工作流框架Flowable 簡介

在此處我們直接解決掉這個請假,然後會走傳送拒絕郵件的流程,這塊我們需要用到JavaDelegate來觸發。

工作流框架Flowable 簡介

我們定義這樣一個Java類

public class SendRejectionMail implements JavaDelegate {
    /**
     * 觸發傳送郵件的操作
     * @param delegateExecution
     */
    @Override
    public void execute(DelegateExecution delegateExecution) {
        System.out.println("請假被拒絕,,,安心工作吧");
    }
}

然後來完成任務

    /**
     * 完成任務
     */
    @Test
    public void testCompleteTask(){
        // 配置資料庫相關資訊 獲取 ProcessEngineConfiguration
        ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
                .setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&nullCatalogMeansCurrent=true")
                .setJdbcUsername("root")
                .setJdbcPassword("123456")
                .setJdbcDriver("com.mysql.cj.jdbc.Driver")
                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        // 獲取流程引擎物件
        ProcessEngine processEngine = cfg.buildProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("holidayRequestNew")
                .taskAssignee("lisi")
                .singleResult();
        // 新增流程變數
        Map<String,Object> variables = new HashMap<>();
        variables.put("approved",false); // 拒絕請假
        // 完成任務
        taskService.complete(task.getId(),variables);
    }

然後可以看到JavaDelegate觸發了

工作流框架Flowable 簡介

6.流程的刪除

有些流程已經沒有用了,我們需要刪除掉,其實也非常簡單

    /**
     * 刪除流程
     */
    @Test
    public void testDeleteProcess(){
        // 配置資料庫相關資訊 獲取 ProcessEngineConfiguration
        ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
                .setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&nullCatalogMeansCurrent=true")
                .setJdbcUsername("root")
                .setJdbcPassword("123456")
                .setJdbcDriver("com.mysql.cj.jdbc.Driver")
                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        // 獲取流程引擎物件
        ProcessEngine processEngine = cfg.buildProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 刪除流程定義,如果該流程定義已經有了流程例項啟動則刪除時報錯
        // repositoryService.deleteDeployment("1");
        // 設定為TRUE 級聯刪除流程定義,及時流程有例項啟動,也可以刪除,設定為false 非級聯刪除操作。
        repositoryService.deleteDeployment("2501",true);

    }

7.檢視歷史資訊

選擇使用Flowable這樣的流程引擎的原因之一,是它可以自動儲存所有流程例項的「審計資料」或「歷史資料」。這些資料可以用於建立報告,深入展現組織執行的情況,瓶頸在哪裡,等等。

例如,如果希望顯示流程例項已經執行的時間,就可以從ProcessEngine獲取HistoryService,並建立*歷史活動(historical activities)*的查詢。在下面的程式碼片段中,可以看到我們新增了一些額外的過濾條件:

  • 只選擇一個特定流程例項的活動
  • 只選擇已完成的活動

結果按照結束時間排序,代表其執行順序。

/**
     * 檢視歷史
     */
    @Test
    public void testQueryHistory(){
        // 配置資料庫相關資訊 獲取 ProcessEngineConfiguration
        ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
                .setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&nullCatalogMeansCurrent=true")
                .setJdbcUsername("root")
                .setJdbcPassword("123456")
                .setJdbcDriver("com.mysql.cj.jdbc.Driver")
                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        // 獲取流程引擎物件
        ProcessEngine processEngine = cfg.buildProcessEngine();
        HistoryService historyService = processEngine.getHistoryService();
        List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
                .processDefinitionId("holidayRequestNew:1:10003")
                .finished()
                .orderByHistoricActivityInstanceEndTime().asc()
                .list();
        for (HistoricActivityInstance historicActivityInstance : list) {
            System.out.println(historicActivityInstance.getActivityId() + " took "
                    + historicActivityInstance.getDurationInMillis() + " milliseconds");
        }

    }

輸出結果

startEvent took 1 milliseconds
approveTask took 837735 milliseconds
decision took 13 milliseconds
sendRejectionMail took 2 milliseconds
rejectEnd took 1 milliseconds

好了~flowable的基本應用我們就先介紹到這裡了。

zz:https://zhuanlan.zhihu.com/p/481828559?utm_id=0

相關文章