背景

近幾年,網際網路企業從消費網際網路向產業網際網路轉型。在消費網際網路時期,企業面對的時C端消費者,而產業網際網路面對的是B端使用者。

產業網際網路涉及方方面面,企業資訊化的建設就是B端使用者的業務之一,在企業就存在上下級關係,存在審批業務,需要流程管理。

在企業資訊化建設中流程管理也是重要的一部分,如下基於flowable簡單的分析流程定義。

流程的一點基本概念

開始節點,結束節點和人工任務節點

閘道器

自動服務任務

 順序流

閘道器分支

  • 並行分叉 AND-split(Parallel Split)

    • 兩個分支都需要執行
    • 如 下單後 發快遞和開發票,兩個動作都會做。
  • 排他分叉或叫異或 XOR-split(Exclusive Choice)

    • 後續分支二選一,只能走一條
    • 如 下單後的支付,選擇銀聯支付,微信支付還是支付寶支付,只能選擇一種支付方式。

網關合並

  • 同步AND-join(Synchronization)

    • 前面的所有的分支等待都需要完成
    • 如上面的下單,收快遞和收發票,都收到了,流程才完成。
  • 簡單合併XOR-join(Simple Merge)
    • 前面的分支只要有一個完成,流程就完成
    • 如上面的下單後支付,不管是銀聯,微信支付還是支付寶支付,只要有一個渠道支付成功,支付流程就完成。

Flowable相關說明

官網地址:https://flowable.com/

幫助文件:https://flowable.com/open-source/docs/bpmn/ch02-GettingStarted/

中文幫助文件:https://tkjohn.github.io/flowable-userguide/#_introduction

github程式碼:https://github.com/flowable/flowable-engine

flowable的發展

  • Flowable是來自從Activiti5.22
  • Activiti6.0的bug太多
  • Activiti7.0主要擁抱雲
  • Flowable6版本開始按照模組進行拆分,社群比較活躍

如下是JBoss,Alfresco和Flowable三個團隊的流程軟體關鍵時間點描述:

Flowable安裝和配置

Flow安裝

第一步、從flowable的github地址上下載如下幾個war,部署到apache的webapps下

flowable-admin.war
flowable-idm.war
flowable-modeler.war
flowable-rest.war
flowable-task.war

第二步、修改如上幾個包的配置檔案,配置mysql資料地址或者使用自身的h2資料庫進行實驗。

修改admin idm modeler rest和task這5個包的配置檔案WEB-INF/classes/flowable-default.properties,本實驗使用的是mysql資料庫。

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/flowable?characterEncoding=UTF-8
spring.datasource.username=flowable
spring.datasource.password=flowable

配置後,啟動tomcat伺服器並登入。

Flowable modeler登入

訪問flowable的流程設計器

地址:http://localhost:8080/flowable-modeler

賬號密碼:admin/test

登入後,可以訪問flowable的管理頁面

地址:http://localhost:8080/flowable-admin/#/engine

可以看到flowable流程由如下幾個引擎組成

可以通過編輯REST端點檢查對應的服務執行是否正常。

本例中以流程引擎為例簡單的說明。

流程定義

定義一個簡單的請假流程,包括一個開始節點,一個審批節點,經管排他閘道器後,審批通過後傳送一個http通知,審批拒絕後也傳送一個http通知。

如上流程定義使用bpmn2.0的xml表示大致如下所示

