網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
為啥想寫 flowable 呢?原因很簡(jiǎn)單,因?yàn)樽罱阡浀?tienchin 項(xiàng)目視頻會(huì)用到,先寫一篇文章和大家打打預(yù)防針,后面視頻再細(xì)講。
流程引擎,也算是一個(gè)比較常見(jiàn)的工具了,我們?cè)谌粘5暮芏嚅_(kāi)發(fā)中都會(huì)用到,當(dāng)然用的最多的就是 OA 系統(tǒng)了,但是在一些非 OA 系統(tǒng)中,我們也會(huì)涉及到,比如一個(gè) CRM 中,可能會(huì)有合同管理的需求,合同的審批,也是需要流程引擎的。
所以今天我們來(lái)簡(jiǎn)單聊聊流程引擎,順便寫一個(gè)簡(jiǎn)單的例子,小伙伴們一起來(lái)感受下流程引擎到底是個(gè)啥。
1. 流程引擎介紹
Flowable 是一個(gè)使用 Java 編寫的輕量級(jí)業(yè)務(wù)流程引擎。Flowable 流程引擎可用于部署 BPMN2.0 流程定義(用于定義流程的行業(yè) XML 標(biāo)準(zhǔn)),創(chuàng)建這些流程定義的流程實(shí)例,進(jìn)行查詢,訪問(wèn)運(yùn)行中或歷史的流程實(shí)例與相關(guān)數(shù)據(jù),等等。
Java 領(lǐng)域另一個(gè)流程引擎是 Activiti,不過(guò)我覺(jué)得這兩個(gè)東西,只要你會(huì)使用其中一個(gè),另一個(gè)就不在話下。
咱就不廢話了,上代碼吧。
2. 創(chuàng)建項(xiàng)目
首先我們創(chuàng)建一個(gè) Spring Boot 項(xiàng)目,引入 Web、和 MySQL 驅(qū)動(dòng)兩個(gè)依賴,如下圖:
項(xiàng)目創(chuàng)建成功之后,我們引入 flowable 依賴,如下:
<dependency>
<groupId>org.flowablegroupId>
<artifactId>flowable-spring-boot-starterartifactId>
<version>6.7.2version>
dependency>
這個(gè)會(huì)幫我們做一些自動(dòng)化配置,默認(rèn)情況下,所以位于 resources/processes 的流程都會(huì)被自動(dòng)部署。
接下來(lái)我們?cè)?application.yaml 中配置一下數(shù)據(jù)庫(kù)連接信息,當(dāng)項(xiàng)目啟動(dòng)的時(shí)候會(huì)自動(dòng)初始化數(shù)據(jù)庫(kù),將來(lái)流程引擎運(yùn)行時(shí)候的數(shù)據(jù)會(huì)被自動(dòng)持久化到數(shù)據(jù)庫(kù)中。
spring:
datasource:
username: root
password: 123
url: jdbc:mysql:///flowable?serverTimezone=Asia/Shanghai&useSSL=false
好啦,配置完成后,我們就可以啟動(dòng)項(xiàng)目了。項(xiàng)目啟動(dòng)成功之后,flowable 數(shù)據(jù)庫(kù)中就會(huì)自動(dòng)創(chuàng)建如下這些表,將來(lái)流程引擎相關(guān)的數(shù)據(jù)都會(huì)自動(dòng)保存到這些表中。
默認(rèn)的表比較多,截圖只是其中一部分。
3. 畫流程圖
畫流程圖算是比較有挑戰(zhàn)的一個(gè)步驟了,也是流程引擎使用的關(guān)鍵。官方提供了一些流程引擎繪制工具,這個(gè)我就不說(shuō)了,感興趣的小伙伴可以自行去體驗(yàn);IDEA 也自帶了一個(gè)流程可視化的工具,但是特別難用,我這里也就 不說(shuō)了。
這里說(shuō)一下我常用的 IDEA 插件 Flowable BPMN visualizer,如下圖:
插件怎么安裝就不用我教了吧,小伙伴們自行安裝即可。
裝好插件之后,我們?cè)?resources 目錄下新建 processes 目錄,這個(gè)目錄下的流程文件將來(lái)會(huì)被自動(dòng)部署。
接下來(lái)我們?cè)?processes 目錄下,新建一個(gè) BPMN 文件(插件裝好了就有這個(gè)選項(xiàng)了),如下:
我們來(lái)畫個(gè)請(qǐng)假的流程,就叫做 ask_for_leave.bpmn20.xml,注意最后面的 .bpmn20.xml
是固定后綴。
文件創(chuàng)建出來(lái)之后,右鍵單擊,選擇 View BPMN(Flowable) Diagram,就打開(kāi)了可視化頁(yè)面了,我們就可以來(lái)繪制自己的流程圖了。
我的請(qǐng)假流程畫出來(lái)是這樣:
員工發(fā)起一個(gè)請(qǐng)假流程,首先是組長(zhǎng)審核,組長(zhǎng)審核通過(guò)了,就進(jìn)入到經(jīng)理審核,經(jīng)理審核通過(guò)了,這個(gè)流程就結(jié)束了,如果組長(zhǎng)審核未通過(guò)或者經(jīng)理審核未通過(guò),則流程給員工發(fā)送一個(gè)請(qǐng)假失敗的通知,流程結(jié)束。
我們來(lái)看下這個(gè)流程對(duì)應(yīng)的 XML 文件,一些流程細(xì)節(jié)會(huì)在 XML 文件中體現(xiàn)出來(lái),如下:
<process id="ask_for_leave" name="ask_for_leave" isExecutable="true">
<userTask id="leaveTask" name="請(qǐng)假" flowable:assignee="#{leaveTask}"/>
<userTask id="zuzhangTask" name="組長(zhǎng)審核" flowable:assignee="#{zuzhangTask}"/>
<userTask id="managerTask" name="經(jīng)理審核" flowable:assignee="#{managerTask}"/>
<exclusiveGateway id="managerJudgeTask"/>
<exclusiveGateway id="zuzhangJudeTask"/>
<endEvent id="endLeave" name="結(jié)束"/>
<startEvent id="startLeave" name="開(kāi)始"/>
<sequenceFlow id="flowStart" sourceRef="startLeave" targetRef="leaveTask"/>
<sequenceFlow id="modeFlow" sourceRef="leaveTask" targetRef="zuzhangTask"/>
<sequenceFlow id="zuzhang_go" sourceRef="zuzhangJudeTask" targetRef="managerTask" name="通過(guò)">
<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="通過(guò)" 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="發(fā)送失敗提示" isForCompensation="true" flowable:class="org.javaboy.flowable.AskForLeaveFail"/>
<sequenceFlow id="endFlow" sourceRef="sendMail" targetRef="askForLeaveFail"/>
<endEvent id="askForLeaveFail" name="請(qǐng)假失敗"/>
<sequenceFlow id="zuzhangTask_zuzhangJudeTask" sourceRef="zuzhangTask" targetRef="zuzhangJudeTask"/>
process>
結(jié)合 XML 文件我來(lái)和大家解釋一下這里涉及到的 Flowable 中的組件,我們來(lái)看下:
-
: 表示一個(gè)完整的工作流程。 -
: 工作流中起點(diǎn)位置,也就是圖中的綠色按鈕。 -
: 工作流中結(jié)束位置,也就是圖中的紅色按鈕。 -
: 代表一個(gè)任務(wù)審核節(jié)點(diǎn)(組長(zhǎng)、經(jīng)理等角色),這個(gè)節(jié)點(diǎn)上有一個(gè)flowable:assignee
屬性,這表示這個(gè)節(jié)點(diǎn)該由誰(shuí)來(lái)處理,將來(lái)在 Java 代碼中調(diào)用的時(shí)候,我們需要指定對(duì)應(yīng)的處理人的 ID 或者其他唯一標(biāo)記。 -
:這是服務(wù)任務(wù),在具體的實(shí)現(xiàn)中,這個(gè)任務(wù)可以做任何事情。 -
: 邏輯判斷節(jié)點(diǎn),相當(dāng)于流程圖中的菱形框。 -
:鏈接各個(gè)節(jié)點(diǎn)的線條,sourceRef 屬性表示線的起始節(jié)點(diǎn),targetRef 屬性表示線指向的節(jié)點(diǎn),我們圖中的線條都屬于這種。
流程圖這塊松哥和大家稍微說(shuō)一下,咋一看這個(gè)圖挺復(fù)雜很難畫,但是實(shí)際上只要你認(rèn)認(rèn)真真去捋一捋這里邊的各個(gè)屬性,基本上很快就明白到底是怎么一回事,我也相信各位小伙伴都有這樣的悟性。
4. 開(kāi)發(fā)接口
接下來(lái)我們寫幾個(gè)接口,來(lái)體驗(yàn)一把流程引擎。
在正式體驗(yàn)之前,我們先來(lái)熟悉幾個(gè)類,這幾個(gè)類我們一會(huì)寫代碼會(huì)用到。
4.1 Java 類梳理
- ProcessDefinition
這個(gè)最好理解,就是流程的定義,也就相當(dāng)于規(guī)范,每個(gè) ProcessDefinition 都會(huì)有一個(gè) id。
- ProcessInstance
這個(gè)就是流程的一個(gè)實(shí)例。簡(jiǎn)單來(lái)說(shuō),ProcessDefinition 相當(dāng)于是類,而 ProcessInstance 則相當(dāng)于是根據(jù)類 new 出來(lái)的對(duì)象。
- Activity
Activity 是流程標(biāo)準(zhǔn)規(guī)范 BPMN2.0 里面的規(guī)范,流程中的每一個(gè)步驟都是一個(gè) Activity。
- Execution
Execution 的含義是流程的執(zhí)行線路,通過(guò) Execution 可以獲得當(dāng)前 ProcessInstance 當(dāng)前執(zhí)行到哪個(gè) Activity了。
- Task
Task 就是當(dāng)前要做的工作。
實(shí)際上這里涉及到的東西比較多,不過(guò)我們今天先整一個(gè)簡(jiǎn)單的例子,所以上面這些知識(shí)點(diǎn)暫時(shí)夠用了。
4.2 查看流程圖
在正式開(kāi)始之前,我們先準(zhǔn)備一個(gè)接口,用來(lái)查看流程圖的實(shí)時(shí)執(zhí)行情況,這樣方便我們查看流程到底執(zhí)行到哪一步了。
具體的代碼如下:
@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();
}
}
}
}
這就一個(gè)工具,沒(méi)啥好說(shuō)的,一會(huì)大家看完后面的代碼,再回過(guò)頭來(lái)看這個(gè)接口,很多地方就都懂了。
4.3 開(kāi)啟一個(gè)流程
為了方便,接下來(lái)的代碼我們都在單元測(cè)試中完成。
首先我們來(lái)開(kāi)啟一個(gè)流程,代碼如下:
String staffId = "1000";
/**
* 開(kāi)啟一個(gè)流程
*/
@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("創(chuàng)建請(qǐng)假流程 processId:{}", processInstance.getId());
}
首先由員工發(fā)起一個(gè)請(qǐng)假流程,map 中存放的 leaveTask 是我們?cè)?XML 流程文件中提前定義好的,提前定義好當(dāng)前這個(gè)任務(wù)創(chuàng)建之后,該由誰(shuí)來(lái)處理,這里我們是假設(shè)由工號(hào)為 1000 的員工來(lái)發(fā)起這樣一個(gè)請(qǐng)假流程。同時(shí),我們還設(shè)置了一些額外信息。ask_for_leave 是我們?cè)?XML 文件中定義的一個(gè) process 的名稱。
好啦,現(xiàn)在我們執(zhí)行這個(gè)單元測(cè)試方法,執(zhí)行完成后,控制臺(tái)會(huì)打印出當(dāng)前這個(gè)流程的 id,我們拿著這個(gè) id 去訪問(wèn) 4.2 小節(jié)的接口,結(jié)果如下:
可以看到,請(qǐng)假用紅色的框框起來(lái)了,說(shuō)明當(dāng)前流程走到了這一步。
4.4 將請(qǐng)求提交給組長(zhǎng)
接下來(lái),我們就需要將這個(gè)請(qǐng)假流程向后推進(jìn)一步,將請(qǐng)假事務(wù)提交給組長(zhǎng),代碼如下:
String zuzhangId = "90";
/**
* 提交給組長(zhǎng)審批
*/
@Test
void submitToZuzhang() {
//員工查找到自己的任務(wù),然后提交給組長(zhǎng)審批
List<Task> list = taskService.createTaskQuery().taskAssignee(staffId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("任務(wù) ID:{};任務(wù)處理人:{};任務(wù)是否掛起:{}", task.getId(), task.getAssignee(), task.isSuspended());
Map<String, Object> map = new HashMap<>();
//提交給組長(zhǎng)的時(shí)候,需要指定組長(zhǎng)的 id
map.put("zuzhangTask", zuzhangId);
taskService.complete(task.getId(), map);
}
}
首先我們利用 staffId 查找到當(dāng)前員工的 id,進(jìn)而找到當(dāng)前員工需要執(zhí)行的任務(wù),遍歷這個(gè)任務(wù),調(diào)用 taskService.complete 方法將任務(wù)提交給組長(zhǎng),注意在 map 中指定組長(zhǎng)的 id。
提交完成后,我們?cè)偃タ戳鞒虉D片,如下:
可以看到,流程圖走到組長(zhǎng)審批了。
4.5 組長(zhǎng)審批
組長(zhǎng)現(xiàn)在有兩種選擇,同意或者拒絕,同意的代碼如下:
/**
* 組長(zhǎng)審批-批準(zhǔn)
*/
@Test
void zuZhangApprove() {
List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("組長(zhǎng) {} 在審批 {} 任務(wù)", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
//組長(zhǎng)審批的時(shí)候,如果是同意,需要指定經(jīng)理的 id
map.put("managerTask", managerId);
map.put("checkResult", "通過(guò)");
taskService.complete(task.getId(), map);
}
}
通過(guò)組長(zhǎng)的 id 查詢組長(zhǎng)的任務(wù),同意的話,需要指定經(jīng)理,也就是這個(gè)流程下一步該由誰(shuí)來(lái)處理。
拒絕的代碼如下:
/**
* 組長(zhǎng)審批-拒絕
*/
@Test
void zuZhangReject() {
List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("組長(zhǎng) {} 在審批 {} 任務(wù)", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
//組長(zhǎng)審批的時(shí)候,如果是拒絕,就不需要指定經(jīng)理的 id
map.put("checkResult", "拒絕");
taskService.complete(task.getId(), map);
}
}
拒絕的話,就沒(méi)那么多事了,直接設(shè)置 checkResult 為拒絕即可。
假設(shè)這里執(zhí)行了同意,那么流程圖如下:
4.6 經(jīng)理審批
經(jīng)理審批和組長(zhǎng)審批差不多,只不過(guò)經(jīng)理這里是最后一步了,不需要再指定下一位處理人了,同意的代碼如下:
/**
* 經(jīng)理審批自己的任務(wù)-批準(zhǔn)
*/
@Test
void managerApprove() {
List<Task> list = taskService.createTaskQuery().taskAssignee(managerId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("經(jīng)理 {} 在審批 {} 任務(wù)", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
map.put("checkResult", "通過(guò)");
taskService.complete(task.getId(), map);
}
}
拒絕代碼如下:
/**
* 經(jīng)理審批自己的任務(wù)-拒絕
*/
@Test
void managerReject() {
List<Task> list = taskService.createTaskQuery().taskAssignee(managerId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("經(jīng)理 {} 在審批 {} 任務(wù)", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
map.put("checkResult", "拒絕");
taskService.complete(task.getId(), map);
}
}
4.7 拒絕流程
如果組長(zhǎng)拒絕了或者經(jīng)理拒絕了,我們也有相應(yīng)的處理方案,首先在 XML 流程文件定義時(shí),如下:
<serviceTask id="sendMail" flowable:exclusive="true" name="發(fā)送失敗提示" isForCompensation="true" flowable:class="org.javaboy.flowable.AskForLeaveFail"/>
如果請(qǐng)假被拒絕,會(huì)進(jìn)入到這個(gè) serviceTask,serviceTask 對(duì)應(yīng)的處理類是 org.javaboy.flowable.AskForLeaveFail,該類的代碼如下:
public class AskForLeaveFail implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("請(qǐng)假失敗。。。");
}
}
也就是請(qǐng)假失敗會(huì)進(jìn)入到這個(gè)方法中,現(xiàn)在我們就可以在這個(gè)方法中該干嘛干嘛了。
5. 小結(jié)
好啦,一個(gè)簡(jiǎn)單的請(qǐng)假流程,希望能帶小伙伴們?nèi)腴T flowable,公眾后臺(tái)回復(fù) flowable,獲取本文案例。
好啦,后面 tienchin 項(xiàng)目視頻中我們?cè)倏纯催@個(gè) flowable 在項(xiàng)目中如何使用:戳戳戳這里–>TienChin 項(xiàng)目配套視頻來(lái)啦。
原文鏈接:https://blog.csdn.net/u012702547/article/details/124834662
- 上一篇:死鎖的產(chǎn)生和避免
- 下一篇:Redis 做接口限流,一個(gè)注解的事
相關(guān)推薦
- 2023-01-31 golang優(yōu)先級(jí)隊(duì)列的實(shí)現(xiàn)全過(guò)程_Golang
- 2023-01-28 GoLang?nil與interface的空指針深入分析_Golang
- 2024-04-08 MAC更新和使用composer
- 2022-09-16 一文詳解Python中復(fù)合語(yǔ)句的用法_python
- 2022-06-01 詳解使用內(nèi)網(wǎng)穿透工具Ngrok代理本地服務(wù)_其它綜合
- 2022-03-28 Android實(shí)現(xiàn)調(diào)取支付寶健康碼_Android
- 2022-12-27 詳解C#中線程傳參,返回值和多線程沖突問(wèn)題的解決_C#教程
- 2022-08-03 python基礎(chǔ)之//、/與%的區(qū)別詳解_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支