Activiti 工作流引擎的初步使用
最近領(lǐng)導(dǎo)讓我研究下工作流,于是查啊查就查到了Activiti,特么剛開(kāi)始一直查的是Activity,查出來(lái)一堆Android的東西,我也是醉了。話不多說(shuō),下面就記錄下這2天的研究成果吧。
所用環(huán)境
Maven工程
JDK:jdk1.8.0_73
IDE:eclipse Mars.2 Release (4.5.2)
數(shù)據(jù)庫(kù):mysql 5.1.39-ndb-7.0.9-cluster-gpl
SSM框架:(spring + spring-mvc)4.3.2.RELEASE + mybatis3.4.1
Activiti:5.21.0
spring+mvc+mybatis整合就不貼了,網(wǎng)上一大堆了
eclipse安裝流程設(shè)計(jì)插件
eclipse依次點(diǎn)擊 Help -> Install New Software -> Add:
Name:Activiti Designer
Location:http://activiti.org/designer/update/
點(diǎn)擊OK選中插件安裝即可
添加 Activiti 到項(xiàng)目中
在 pom.xml 中添加 Activiti 依賴
<activiti.version>5.21.0</activiti.version>
2.新建 applicationContext-activiti.xml,別忘了在主配置文件中將其import
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
3.啟動(dòng)項(xiàng)目,如果未出現(xiàn)錯(cuò)誤,Activiti會(huì)在連接的數(shù)據(jù)庫(kù)中自動(dòng)新建25張表,如下:
25張表.png
至此Activiti流程引擎添加完畢,下面開(kāi)始使用Activiti,實(shí)現(xiàn)一次請(qǐng)假流程
設(shè)計(jì)流程
流程設(shè)計(jì)插件安裝成功后會(huì)在eclipse新建向?qū)е谐霈F(xiàn)Activiti向?qū)?如圖
Paste_Image.png
1.我們新建一個(gè) Activiti Diagram 命名為 leave.bpmn,完成時(shí)如下圖:
Paste_Image.png
主要就是拖拖拉拉,從左至右控件分別為
StartEvent,UserTask,ExlusiveGateway,UserTask,EndEvent;連線都是SequenceFlow
屬性可在Properties視圖中設(shè)置,如果沒(méi)有這個(gè)視圖,可在 eclipse 中依次點(diǎn)擊
Window->Show View->Other 搜索Properties點(diǎn)擊OK即可
- 流程屬性:一般設(shè)置一個(gè)Id,Name,NameSpace就可以了,此處為分別為leaveProcess、Leave Process、http://www.mario.com; 這個(gè)Id在之后啟動(dòng)流程時(shí)會(huì)用到,不同流程Id不可相同
- 開(kāi)始事件(StartEvent):流程開(kāi)始
- 結(jié)束事件(EndEvent):流程結(jié)束
- 用戶任務(wù)(UserTask):主要用到Id,Name,Assignee
Assignee為此任務(wù)的辦理人,在查找任務(wù)時(shí)需要用到,三個(gè)任務(wù)分別指派給 apply,pm,boss
Paste_Image.png
Paste_Image.png
- 排他網(wǎng)關(guān)(ExlusiveGateway):只會(huì)尋找唯一一條能走完的順序流
- 順序流(SequenceFlow):主要用到Id,Name,Condition
Condition用于指定該順序流表達(dá)式 ,day的值會(huì)在調(diào)用執(zhí)行任務(wù)方法時(shí)傳入
Paste_Image.png
除了使用可視化組件,我們也可以通過(guò)xml來(lái)設(shè)計(jì)流程,以上流程的xml定義如下:
<?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:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.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.mario.com"><process id="leaveProcess" name="Leave Process" isExecutable="true"><startEvent id="startevent1" name="開(kāi)始"></startEvent><userTask id="usertask1" name="請(qǐng)假申請(qǐng)" activiti:assignee="apply"></userTask><exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway><sequenceFlow id="flow2" name="天數(shù)判斷" sourceRef="usertask1" targetRef="exclusivegateway1"></sequenceFlow><userTask id="usertask2" name="審批(項(xiàng)目經(jīng)理)" activiti:assignee="pm"></userTask><sequenceFlow id="flow3" name="小于等于三天" sourceRef="exclusivegateway1" targetRef="usertask2"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${day<=3}]]></conditionExpression></sequenceFlow><userTask id="usertask3" name="審批(老板)" activiti:assignee="boss"></userTask><sequenceFlow id="flow4" name="大于三天" sourceRef="exclusivegateway1" targetRef="usertask3"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${day>3}]]></conditionExpression></sequenceFlow><endEvent id="endevent1" name="End"></endEvent><sequenceFlow id="flow5" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow><sequenceFlow id="flow6" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow><sequenceFlow id="flow7" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow></process><bpmndi:BPMNDiagram id="BPMNDiagram_leaveProcess"><bpmndi:BPMNPlane bpmnElement="leaveProcess" id="BPMNPlane_leaveProcess"><bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1"><omgdc:Bounds height="35.0" width="35.0" x="30.0" y="211.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1"><omgdc:Bounds height="55.0" width="105.0" x="110.0" y="201.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1"><omgdc:Bounds height="40.0" width="40.0" x="285.0" y="208.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2"><omgdc:Bounds height="55.0" width="105.0" x="400.0" y="120.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3"><omgdc:Bounds height="55.0" width="105.0" x="400.0" y="290.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1"><omgdc:Bounds height="35.0" width="35.0" x="560.0" y="211.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2"><omgdi:waypoint x="215.0" y="228.0"></omgdi:waypoint><omgdi:waypoint x="285.0" y="228.0"></omgdi:waypoint><bpmndi:BPMNLabel><omgdc:Bounds height="14.0" width="48.0" x="230.0" y="228.0"></omgdc:Bounds></bpmndi:BPMNLabel></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3"><omgdi:waypoint x="305.0" y="208.0"></omgdi:waypoint><omgdi:waypoint x="452.0" y="175.0"></omgdi:waypoint><bpmndi:BPMNLabel><omgdc:Bounds height="14.0" width="100.0" x="295.0" y="180.0"></omgdc:Bounds></bpmndi:BPMNLabel></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4"><omgdi:waypoint x="305.0" y="248.0"></omgdi:waypoint><omgdi:waypoint x="452.0" y="290.0"></omgdi:waypoint><bpmndi:BPMNLabel><omgdc:Bounds height="14.0" width="48.0" x="285.0" y="257.0"></omgdc:Bounds></bpmndi:BPMNLabel></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5"><omgdi:waypoint x="452.0" y="175.0"></omgdi:waypoint><omgdi:waypoint x="577.0" y="211.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6"><omgdi:waypoint x="452.0" y="290.0"></omgdi:waypoint><omgdi:waypoint x="577.0" y="246.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7"><omgdi:waypoint x="65.0" y="228.0"></omgdi:waypoint><omgdi:waypoint x="110.0" y="228.0"></omgdi:waypoint></bpmndi:BPMNEdge></bpmndi:BPMNPlane></bpmndi:BPMNDiagram> </definitions>流程部署
有了流程圖,我們就可以部署該流程了,Activiti提供多種部署方法(有自動(dòng)部署,手動(dòng)部署等),這里演示兩種手動(dòng)部署方法
Workflow.java代碼片段
private static ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); /*** 通過(guò)定義好的流程圖文件部署,一次只能部署一個(gè)流程*/ public static void deploy() {RepositoryService repositoryService = processEngine.getRepositoryService();Deployment deployment = repositoryService.createDeployment().addClasspathResource("death/note/lawliet/web/workflow/leave.bpmn").deploy(); } /*** 將多個(gè)流程文件打包部署,一次可以部署多個(gè)流程*/ public void deployByZip() {InputStream is = this.getClass().getClassLoader().getResourceAsStream("diagrams/bpm.zip");ZipInputStream zip = new ZipInputStream(is);Deployment deployment = processEngine.getRepositoryService().createDeployment().addZipInputStream(zip).deploy(); }方便起見(jiàn),通過(guò)一個(gè)Deploy按鈕來(lái)部署流程
Paste_Image.png
部署成功后,會(huì)分別在 act_ge_bytearray,act_re_deployment,act_re_procdef三張表插入相應(yīng)數(shù)據(jù),多次部署同一流程的話會(huì)增加版本號(hào),以此獲取最新的流程
啟動(dòng)流程
我們通過(guò)一個(gè)表單來(lái)用于請(qǐng)假申請(qǐng)
Paste_Image.png
我們定義一個(gè) Leave 對(duì)象用于保存請(qǐng)假信息,相應(yīng)的數(shù)據(jù)表為 leave 。Result 對(duì)象用于封裝一些返回信息。這里的 "leaveProcess" 就是在流程屬性中定義的Id。啟動(dòng)成功后會(huì)返回一個(gè) ProcessInstance 對(duì)象,這個(gè)對(duì)象主要是一些流程的基本信息,具體可以查看文檔或源碼。這里將返回的流程實(shí)例 id 存入到我們的 leave 表,以便以后可以通過(guò)這個(gè) id 查詢相關(guān)的流程。
@ResponseBody @RequestMapping(value = "/save", method = RequestMethod.POST) public Result save(@RequestBody Leave user) {Result result = new Result();ProcessInstance pi = Workflow.startInstanceByKey("leaveProcess");user.setInstaceId(pi.getId());leaveService.insert(user);result.info(true, 0, "保存成功");return result; }Workflow.java代碼片段
public static ProcessInstance startInstanceByKey(String instanceByKey) {RuntimeService runtimeService = processEngine.getRuntimeService();ProcessInstance instance = runtimeService.startProcessInstanceByKey(instanceByKey);return instance; }流程啟動(dòng)成功后會(huì)在 act_hi_actinst,act_hi_identitylink,act_hi_procinst,act_hi_taskinst,act_ru_execution,act_ru_identitylink,act_ru_task 表中插入相應(yīng)數(shù)據(jù)。我們比較關(guān)心的就是 act_ru_task 表了,它存儲(chǔ)了任務(wù)的相關(guān)信息。
查看任務(wù)
流程啟動(dòng)完畢后,應(yīng)該就是進(jìn)入了請(qǐng)假申請(qǐng)任務(wù),我們可以通過(guò)請(qǐng)假申請(qǐng)的辦理人 apply 來(lái)查詢?cè)撊蝿?wù)(當(dāng)然還有其他方法),這里可以指定不同查詢條件和過(guò)濾條件。返回 taskList 就是當(dāng)前的任務(wù)列表了,Task是Activiti為我們定義好的接口對(duì)象,主要封裝了任務(wù)的信息。
這里由于 Activiti 的Task是接口對(duì)象無(wú)法轉(zhuǎn)換為json,所以自定義了一個(gè)Task對(duì)象,來(lái)轉(zhuǎn)換成json
Workflow.java代碼片段
public static List<Task> findTaskByAssignee(String assignee) {TaskService taskService = processEngine.getTaskService();List<Task> taskList = taskService.createTaskQuery().taskAssignee(assignee).list();return taskList; }Paste_Image.png
查看流程圖
我們可以通過(guò)流程定義ID(processDefinitionId)來(lái)獲取流程圖
@RequestMapping(value = "/shwoImg/{procDefId}") public void shwoImg(@PathVariable String procDefId,HttpServletResponse response){ try { InputStream pic = Workflow.findProcessPic(procDefId); byte[] b = new byte[1024]; int len = -1; while((len = pic.read(b, 0, 1024)) != -1) { response.getOutputStream().write(b, 0, len); } } catch (Exception e) { e.printStackTrace(); } }Workflow.java代碼片段
public static InputStream findProcessPic(String procDefId) throws Exception {RepositoryService repositoryService = processEngine.getRepositoryService();ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId).singleResult();String diagramResourceName = procDef.getDiagramResourceName();InputStream imageStream = repositoryService.getResourceAsStream(procDef.getDeploymentId(), diagramResourceName);return imageStream; }然后通過(guò)processDefinitionId,executionId 來(lái)獲取當(dāng)前正在執(zhí)行任務(wù)的位置坐標(biāo),以便用于標(biāo)識(shí)流程圖上的位置。ActivityImpl 對(duì)象封裝了任務(wù)的位置信息,包括xy坐標(biāo),長(zhǎng)和寬。自定義 Rect 對(duì)象用于轉(zhuǎn)換json
@ResponseBody @RequestMapping(value = "/showImg/{procDefId}/{executionId}") public Rect showImg(@PathVariable String procDefId,@PathVariable String executionId ) {Rect rect = new Rect();try {ActivityImpl img = Workflow.getProcessMap(procDefId,executionId );rect.setX(img.getX());rect.setY(img.getY());rect.setWidth(img.getWidth());rect.setHeight(img.getHeight());} catch (Exception e) {e.printStackTrace();}return rect; }Workflow.java代碼片段
public static ActivityImpl getProcessMap(String procDefId, String executionId) throws Exception {ActivityImpl actImpl = null;RepositoryService repositoryService = processEngine.getRepositoryService();//獲取流程定義實(shí)體ProcessDefinitionEntity def = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition(procDefId);RuntimeService runtimeService = processEngine.getRuntimeService();//獲取執(zhí)行實(shí)體ExecutionEntity execution = (ExecutionEntity) runtimeService.createExecutionQuery().executionId(executionId).singleResult();// 獲取當(dāng)前任務(wù)執(zhí)行到哪個(gè)節(jié)點(diǎn)String activitiId = execution.getActivityId();// 獲得當(dāng)前任務(wù)的所有節(jié)點(diǎn)List<ActivityImpl> activitiList = def.getActivities();for (ActivityImpl activityImpl : activitiList) {String id = activityImpl.getId();if (id.equals(activitiId)) {actImpl = activityImpl;break;}}return actImpl; }最終生成圖片時(shí)分別調(diào)用2個(gè) showImg 方法即可,紅框可以根據(jù)返回的 Rect 來(lái)繪制,起始坐標(biāo)根據(jù)自己的布局自行調(diào)整
LeavePicController.js片段
var pic.rect = {}; var pic.procDefId = $stateParams.procDefId; $http.get('workflow/showImg/'+$stateParams.procDefId +'/'+$stateParams.executionId) .success(function(data) {pic.rect.x = data.x;pic.rect.y = data.y;pic.rect.width = data.width;pic.rect.height = data.height;});picture.html
<div class="container-fluid" ng-controller="LeavePicController as pic"><img src="workflow/showImg/{{pic.procDefId}}"><div style="position:absolute; border:2px solid red;left:{{pic.rect.x + 20 }}px;top:{{pic.rect.y + 88 }}px;width:{{pic.rect.width }}px;height:{{pic.rect.height }}px;"></div> </div>Paste_Image.png
流程審批
通過(guò) taskId 就可以對(duì)當(dāng)前執(zhí)行的任務(wù)進(jìn)行審批,這里的 day 應(yīng)該從 leave 表中查詢出來(lái),方便起見(jiàn)就寫(xiě)死了,這個(gè)day也就是在順序流的Condition中指定的變量,小于等于3就會(huì)流向項(xiàng)目經(jīng)理(pm)審批任務(wù)了
@ResponseBody @RequestMapping(value = "/check/{taskId}") public Result check(@PathVariable String taskId) {Result result = new Result();Map<String, Object> map = new HashMap<>();map.put("day", 3);Workflow.completeTask(taskId,map);result.info(true, 0, "審批成功");return result; }Workflow.java代碼片段
public static void completeTask(String taskid,Map<String, Object> map map) {TaskService taskService = processEngine.getTaskService();taskService.complete(taskid, map); }審批成功后,就需要通過(guò)辦理人(pm)來(lái)查詢?nèi)蝿?wù)了,并且請(qǐng)假辦理(apply)中的任務(wù)被移除了
Paste_Image.png
Paste_Image.png
同時(shí)流程圖也流向了下一節(jié)點(diǎn)
Paste_Image.png
項(xiàng)目經(jīng)理再執(zhí)行一次審批方法,這個(gè)流程就算走完了
最后
以上就是一次相對(duì)簡(jiǎn)單的流程:部署,啟動(dòng),查詢?nèi)蝿?wù),顯示流程圖,審批。Activiti流程引擎還有很多核心操作,包括駁回、會(huì)簽、轉(zhuǎn)辦、中止、掛起等,等有空的時(shí)候再深入研究吧
最后的最后,初來(lái)乍到,不喜勿噴或輕噴 - -!Thanks...
作者:影秋
鏈接:https://www.jianshu.com/p/bdc9c9fa719d
來(lái)源:簡(jiǎn)書(shū)
?
?
?
總結(jié)
以上是生活随笔為你收集整理的Activiti 工作流引擎的初步使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 工作流引擎Activiti使用总结
- 下一篇: Activiti总体框架分析