<?xml version="1.0" encoding="UTF-8"?>
<definitions>
<process id="flow_h1" name="holiday-new" isExecutable="true">
<documentation>請求流程描述</documentation>
<startEvent id="startEvent1" name="請假開始" flowable:formKey="formKey1" flowable:formFieldValidation="true">
<extensionElements>
<flowable:formProperty id="employee" name="請假人" type="string" required="true"></flowable:formProperty>
<flowable:formProperty id="noOfHolidays" name="天數" type="long" required="true"></flowable:formProperty>
</extensionElements>
</startEvent>
<userTask id="approveKey1" name="審批節點1" flowable:formFieldValidation="true"></userTask>
<exclusiveGateway id="sid-D7F080C0-BC9D-4C03-B9EB-2D81E4010CA6" name="審批是否通過"></exclusiveGateway>
<serviceTask id="AcceptKey1" name="通過" flowable:class="org.example.tut.flowable.handler.HolidayRejectionHandler"></serviceTask>
<serviceTask id="RejectKey1" name="拒絕" flowable:class="org.example.tut.flowable.handler.HolidayApprovalHandler"></serviceTask>
<endEvent id="sid-E86D23D8-DC24-44D0-B96D-BA3E8CF801C0"></endEvent>
<serviceTask id="RejectHttpKey1" name="HTTP測試節點" flowable:type="http">
<extensionElements>
<flowable:field name="requestMethod">
<flowable:string><![CDATA[POST]]></flowable:string>
</flowable:field>
<flowable:field name="requestUrl">
<flowable:string><![CDATA[http://127.0.0.1:9901/test]]></flowable:string>
</flowable:field>
<flowable:field name="requestBody">
<flowable:expression><![CDATA[Reject action, employee ${employee} apply ${noOfHolidays} days with reason ${description}]]></flowable:expression>
</flowable:field>
</extensionElements>
</serviceTask>
<sequenceFlow id="sid-84648711-833A-4ABE-AD08-3E32B026F247" sourceRef="RejectKey1" targetRef="RejectHttpKey1"></sequenceFlow>
<sequenceFlow id="sid-448B4748-A744-43D9-B3EB-FC0846035F20" sourceRef="RejectHttpKey1" targetRef="sid-E86D23D8-DC24-44D0-B96D-BA3E8CF801C0"></sequenceFlow>
<endEvent id="sid-683697FC-2E68-45C9-BE63-0F526FC2F3AB"></endEvent>
<serviceTask id="AcceptHttpKey1" name="審批通過的HTTP訊息" flowable:type="http">
<extensionElements>
<flowable:field name="requestMethod">
<flowable:string><![CDATA[POST]]></flowable:string>
</flowable:field>
<flowable:field name="requestUrl">
<flowable:string><![CDATA[http://127.0.0.1:9901/test]]></flowable:string>
</flowable:field>
<flowable:field name="requestBody">
<flowable:expression><![CDATA[Accept Holiday employee ${employee} apply ${noOfHolidays} days with reason ${description}]]></flowable:expression>
</flowable:field>
</extensionElements>
</serviceTask>
<sequenceFlow id="sid-9788654A-171C-45B9-9A41-94A35C531AA8" sourceRef="AcceptHttpKey1" targetRef="sid-683697FC-2E68-45C9-BE63-0F526FC2F3AB"></sequenceFlow>
<sequenceFlow id="sid-C4456145-0531-492F-AA80-E733CD0C35C9" sourceRef="AcceptKey1" targetRef="AcceptHttpKey1"></sequenceFlow>
<sequenceFlow id="SeqFromStartToApprove" sourceRef="startEvent1" targetRef="approveKey1"></sequenceFlow>
<sequenceFlow id="SeqFromApproveToGW" sourceRef="approveKey1" targetRef="sid-D7F080C0-BC9D-4C03-B9EB-2D81E4010CA6"></sequenceFlow>
<sequenceFlow id="SeqRejectKey1" sourceRef="sid-D7F080C0-BC9D-4C03-B9EB-2D81E4010CA6" targetRef="RejectKey1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${!approved}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="ReqAcceptKey1" sourceRef="sid-D7F080C0-BC9D-4C03-B9EB-2D81E4010CA6" targetRef="AcceptKey1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${approved}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_flow_h1">
... 省
</bpmndi:BPMNDiagram>
</definitions>

節點說明

這個簡單的流程中使用了flowable的幾個節點

流程定義的組成

process

  • startEvent 表示開始節點

    • 開始節點可以是空節點
    • 開始節點也可以是定時節點
  • userTask 表示人工審批節點
    • 可以通過 flowable:assignee="userId" 表示這個節點的審批人是誰
    • 可以通過 flowable:candidateGroups="managerGroup" 表示候選的審批組是什麼,在這個組中的人都可以做為審批人。
    • 人工審批節點可以增加extensionElements增加自定義擴充套件屬性,將流程定義的擴充套件資訊儲存到CDATA中
  • exclusiveGateway 表示排他閘道器
    • 人工審批後的審批結果做為排他閘道器的判斷依據
  • serviceTask 表示自動節點
    • 有自定義的節點需要自定義一個Handler做為serviceTask節點的處理。
    • serviceTask也有一些系統內建實現(如HTTP服務 MAIL服務 MULE服務等)
    • 自動服務節點可以增加extensionElements增加自定義擴充套件屬性,將流程定義的擴充套件資訊儲存到CDATA中
  • sequenceFlow 表示分支流
    • 在分支流上會存在一個條件,排他閘道器後會跟多個sequenceFlow,排他閘道器會根據各自sequenceFlow上的條件決定流程的流向。
    • 條件會使用conditionExpression來表示,這個表示式一般使用JUEL表示式實現。
  • endEvent 表示結束節點

bpmndi:BPMNDiagram

  • bpmndi:BPMNShape

    • omgdc:Bounds 表示影象的輪廓包含寬高

      • width 寬
      • height 高
  • bpmndi:BPMNEdge
    • pmgdi:waypoint

      • x 橫軸座標
      • y 縱軸座標

startEvent的定時節點如何定義?

<startEvent id="timer_start1" name="定時器節點" isInterrupting="false">
<timerEventDefinition>
<timeDate>2021-06-30T15:01:02</timeDate>
<timeCycle>R/P1DT0S</timeCycle>
<timeDuration>P10DT0S</timeDuration>
</timerEventDefinition>
</startEvent>

時間定義參考ISO8601標準,可以參考@https://www.cnblogs.com/xdao/p/iso8601.html

格式解析
R2/2015-06-04T19:25:16.828696-07:00/P1DT10S 上面的字串通過"/"分為了三部分即: 重複次數/開始時間/執行間隔 重複次數
R - 將永遠重複
R1 - 將重複一次
R231 - 將重複231次。
開始時間
任務第一次執行的時間。如果開始日期時間已經過去,Kala將返回一個錯誤。 其中"T"用來分割日期和時間,時間後面跟著的"-07:00"表示西七區,注意"-"是連字元,不是減號。 時區預設是0時區,可以用"Z"表示,也可以不寫。 對於我國,要使用"+08:00",表示東八區。
上面的字串表示 2015年6月4日,19點25分16秒828696納秒,西七區。 執行間隔
執行間隔以"P"開始,和上面一樣也是用"T"分割日期和時間,如P1Y2M10DT2H30M15S P 開始標記
1Y - 一年
2M - 兩個月
10D - 十天
T - 時間和日期分的割標記
2H - 兩個小時
30M - 三十分鐘
15S 十五秒鐘
例子,注意如果沒有年月日,"T"也不能省略 P1DT1M - 一天一分鐘執行一次
P1W - 一週執行一次
PT1H - 一小時執行一次
PT10S - 十秒執行一次

啟動流程

啟動流程的程式碼:

  public ProcessInstanceResponse startProcessInstance(HolidayRequest holidayRequest) {

        Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employee", holidayRequest.getEmpName());
variables.put("noOfHolidays", holidayRequest.getNoOfHolidays());
variables.put("description", holidayRequest.getRequestDescription());
variables.put("initiator", "admin"); ProcessInstance processInstance =
runtimeService.startProcessInstanceByKey(holidayRequest.getProcessKey(), variables); return new ProcessInstanceResponse(processInstance.getId(), processInstance.isEnded());
}

啟動流程後,會在資料庫中增加記錄

ACT_RU_ACTINST 增加流程例項資訊。

ACT_RU_VARIABLE 增加variables變數列表的變數,這個變數會貫穿流程例項的全程。

分配任務

分配任務的程式碼:

TaskService taskService;

// taskId是任務Id,userId是使用者id,表示將任務分配給這個使用者
taskService.setAssignee(taskId, userId);

分配任務後,會在資料庫中設定審批人資訊

ACT_RU_TASK 表的欄位 ASSIGEE_表示審批人是誰

獲取待辦任務

獲取待辦任務的程式碼

// 跟進userId使用者id查詢分配給這個使用者或將這個使用者設為候選的任務列表
List<Task> taskList = taskService.createTaskQuery().taskCandidateOrAssigned(userId).list();

會查詢ACT_RU_TASK表,獲取審批人的任務。

完成任務

完成任務也叫做審批任務

taskService.complete(taskId, variables);

流程執行

流程執行時,例項和變數說明什麼

可以在管理頁面看到啟動流程例項是填寫的變數列表

啟動流程增加的變數

variables.put("employee", holidayRequest.getEmpName());
variables.put("noOfHolidays", holidayRequest.getNoOfHolidays());
variables.put("description", holidayRequest.getRequestDescription());
variables.put("initiator", "admin");

人工節點審批時增加的變數是什麼

variables.put("approved", approved);
taskService.complete(taskId, variables);

即:變數時貫穿流程例項的全宣告週期,流程的很多自定義功能可以通過變數和擴充套件屬性進行實現。

展示流程例項的執行圖

 

done.

祝玩得開心~