從零開始搭建SpringBoot的Flowable工作流

在這個初步教程中挚赊,將構(gòu)建一個簡單的例子,以展示如何創(chuàng)建一個Flowable流程引擎触机,介紹一些核心概念,并展示如何使用API玷或。 截圖時使用的是IDEA儡首,但實際上可以使用任何IDE。我們使用Maven獲取Flowable依賴及管理構(gòu)建

我們將構(gòu)建的例子是一個簡單的請假(holiday request)流程:

雇員(employee)申請幾天的假期

經(jīng)理(manager)批準或駁回申請

1.搭建環(huán)境

image.png

點擊next


image.png

點擊next


image.png

點擊Finish
image.png

就生成了一個空的項目了

然后添加兩個依賴:

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)圖如下:

image.png

點擊右鍵啟動

image.png
image.png
image.png

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

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ù)流程香椎。

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

image.png

這個流程應(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了,這里就不再贅述了

image.png

現(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ì)原理同上
image.png

我們現(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);
    }
}

8.測試

image.png

啟動流程

image.png

經(jīng)理審批列表

image.png

同意審批

image.png

image.png

不同意審批

image.png

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缴守,隨后出現(xiàn)的幾起案子葬毫,更是在濱河造成了極大的恐慌,老刑警劉巖屡穗,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件供常,死亡現(xiàn)場離奇詭異,居然都是意外死亡鸡捐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門麻裁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箍镜,“玉大人,你說我怎么就攤上這事煎源∩兀” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵手销,是天一觀的道長歇僧。 經(jīng)常有香客問我,道長锋拖,這世上最難降的妖魔是什么诈悍? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮兽埃,結(jié)果婚禮上侥钳,老公的妹妹穿的比我還像新娘。我一直安慰自己柄错,他們只是感情好舷夺,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著售貌,像睡著了一般给猾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上颂跨,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天敢伸,我揣著相機與錄音,去河邊找鬼毫捣。 笑死详拙,一個胖子當(dāng)著我的面吹牛帝际,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播饶辙,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼蹲诀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了弃揽?” 一聲冷哼從身側(cè)響起脯爪,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎矿微,沒想到半個月后痕慢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡涌矢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年掖举,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娜庇。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡塔次,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出名秀,到底是詐尸還是另有隱情励负,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布匕得,位于F島的核電站继榆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏汁掠。R本人自食惡果不足惜略吨,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望调塌。 院中可真熱鬧晋南,春花似錦、人聲如沸羔砾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姜凄。三九已至政溃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間态秧,已是汗流浹背董虱。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人愤诱。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓云头,卻偏偏與公主長得像,于是被迫代替她去往敵國和親淫半。 傳聞我的和親對象是個殘疾皇子溃槐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359