網站首頁 編程語言 正文
為啥想寫 flowable 呢?原因很簡單,因為最近在錄的 tienchin 項目視頻會用到,先寫一篇文章和大家打打預防針,后面視頻再細講。
流程引擎,也算是一個比較常見的工具了,我們在日常的很多開發中都會用到,當然用的最多的就是 OA 系統了,但是在一些非 OA 系統中,我們也會涉及到,比如一個 CRM 中,可能會有合同管理的需求,合同的審批,也是需要流程引擎的。
所以今天我們來簡單聊聊流程引擎,順便寫一個簡單的例子,小伙伴們一起來感受下流程引擎到底是個啥。
1. 流程引擎介紹
Flowable 是一個使用 Java 編寫的輕量級業務流程引擎。Flowable 流程引擎可用于部署 BPMN2.0 流程定義(用于定義流程的行業 XML 標準),創建這些流程定義的流程實例,進行查詢,訪問運行中或歷史的流程實例與相關數據,等等。
Java 領域另一個流程引擎是 Activiti,不過我覺得這兩個東西,只要你會使用其中一個,另一個就不在話下。
咱就不廢話了,上代碼吧。
2. 創建項目
首先我們創建一個 Spring Boot 項目,引入 Web、和 MySQL 驅動兩個依賴,如下圖:
項目創建成功之后,我們引入 flowable 依賴,如下:
<dependency>
<groupId>org.flowablegroupId>
<artifactId>flowable-spring-boot-starterartifactId>
<version>6.7.2version>
dependency>
這個會幫我們做一些自動化配置,默認情況下,所以位于 resources/processes 的流程都會被自動部署。
接下來我們在 application.yaml 中配置一下數據庫連接信息,當項目啟動的時候會自動初始化數據庫,將來流程引擎運行時候的數據會被自動持久化到數據庫中。
spring:
datasource:
username: root
password: 123
url: jdbc:mysql:///flowable?serverTimezone=Asia/Shanghai&useSSL=false
好啦,配置完成后,我們就可以啟動項目了。項目啟動成功之后,flowable 數據庫中就會自動創建如下這些表,將來流程引擎相關的數據都會自動保存到這些表中。
默認的表比較多,截圖只是其中一部分。
3. 畫流程圖
畫流程圖算是比較有挑戰的一個步驟了,也是流程引擎使用的關鍵。官方提供了一些流程引擎繪制工具,這個我就不說了,感興趣的小伙伴可以自行去體驗;IDEA 也自帶了一個流程可視化的工具,但是特別難用,我這里也就 不說了。
這里說一下我常用的 IDEA 插件 Flowable BPMN visualizer,如下圖:
插件怎么安裝就不用我教了吧,小伙伴們自行安裝即可。
裝好插件之后,我們在 resources 目錄下新建 processes 目錄,這個目錄下的流程文件將來會被自動部署。
接下來我們在 processes 目錄下,新建一個 BPMN 文件(插件裝好了就有這個選項了),如下:
我們來畫個請假的流程,就叫做 ask_for_leave.bpmn20.xml,注意最后面的 .bpmn20.xml
是固定后綴。
文件創建出來之后,右鍵單擊,選擇 View BPMN(Flowable) Diagram,就打開了可視化頁面了,我們就可以來繪制自己的流程圖了。
我的請假流程畫出來是這樣:
員工發起一個請假流程,首先是組長審核,組長審核通過了,就進入到經理審核,經理審核通過了,這個流程就結束了,如果組長審核未通過或者經理審核未通過,則流程給員工發送一個請假失敗的通知,流程結束。
我們來看下這個流程對應的 XML 文件,一些流程細節會在 XML 文件中體現出來,如下:
<process id="ask_for_leave" name="ask_for_leave" isExecutable="true">
<userTask id="leaveTask" name="請假" flowable:assignee="#{leaveTask}"/>
<userTask id="zuzhangTask" name="組長審核" flowable:assignee="#{zuzhangTask}"/>
<userTask id="managerTask" name="經理審核" flowable:assignee="#{managerTask}"/>
<exclusiveGateway id="managerJudgeTask"/>
<exclusiveGateway id="zuzhangJudeTask"/>
<endEvent id="endLeave" name="結束"/>
<startEvent id="startLeave" name="開始"/>
<sequenceFlow id="flowStart" sourceRef="startLeave" targetRef="leaveTask"/>
<sequenceFlow id="modeFlow" sourceRef="leaveTask" targetRef="zuzhangTask"/>
<sequenceFlow id="zuzhang_go" sourceRef="zuzhangJudeTask" targetRef="managerTask" name="通過">
<conditionExpression xsi:type="tFormalExpression">conditionExpression>
sequenceFlow>
<sequenceFlow id="zuzhang_reject" sourceRef="zuzhangJudeTask" targetRef="sendMail" name="拒絕">
<conditionExpression xsi:type="tFormalExpression">conditionExpression>
sequenceFlow>
<sequenceFlow id="jugdeFlow" sourceRef="managerTask" targetRef="managerJudgeTask"/>
<sequenceFlow id="flowEnd" name="通過" sourceRef="managerJudgeTask" targetRef="endLeave">
<conditionExpression xsi:type="tFormalExpression">conditionExpression>
sequenceFlow>
<sequenceFlow id="rejectFlow" name="拒絕" sourceRef="managerJudgeTask" targetRef="sendMail">
<conditionExpression xsi:type="tFormalExpression">conditionExpression>
sequenceFlow>
<serviceTask id="sendMail" flowable:exclusive="true" name="發送失敗提示" isForCompensation="true" flowable:class="org.javaboy.flowable.AskForLeaveFail"/>
<sequenceFlow id="endFlow" sourceRef="sendMail" targetRef="askForLeaveFail"/>
<endEvent id="askForLeaveFail" name="請假失敗"/>
<sequenceFlow id="zuzhangTask_zuzhangJudeTask" sourceRef="zuzhangTask" targetRef="zuzhangJudeTask"/>
process>
結合 XML 文件我來和大家解釋一下這里涉及到的 Flowable 中的組件,我們來看下:
-
: 表示一個完整的工作流程。 -
: 工作流中起點位置,也就是圖中的綠色按鈕。 -
: 工作流中結束位置,也就是圖中的紅色按鈕。 -
: 代表一個任務審核節點(組長、經理等角色),這個節點上有一個flowable:assignee
屬性,這表示這個節點該由誰來處理,將來在 Java 代碼中調用的時候,我們需要指定對應的處理人的 ID 或者其他唯一標記。 -
:這是服務任務,在具體的實現中,這個任務可以做任何事情。 -
: 邏輯判斷節點,相當于流程圖中的菱形框。 -
:鏈接各個節點的線條,sourceRef 屬性表示線的起始節點,targetRef 屬性表示線指向的節點,我們圖中的線條都屬于這種。
流程圖這塊松哥和大家稍微說一下,咋一看這個圖挺復雜很難畫,但是實際上只要你認認真真去捋一捋這里邊的各個屬性,基本上很快就明白到底是怎么一回事,我也相信各位小伙伴都有這樣的悟性。
4. 開發接口
接下來我們寫幾個接口,來體驗一把流程引擎。
在正式體驗之前,我們先來熟悉幾個類,這幾個類我們一會寫代碼會用到。
4.1 Java 類梳理
- ProcessDefinition
這個最好理解,就是流程的定義,也就相當于規范,每個 ProcessDefinition 都會有一個 id。
- ProcessInstance
這個就是流程的一個實例。簡單來說,ProcessDefinition 相當于是類,而 ProcessInstance 則相當于是根據類 new 出來的對象。
- Activity
Activity 是流程標準規范 BPMN2.0 里面的規范,流程中的每一個步驟都是一個 Activity。
- Execution
Execution 的含義是流程的執行線路,通過 Execution 可以獲得當前 ProcessInstance 當前執行到哪個 Activity了。
- Task
Task 就是當前要做的工作。
實際上這里涉及到的東西比較多,不過我們今天先整一個簡單的例子,所以上面這些知識點暫時夠用了。
4.2 查看流程圖
在正式開始之前,我們先準備一個接口,用來查看流程圖的實時執行情況,這樣方便我們查看流程到底執行到哪一步了。
具體的代碼如下:
@RestController
public class HelloController {
@Autowired
RuntimeService runtimeService;
@Autowired
TaskService taskService;
@Autowired
RepositoryService repositoryService;
@Autowired
ProcessEngine processEngine;
@GetMapping("/pic")
public void showPic(HttpServletResponse resp, String processId) throws Exception {
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
if (pi == null) {
return;
}
List<Execution> executions = runtimeService
.createExecutionQuery()
.processInstanceId(processId)
.list();
List<String> activityIds = new ArrayList<>();
List<String> flows = new ArrayList<>();
for (Execution exe : executions) {
List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
activityIds.addAll(ids);
}
/**
* 生成流程圖
*/
BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0, false);
OutputStream out = null;
byte[] buf = new byte[1024];
int legth = 0;
try {
out = resp.getOutputStream();
while ((legth = in.read(buf)) != -1) {
out.write(buf, 0, legth);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
這就一個工具,沒啥好說的,一會大家看完后面的代碼,再回過頭來看這個接口,很多地方就都懂了。
4.3 開啟一個流程
為了方便,接下來的代碼我們都在單元測試中完成。
首先我們來開啟一個流程,代碼如下:
String staffId = "1000";
/**
* 開啟一個流程
*/
@Test
void askForLeave() {
HashMap<String, Object> map = new HashMap<>();
map.put("leaveTask", staffId);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("ask_for_leave", map);
runtimeService.setVariable(processInstance.getId(), "name", "javaboy");
runtimeService.setVariable(processInstance.getId(), "reason", "休息一下");
runtimeService.setVariable(processInstance.getId(), "days", 10);
logger.info("創建請假流程 processId:{}", processInstance.getId());
}
首先由員工發起一個請假流程,map 中存放的 leaveTask 是我們在 XML 流程文件中提前定義好的,提前定義好當前這個任務創建之后,該由誰來處理,這里我們是假設由工號為 1000 的員工來發起這樣一個請假流程。同時,我們還設置了一些額外信息。ask_for_leave 是我們在 XML 文件中定義的一個 process 的名稱。
好啦,現在我們執行這個單元測試方法,執行完成后,控制臺會打印出當前這個流程的 id,我們拿著這個 id 去訪問 4.2 小節的接口,結果如下:
可以看到,請假用紅色的框框起來了,說明當前流程走到了這一步。
4.4 將請求提交給組長
接下來,我們就需要將這個請假流程向后推進一步,將請假事務提交給組長,代碼如下:
String zuzhangId = "90";
/**
* 提交給組長審批
*/
@Test
void submitToZuzhang() {
//員工查找到自己的任務,然后提交給組長審批
List<Task> list = taskService.createTaskQuery().taskAssignee(staffId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("任務 ID:{};任務處理人:{};任務是否掛起:{}", task.getId(), task.getAssignee(), task.isSuspended());
Map<String, Object> map = new HashMap<>();
//提交給組長的時候,需要指定組長的 id
map.put("zuzhangTask", zuzhangId);
taskService.complete(task.getId(), map);
}
}
首先我們利用 staffId 查找到當前員工的 id,進而找到當前員工需要執行的任務,遍歷這個任務,調用 taskService.complete 方法將任務提交給組長,注意在 map 中指定組長的 id。
提交完成后,我們再去看流程圖片,如下:
可以看到,流程圖走到組長審批了。
4.5 組長審批
組長現在有兩種選擇,同意或者拒絕,同意的代碼如下:
/**
* 組長審批-批準
*/
@Test
void zuZhangApprove() {
List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("組長 {} 在審批 {} 任務", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
//組長審批的時候,如果是同意,需要指定經理的 id
map.put("managerTask", managerId);
map.put("checkResult", "通過");
taskService.complete(task.getId(), map);
}
}
通過組長的 id 查詢組長的任務,同意的話,需要指定經理,也就是這個流程下一步該由誰來處理。
拒絕的代碼如下:
/**
* 組長審批-拒絕
*/
@Test
void zuZhangReject() {
List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("組長 {} 在審批 {} 任務", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
//組長審批的時候,如果是拒絕,就不需要指定經理的 id
map.put("checkResult", "拒絕");
taskService.complete(task.getId(), map);
}
}
拒絕的話,就沒那么多事了,直接設置 checkResult 為拒絕即可。
假設這里執行了同意,那么流程圖如下:
4.6 經理審批
經理審批和組長審批差不多,只不過經理這里是最后一步了,不需要再指定下一位處理人了,同意的代碼如下:
/**
* 經理審批自己的任務-批準
*/
@Test
void managerApprove() {
List<Task> list = taskService.createTaskQuery().taskAssignee(managerId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("經理 {} 在審批 {} 任務", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
map.put("checkResult", "通過");
taskService.complete(task.getId(), map);
}
}
拒絕代碼如下:
/**
* 經理審批自己的任務-拒絕
*/
@Test
void managerReject() {
List<Task> list = taskService.createTaskQuery().taskAssignee(managerId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("經理 {} 在審批 {} 任務", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
map.put("checkResult", "拒絕");
taskService.complete(task.getId(), map);
}
}
4.7 拒絕流程
如果組長拒絕了或者經理拒絕了,我們也有相應的處理方案,首先在 XML 流程文件定義時,如下:
<serviceTask id="sendMail" flowable:exclusive="true" name="發送失敗提示" isForCompensation="true" flowable:class="org.javaboy.flowable.AskForLeaveFail"/>
如果請假被拒絕,會進入到這個 serviceTask,serviceTask 對應的處理類是 org.javaboy.flowable.AskForLeaveFail,該類的代碼如下:
public class AskForLeaveFail implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("請假失敗。。。");
}
}
也就是請假失敗會進入到這個方法中,現在我們就可以在這個方法中該干嘛干嘛了。
5. 小結
好啦,一個簡單的請假流程,希望能帶小伙伴們入門 flowable,公眾后臺回復 flowable,獲取本文案例。
好啦,后面 tienchin 項目視頻中我們再看看這個 flowable 在項目中如何使用:戳戳戳這里–>TienChin 項目配套視頻來啦。
原文鏈接:https://blog.csdn.net/u012702547/article/details/124834662
- 上一篇:死鎖的產生和避免
- 下一篇:Redis 做接口限流,一個注解的事
相關推薦
- 2023-07-24 前端實現電子簽名(web、移動端)通用
- 2022-12-27 查看Python安裝路徑幾種方法小結_python
- 2022-11-17 VMware?vSphere?ESXi系統設置靜態IP的方法_VMware
- 2022-10-19 Android?webview加載H5方法詳細介紹_Android
- 2022-01-05 出現escript: exception error: undefined function rab
- 2022-10-17 使用docker部署django的詳細步驟_docker
- 2023-02-12 python中使用docx模塊處理word文檔_python
- 2022-11-27 Python?Django教程之實現待辦事項應用程序_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支