前言
工作流框架大家一定不陌生,各種OA系統里我們常常用到。
對於JAVA領域來說一說起工作流框架第一浮現我在腦海中的便是大名鼎鼎的Activiti了。很久以前學習Activiti框架時我也曾記錄過一篇文章。見鏈接:工作流框架Activiti常用功能初探 盡管當時只是學習了一下在之后的相關工作和項目中並沒有用到,通過學習后了解了下, 僅對於知識廣度進行了擴寬。
最近在一個開源項目里見到有使用另一個工做流框架:flowable 。簡單用了下感覺還是挺方便的,於是乎決定還是要來使用下並在此做下記錄,便於后面用到時可以“拿來主義”,哈哈!
什么是flowable?
對於flowable是什么以及關於此框架的具體信息可以參看此項目的官方文檔:https://www.flowable.org/docs/userguide/index.html
官網對於此項目如何使用有非常詳細的描述,只是目前還沒有對應的中文文檔。
Flowable is a light-weight business process engine written in Java.這是官網文檔對此框架的完美解釋:Flowable是一個用java語言寫的輕量級工作流引擎。
在簡單了解flowable后與activiti框架相比的第一感覺就是開發方便快速,易與springBoot等各種框架快速整合。如果項目中需要快速實現一些工作流的相關功能那么用此框架是一個不錯的選擇。
使用版本
用測試方便,這里都使用springBoot和flowable最新的穩定版本
springBoot版本:2.0.1.RELEASE
flowable版本:6.3.0
Flowable與springBoot項目整合
添加依賴
將flowable的依賴加入到POM中即可,flowable使用需要一個數據庫,這里為了方便我選擇mysql
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--flowable工作流依賴--> <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter</artifactId> <version>6.3.0</version> </dependency> <!--mysql依賴--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.45</version> </dependency> </dependencies>
flowable配置
測試方便flowable配置為默認的即可。為了測試時方便看日志信息,我這里將flowable的定時job功能暫時關閉,其他的都用默認的
當然記得要添加一個數據源,我這里添加的mysql,並且記得建好對應的mysql庫,如果沒有建就自己建一個吧
like this:
CREATE DATABASE `flowable-spring-boot` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/flowable-spring-boot?characterEncoding=UTF-8
username: root
password: root
flowable:
#關閉定時任務JOB
async-executor-activate: false
這樣操作后,flowable與springBoot的整個就完成了! 個人非常方便!
然后就可以運行了,初次運行時flowable會將自動執行flowable中的初始化腳本完成工作流所需要的數據表的建立,如果指定的數據庫中還未創建過flowable的相關數據表的話。
定義流程文件
上面已經完成了flowable與springboot的整合了,接下來就可以使用此框架進行流程需要開發了!
同樣在flowable官方文檔中對於流程文件它有這樣的建議:
The Flowable engine expects processes to be defined in the BPMN 2.0 format, which is an XML standard that is widely accepted in the industry.
flowable建議采用業界標准BPMN2.0的XML來描述需要定義的工作流。
那么BPMN這個流程文件應該怎么寫呢?
Typically, such a process definition is modeled with a visual modeling tool, such as the Flowable Designer (Eclipse) or the Flowable Modeler (web application).
上官方文檔中有看到這樣的描述后即便我不會寫也不怕了。通常都是通過專門的流程建模工具來畫出來的,可以用Eclipse里的流程插件來畫。同時Flowable也提供了對應的web管理台可以對流程文件進行創建。詳見: Flowable UI applications
為了方便測試,這里采用一個開源項目中的流程文件,其描述如下:
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef"> <process id="Expense" name="ExpenseProcess" isExecutable="true"> <documentation>報銷流程</documentation> <startEvent id="start" name="開始"></startEvent> <userTask id="fillTask" name="出差報銷" flowable:assignee="${taskUser}"> <extensionElements> <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"> <![CDATA[false]]></modeler:initiator-can-complete> </extensionElements> </userTask> <exclusiveGateway id="judgeTask"></exclusiveGateway> <userTask id="directorTak" name="經理審批"> <extensionElements> <flowable:taskListener event="create" class="com.haiyang.flowable.listener.ManagerTaskHandler"></flowable:taskListener> </extensionElements> </userTask> <userTask id="bossTask" name="老板審批"> <extensionElements> <flowable:taskListener event="create" class="com.haiyang.flowable.listener.BossTaskHandler"></flowable:taskListener> </extensionElements> </userTask> <endEvent id="end" name="結束"></endEvent> <sequenceFlow id="directorNotPassFlow" name="駁回" sourceRef="directorTak" targetRef="fillTask"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='駁回'}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="bossNotPassFlow" name="駁回" sourceRef="bossTask" targetRef="fillTask"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='駁回'}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="flow1" sourceRef="start" targetRef="fillTask"></sequenceFlow> <sequenceFlow id="flow2" sourceRef="fillTask" targetRef="judgeTask"></sequenceFlow> <sequenceFlow id="judgeMore" name="大於500元" sourceRef="judgeTask" targetRef="bossTask"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${money > 500}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="bossPassFlow" name="通過" sourceRef="bossTask" targetRef="end"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='通過'}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="directorPassFlow" name="通過" sourceRef="directorTak" targetRef="end"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='通過'}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="judgeLess" name="小於500元" sourceRef="judgeTask" targetRef="directorTak"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${money <= 500}]]></conditionExpression> </sequenceFlow> </process> <bpmndi:BPMNDiagram id="BPMNDiagram_Expense"> <bpmndi:BPMNPlane bpmnElement="Expense" id="BPMNPlane_Expense"> <bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start"> <omgdc:Bounds height="30.0" width="30.0" x="285.0" y="135.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="fillTask" id="BPMNShape_fillTask"> <omgdc:Bounds height="80.0" width="100.0" x="405.0" y="110.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="judgeTask" id="BPMNShape_judgeTask"> <omgdc:Bounds height="40.0" width="40.0" x="585.0" y="130.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="directorTak" id="BPMNShape_directorTak"> <omgdc:Bounds height="80.0" width="100.0" x="735.0" y="110.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="bossTask" id="BPMNShape_bossTask"> <omgdc:Bounds height="80.0" width="100.0" x="555.0" y="255.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end"> <omgdc:Bounds height="28.0" width="28.0" x="771.0" y="281.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1"> <omgdi:waypoint x="315.0" y="150.0"></omgdi:waypoint> <omgdi:waypoint x="405.0" y="150.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2"> <omgdi:waypoint x="505.0" y="150.16611295681062"></omgdi:waypoint> <omgdi:waypoint x="585.4333333333333" y="150.43333333333334"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="judgeLess" id="BPMNEdge_judgeLess"> <omgdi:waypoint x="624.5530726256983" y="150.44692737430168"></omgdi:waypoint> <omgdi:waypoint x="735.0" y="150.1392757660167"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="directorNotPassFlow" id="BPMNEdge_directorNotPassFlow"> <omgdi:waypoint x="785.0" y="110.0"></omgdi:waypoint> <omgdi:waypoint x="785.0" y="37.0"></omgdi:waypoint> <omgdi:waypoint x="455.0" y="37.0"></omgdi:waypoint> <omgdi:waypoint x="455.0" y="110.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="bossPassFlow" id="BPMNEdge_bossPassFlow"> <omgdi:waypoint x="655.0" y="295.0"></omgdi:waypoint> <omgdi:waypoint x="771.0" y="295.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="judgeMore" id="BPMNEdge_judgeMore"> <omgdi:waypoint x="605.4340277777778" y="169.56597222222223"></omgdi:waypoint> <omgdi:waypoint x="605.1384083044983" y="255.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="directorPassFlow" id="BPMNEdge_directorPassFlow"> <omgdi:waypoint x="785.0" y="190.0"></omgdi:waypoint> <omgdi:waypoint x="785.0" y="281.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="bossNotPassFlow" id="BPMNEdge_bossNotPassFlow"> <omgdi:waypoint x="555.0" y="295.0"></omgdi:waypoint> <omgdi:waypoint x="455.0" y="295.0"></omgdi:waypoint> <omgdi:waypoint x="455.0" y="190.0"></omgdi:waypoint> </bpmndi:BPMNEdge> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram> </definitions>
其中的兩個代理類為:
import org.flowable.engine.delegate.TaskListener; import org.flowable.task.service.delegate.DelegateTask; public class ManagerTaskHandler implements TaskListener { @Override public void notify(DelegateTask delegateTask) { delegateTask.setAssignee("經理"); } }
public class BossTaskHandler implements TaskListener { @Override public void notify(DelegateTask delegateTask) { delegateTask.setAssignee("老板"); } }
為了方便,也可以去掉這兩個JAVA類,將其對應的task改寫為如下的形式:
<userTask id="holidayApprovedTask" name="Holiday approved" flowable:assignee="${employee}"/>
盡管上面的BPMN文件很長,但放心,畢竟那是通過相關的工具生成出來的,對於核心的邏輯部分也很少(主要在process 標簽內) ,如需要詳細了解的可自行學習下BPMN的標簽即可!當然,在flowable的使用文檔中也有相關的描述,詳見:Creating a ProcessEngine
如上定義好一個流程文件后,將其命令為ExpenseProcess.bpmn20.xml並將其放於項目中的resource目錄下的processes(如此目錄不存在自行創建)目錄下就可以了。
like this:
這樣當此框架啟動的時候它會默認加載resource目錄下的processes時就可以將此流程配置加載到數據庫進行持久化了
測試controller
為了方便這里通過一個controller來完成此DEMO的快速編寫
@Controller @RequestMapping(value = "expense") public class ExpenseController { @Autowired private RuntimeService runtimeService; @Autowired private TaskService taskService; @Autowired private RepositoryService repositoryService; @Autowired private ProcessEngine processEngine; /***************此處為業務代碼******************/ }
寫一個controller,並注入由flowable框架啟動時自動注冊的幾個bean,下面的功能將會用到!
開始流程
/** * 添加報銷 * * @param userId 用戶Id * @param money 報銷金額 * @param descption 描述 */ @RequestMapping(value = "add") @ResponseBody public String addExpense(String userId, Integer money, String descption) { //啟動流程 HashMap<String, Object> map = new HashMap<>(); map.put("taskUser", userId); map.put("money", money); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("Expense", map); return "提交成功.流程Id為:" + processInstance.getId(); }
上面的代碼通過接收用戶的一個請求傳入用戶的ID和金額以及描述信息來開啟一個報銷流程,並返回給用戶這個流程的Id
查詢流程列表,待辦列表
/** * 獲取審批管理列表 */ @RequestMapping(value = "/list") @ResponseBody public Object list(String userId) { List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list(); for (Task task : tasks) { System.out.println(task.toString()); } return tasks.toArray().toString(); }
通過上面的代碼獲取出此用戶需要處理的流程
批准,同意
/** * 批准 * * @param taskId 任務ID */ @RequestMapping(value = "apply") @ResponseBody public String apply(String taskId) { Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); if (task == null) { throw new RuntimeException("流程不存在"); } //通過審核 HashMap<String, Object> map = new HashMap<>(); map.put("outcome", "通過"); taskService.complete(taskId, map); return "processed ok!"; }
通過前端傳入的任務ID來對此流程進行同意處理
拒絕,不同意
/** * 拒絕 */ @ResponseBody @RequestMapping(value = "reject") public String reject(String taskId) { HashMap<String, Object> map = new HashMap<>(); map.put("outcome", "駁回"); taskService.complete(taskId, map); return "reject"; }
生成當前流程圖表
/** * 生成流程圖 * * @param processId 任務ID */ @RequestMapping(value = "processDiagram") public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) throws Exception { ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult(); //流程走完的不顯示圖 if (pi == null) { return; } Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult(); //使用流程實例ID,查詢正在執行的執行對象表,返回流程實例對象 String InstanceId = task.getProcessInstanceId(); List<Execution> executions = runtimeService .createExecutionQuery() .processInstanceId(InstanceId) .list(); //得到正在執行的Activity的Id 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); OutputStream out = null; byte[] buf = new byte[1024]; int legth = 0; try { out = httpServletResponse.getOutputStream(); while ((legth = in.read(buf)) != -1) { out.write(buf, 0, legth); } } finally { if (in != null) { in.close(); } if (out != null) { out.close(); } } }
通過傳入流程ID生成當前流程的流程圖給前端,如果流程中使用到中文且生成的圖片是亂碼的,則需要進配置下字體:
/** * @author haiyangp * date: 2018/4/7 * desc: flowable配置----為放置生成的流程圖中中文亂碼 */ @Configuration public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> { @Override public void configure(SpringProcessEngineConfiguration engineConfiguration) { engineConfiguration.setActivityFontName("宋體"); engineConfiguration.setLabelFontName("宋體"); engineConfiguration.setAnnotationFontName("宋體"); } }
整體演示
上面的代碼寫好后就可以演示下整體流程了
1.先啟動好此項目,然后創建一個流程:
訪問:http://localhost:8080/expense/add?userId=123&money=123321
返回:提交成功.流程Id為:2501
2.查詢待辦列表:
訪問:http://localhost:8080/expense/list?userId=123
輸出:Task[id=2507, name=出差報銷]
3.同意:
訪問:http://localhost:8080/expense/apply?taskId=2507
返回:processed ok!
4.生成流程圖:
訪問:http://localhost:8080/expense/processDiagram?processId=2501
返回如下圖片:
整體流程截圖如下:
總結
通過springBoot與flowable的整合體驗到了工作流的開發原來如此簡單方便。
給此框架點贊,向巨人們致敬!
本文源碼地址:https://github.com/puhaiyang/flowable-springboot
文末推薦一個關於flowable的QQ群!
QQ群:242771252
點擊鏈接加入群【flowable學習交流】:https://jq.qq.com/?_wv=1027&k=5DKcjgU
轉載於:https://blog.csdn.net/puhaiyang/article/details/79845248