在這個初步教程中挚赊,將構(gòu)建一個簡單的例子,以展示如何創(chuàng)建一個Flowable流程引擎触机,介紹一些核心概念,并展示如何使用API玷或。 截圖時使用的是IDEA儡首,但實際上可以使用任何IDE。我們使用Maven獲取Flowable依賴及管理構(gòu)建
我們將構(gòu)建的例子是一個簡單的請假(holiday request)流程:
雇員(employee)申請幾天的假期
經(jīng)理(manager)批準或駁回申請
1.搭建環(huán)境
點擊next
點擊next
點擊Finish
就生成了一個空的項目了
然后添加兩個依賴:
Flowable流程引擎偏友。使我們可以創(chuàng)建一個ProcessEngine流程引擎對象蔬胯,并訪問Flowable API。
MySQL的驅(qū)動
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jykj</groupId>
<artifactId>flowable.boot</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql驅(qū)動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mysql驅(qū)動-->
<!-- flowable-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.5.0</version>
</dependency>
</dependencies>
</project>
創(chuàng)建一個新的Java類位他,并添加標準的Java main方法:
package com.jykj.flow;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author netgy
* @since 2020/9/10 15:13
*/
@SpringBootApplication(scanBasePackages="com.jykj")
public class FlowBootApplication {
public static void main(String[] args) {
SpringApplication.run(FlowBootApplication.class,args);
}
}
在resource下面添加application.yml
spring:
application:
name: flow
datasource:
url: jdbc:mysql://60.169.77.40:3306/flowable?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
username: root
password: 801682
main:
allow-bean-definition-overriding: true #當(dāng)遇到同樣名字的時候氛濒,是否允許覆蓋注冊
server:
port: 7777
management:
endpoint:
flowable:
enabled: true
代碼結(jié)構(gòu)圖如下:
點擊右鍵啟動
這樣就得到了一個啟動可用的流程引擎。接下來為它提供一個流程棱诱!
2.部署流程定義
我們要構(gòu)建的流程是一個非常簡單的請假流程泼橘。Flowable引擎需要流程定義為BPMN 2.0格式,這是一個業(yè)界廣泛接受的XML標準迈勋。 在Flowable術(shù)語中炬灭,我們將其稱為一個流程定義(process definition)。一個流程定義可以啟動多個流程實例(process instance)靡菇。流程定義可以看做是重復(fù)執(zhí)行流程的藍圖重归。 在這個例子中,流程定義定義了請假的各個步驟厦凤,而一個流程實例對應(yīng)某個雇員提出的一個請假申請鼻吮。
BPMN 2.0存儲為XML,并包含可視化的部分:使用標準方式定義了每個步驟類型(人工任務(wù)较鼓,自動服務(wù)調(diào)用椎木,等等)如何呈現(xiàn)违柏,以及如何互相連接。這樣BPMN 2.0標準使技術(shù)人員與業(yè)務(wù)人員能用雙方都能理解的方式交流業(yè)務(wù)流程香椎。
我們要使用的流程定義為:
這個流程應(yīng)該已經(jīng)十分自我解釋了漱竖。但為了明確起見,說明一下幾個要點:
我們假定啟動流程需要提供一些信息畜伐,例如雇員名字馍惹、請假時長以及說明。當(dāng)然玛界,這些可以單獨建模為流程中的第一步万矾。 但是如果將它們作為流程的“輸入信息”,就能保證只有在實際請求時才會建立一個流程實例慎框。否則(將提交作為流程的第一步)良狈,用戶可能在提交之前改變主意并取消,但流程實例已經(jīng)創(chuàng)建了鲤脏。 在某些場景中们颜,就可能影響重要的指標(例如啟動了多少申請,但還未完成)猎醇,取決于業(yè)務(wù)目標窥突。
左側(cè)的圓圈叫做啟動事件(start event)。這是一個流程實例的起點硫嘶。
第一個矩形是一個用戶任務(wù)(user task)阻问。這是流程中人類用戶操作的步驟。在這個例子中沦疾,經(jīng)理需要批準或駁回申請称近。
取決于經(jīng)理的決定,排他網(wǎng)關(guān)(exclusive gateway) (帶叉的菱形)會將流程實例路由至批準或駁回路徑哮塞。
如果批準刨秆,則需要將申請注冊至某個外部系統(tǒng),并跟著另一個用戶任務(wù)忆畅,將經(jīng)理的決定通知給申請人衡未。當(dāng)然也可以改為發(fā)送郵件。
如果駁回家凯,則為雇員發(fā)送一封郵件通知他缓醋。
一般來說,這樣的流程定義使用可視化建模工具建立绊诲,如Flowable Designer(Eclipse)或Flowable Web Modeler(Web應(yīng)用)送粱。
flowable-modeler 流程設(shè)計器 點擊可以訪問
admin/test
PPT中已經(jīng)詳細介紹了流程XML了,這里就不再贅述了
現(xiàn)在我們已經(jīng)有了流程BPMN 2.0 XML文件掂之,下來需要將它部署(deploy)到引擎中抗俄。部署一個流程定義意味著:
流程引擎會將XML文件存儲在數(shù)據(jù)庫中脆丁,這樣可以在需要的時候獲取它。
流程定義轉(zhuǎn)換為內(nèi)部的橄镜、可執(zhí)行的對象模型偎快,這樣使用它就可以啟動流程實例。
將流程定義部署至Flowable引擎洽胶,需要使用RepositoryService,其可以從ProcessEngine對象獲取裆馒。使用RepositoryService姊氓,可以通過XML文件的路徑創(chuàng)建一個新的部署(Deployment),并調(diào)用deploy()方法實際執(zhí)行:
部署方式一:
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("holiday-request.bpmn20.xml")
.deploy();
部署方式二:使用flowable-modeler提供的部署工具喷好,本質(zhì)原理同上
我們現(xiàn)在可以通過API查詢驗證流程定義已經(jīng)部署在引擎中(并學(xué)習(xí)一些API)翔横。通過RepositoryService創(chuàng)建的ProcessDefinitionQuery對象實現(xiàn)。
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deployment.getId())
.singleResult();
System.out.println("Found process definition : " + processDefinition.getName());
3.啟動流程
現(xiàn)在已經(jīng)在流程引擎中部署了流程定義梗搅,因此可以使用這個流程定義作為“藍圖”啟動流程實例禾唁。
要啟動流程實例,需要提供一些初始化流程變量无切。一般來說荡短,可以通過呈現(xiàn)給用戶的表單,
接下來哆键,我們使用RuntimeService啟動一個流程實例掘托。收集的數(shù)據(jù)作為一個java.util.Map實例傳遞,其中的鍵就是之后用于獲取變量的標識符籍嘹。這個流程實例使用key啟動闪盔。這個key就是BPMN 2.0 XML文件中設(shè)置的id屬性,在這個例子里是holidayRequest辱士。
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employee", employee);
variables.put("nrOfHolidays", nrOfHolidays);
variables.put("description", description);
ProcessInstance processInstance =
runtimeService.startProcessInstanceByKey("holidayRequest", variables);
在流程實例啟動后泪掀,會創(chuàng)建一個執(zhí)行(execution),并將其放在啟動事件上颂碘。從這里開始异赫,這個執(zhí)行沿著順序流移動到經(jīng)理審批的用戶任務(wù),并執(zhí)行用戶任務(wù)行為凭涂。這個行為將在數(shù)據(jù)庫中創(chuàng)建一個任務(wù)祝辣,該任務(wù)可以之后使用查詢找到。用戶任務(wù)是一個等待狀態(tài)(wait state)切油,引擎會停止執(zhí)行蝙斜,返回API調(diào)用處。
4.查詢并完成任務(wù)
在更實際的應(yīng)用中澎胡,會為雇員及經(jīng)理提供用戶界面孕荠,讓他們可以登錄并查看任務(wù)列表娩鹉。其中可以看到作為流程變量存儲的流程實例數(shù)據(jù),并決定如何操作任務(wù)稚伍。在這個例子中弯予,我們通過執(zhí)行API調(diào)用來模擬任務(wù)列表,通常這些API都是由UI驅(qū)動的服務(wù)在后臺調(diào)用的个曙。
我們還沒有為用戶任務(wù)配置辦理人锈嫩。我們想將第一個任務(wù)指派給"經(jīng)理(managers)"組,而第二個用戶任務(wù)指派給請假申請的提交人垦搬。因此需要為第一個任務(wù)添加candidateGroups屬性:
要獲得實際的任務(wù)列表呼寸,需要通過TaskService創(chuàng)建一個TaskQuery。我們配置這個查詢只返回’managers’組的任務(wù):
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskAssignee(assignee).list()
System.out.println("You have " + tasks.size() + " tasks:");
for (int i=0; i<tasks.size(); i++) {
System.out.println((i+1) + ") " + tasks.get(i).getName());
}
經(jīng)理現(xiàn)在就可以完成任務(wù)了猴贰。在現(xiàn)實中对雪,這通常意味著由用戶提交一個表單。表單中的數(shù)據(jù)作為流程變量傳遞米绕。在這里瑟捣,我們在完成任務(wù)時傳遞帶有’approved’變量(這個名字很重要捉蚤,因為之后會在順序流的條件中使用T)的map來模擬:
variables = new HashMap<String, Object>();
variables.put("approved", approved);
taskService.complete(task.getId(), variables);
現(xiàn)在任務(wù)完成甥雕,并會在離開排他網(wǎng)關(guān)的兩條路徑中延蟹,基于’approved’流程變量選擇一條胧卤。
5.服務(wù)任務(wù)(service task)
<serviceTask id="sid-B218EF6F-2E84-4C2B-AADA-DCA1E819BD64" name="調(diào)用外部系統(tǒng)"
flowable:class="com.jykj.flow.listener.CallExternalSystemDelegate"></serviceTask>
在現(xiàn)實中畦徘,這個邏輯可以做任何事情:向某個系統(tǒng)發(fā)起一個HTTP REST服務(wù)調(diào)用剃幌,或調(diào)用某個使用了好幾十年的系統(tǒng)中的遺留代碼恰响。我們不會在這里實現(xiàn)實際的邏輯劫笙,而只是簡單的日志記錄流程芙扎。
創(chuàng)建一個新的類CallExternalSystemDelegate作為類名。讓這個類實現(xiàn)org.flowable.engine.delegate.JavaDelegate接口填大,并實現(xiàn)execute方法:
package com.jykj.flow.listener;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
/**
* @author netgy
* @since 2020/9/10 15:59
*/
public class CallExternalSystemDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) {
Object employee=execution.getVariable("employee");
Object nrOfHolidays=execution.getVariable("nrOfHolidays");
Object description=execution.getVariable("description");
Object comments=execution.getVariable("comments");
System.out.println("調(diào)用外部系統(tǒng)戒洼,為員工: "
+ employee);
System.out.println("請假天數(shù): "
+ nrOfHolidays);
System.out.println("請假原因: "
+ description);
System.out.println("審批意見: "
+ comments);
}
}
創(chuàng)建一個新的類SendEmailDelegate作為類名。讓這個類實現(xiàn)org.flowable.engine.delegate.JavaDelegate接口允华,并實現(xiàn)execute方法:
package com.jykj.flow.listener;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
/**
* @author netgy
* @since 2020/9/10 15:59
*/
public class SendEmailDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) {
Object employee=execution.getVariable("employee");
Object nrOfHolidays=execution.getVariable("nrOfHolidays");
Object description=execution.getVariable("description");
Object comments=execution.getVariable("comments");
System.out.println("駁回了圈浇,發(fā)郵件給員工: "
+ employee);
System.out.println("請假天數(shù): "
+ nrOfHolidays);
System.out.println("請假原因: "
+ description);
System.out.println("審批意見: "
+ comments);
}
}
6.使用歷史數(shù)據(jù)
選擇使用Flowable這樣的流程引擎的原因之一,是它可以自動存儲所有流程實例的審計數(shù)據(jù)或歷史數(shù)據(jù)靴寂。這些數(shù)據(jù)可以用于創(chuàng)建報告磷蜀,深入展現(xiàn)組織運行的情況,瓶頸在哪里百炬,等等褐隆。
例如,如果希望顯示流程實例已經(jīng)執(zhí)行的時間剖踊,就可以從ProcessEngine獲取HistoryService庶弃,并創(chuàng)建歷史活動(historical activities)的查詢衫贬。在下面的代碼片段中,可以看到我們添加了一些額外的過濾條件:
只選擇一個特定流程實例的活動
只選擇已完成的活動
結(jié)果按照結(jié)束時間排序歇攻,代表其執(zhí)行順序固惯。
HistoryService historyService = processEngine.getHistoryService();
List<HistoricActivityInstance> activities =
historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstance.getId())
.finished()
.orderByHistoricActivityInstanceEndTime().asc()
.list();
for (HistoricActivityInstance activity : activities) {
System.out.println(activity.getActivityId() + " took "
+ activity.getDurationInMillis() + " milliseconds");
}
7進階 將流程對外提供HTTP請求的支持
maven增加依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
增加FlowController類
package com.jykj.flow.controller;
import com.jykj.flow.common.Result;
import com.jykj.flow.service.FlowService;
import com.jykj.flow.vo.TaskVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author netgy
* @since 2020/9/3 9:39
*/
@RestController
public class FlowController {
@Autowired
private FlowService flowService;
@GetMapping(value="/process")
public Result startProcessInstance(String key, Integer nrOfHolidays, String description, String employee) {
flowService.startProcess(key, nrOfHolidays, description, employee);
return Result.success("success");
}
@RequestMapping(value="/tasks", method= RequestMethod.GET, produces= MediaType.APPLICATION_JSON_VALUE)
public List<TaskVo> getTasks(@RequestParam String assignee) {
return flowService.getTasks(assignee);
}
@RequestMapping(value="/completeTask", method= RequestMethod.GET, produces= MediaType.APPLICATION_JSON_VALUE)
public Result completeTask(String taskId, Integer approved,String comments){
flowService.completeTask(taskId,approved,comments);
return Result.success("success");
}
}
增加FlowService類
package com.jykj.flow.service;
import com.jykj.flow.vo.HolidayVo;
import com.jykj.flow.vo.TaskVo;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author netgy
* @since 2020/9/3 9:39
*/
@Service
public class FlowService {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Transactional
public void startProcess(String key, Integer nrOfHolidays, String description, String employee) {
Map<String, Object> variables = new HashMap();
variables.put("employee", employee);
variables.put("nrOfHolidays", nrOfHolidays);
variables.put("description", description);
variables.put("approved", 0);
variables.put("comments", "");
runtimeService.startProcessInstanceByKey(key, variables);
List<TaskVo> tasks = this.getTasks(employee);
if(tasks!=null&&!tasks.isEmpty()){
taskService.complete( tasks.get(0).getId());
}
}
@Transactional
public List<TaskVo> getTasks(String assignee) {
List<Task> tasks = new ArrayList<>();
tasks.addAll(taskService.createTaskQuery().taskAssignee(assignee).list());
List<TaskVo> dtos = new ArrayList<TaskVo>();
for (Task task : tasks) {
TaskVo taskVo=new TaskVo(task.getId(), task.getName());
HolidayVo holidayVo=new HolidayVo();
holidayVo.setEmployee((String)taskService.getVariable(task.getId(),"employee"));
holidayVo.setNrOfHolidays((Integer)taskService.getVariable(task.getId(),"nrOfHolidays")+"");
holidayVo.setDescription((String)taskService.getVariable(task.getId(),"description"));
taskVo.setHolidayVo(holidayVo);
dtos.add(taskVo);
}
return dtos;
}
@Transactional
public void completeTask(String taskId, Integer approved,String comments) {
taskService.setVariable(taskId, "approved", approved);
taskService.setVariable(taskId, "comments", comments);
taskService.complete(taskId);
}
}