設計思路:前端
(1)一個頁面,兩個tab標籤:A和B。java
(2)A標籤加載流程圖,B標籤加載流程數據。數據庫
流程圖的做用:顯示全局流程佈局,高亮顯示當前進行的環節。svg
流程數據的做用:顯示當前人,處理人,處理時間等須要的信息。佈局
(3)加載流程圖:性能
頁面傳回businessKey,後臺實現查詢。查詢須要用到的對象:HistoricProcessInstance 或者 ProcessInstance,二者有什麼區別?this
HistoricProcessInstance:既能夠查詢歷史流程實例(結束的流程),也能夠查詢運行中的流程實例。(調用getHistoricService()方法實現業務處理)spa
ProcessInstance :只查詢運行中的流程實例。(調用getRuntimeService()方法實現業務處理)設計
結論:須要效率而且不關注結束流程的狀況選擇 ProcessInstance,而針對流程監控若是結束的流程也須要監控,應該選擇 HistoricProcessInstance 。code
重要代碼:
/*取歷史流程實例,既能取到歷史實例又能取到運行中的流程實例*/ HistoricProcessInstance hpi = workFlowEngineServiceImpl.findHistoryProcessInstanceByBusKey(businessKey); try { if (hpi == null) { throw new RuntimeException("獲取流程圖異常!"); } else { InputStream imageStream = workFlowEngineServiceImpl.getFlowMap(hpi, hpi.getId(), flowType); ServletOutputStream os = response.getOutputStream(); int bytesRead = 0; byte[] buffer = new byte[1024]; while ((bytesRead = imageStream.read(buffer, 0, 1024)) != -1) { os.write(buffer, 0, bytesRead); } os.flush(); os.close(); imageStream.close(); } } catch (Exception e) { logger.error(e, e); throw new RuntimeException("獲取流程圖異常!"); } /*findHistoryProcessInstanceByBusKey方法*/ /** * 根據流程businessKey查詢歷史流程實例 * @param processId * @return */ public HistoricProcessInstance findHistoryProcessInstanceByBusKey(String businessKey){ HistoryService historyService = this.getHistoryService(); return historyService.createHistoricProcessInstanceQuery() .processInstanceBusinessKey(businessKey).singleResult(); } /*getFlowMap方法*/ public InputStream getFlowMap(HistoricProcessInstance processInstance, String instanceId, String flowType) { processEngine = getInstance(); // RuntimeService runtimeService = processEngine.getRuntimeService(); // DynamicBpmnService flowMoniService = processEngine.getDynamicBpmnService(); /*資源服務*/ RepositoryService repositoryService = processEngine.getRepositoryService(); /*歷史數據服務*/ HistoryService historyService = processEngine.getHistoryService(); ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity)repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId()); BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); /*爲了流程監控圖顯示效果,替換多有未取到的變量,只顯示節點的中文描述*/ List<ActivityImpl> activityList = processDefinition.getActivities(); for(ActivityImpl activity : activityList){ String name = bpmnModel.getFlowElement(activity.getId()).getName(); bpmnModel.getFlowElement(activity.getId()) .setName(name.replaceAll("[\\w{}$\\-+]", "")); } /*歷史節點,取出變量,設置爲節點的名稱*/ List<ArkHistoricActivity> hisList = findProcessHistoryByPiid(instanceId); for(ArkHistoricActivity hisActivity : hisList){ bpmnModel.getFlowElement(hisActivity.getActivityId()) .setName(hisActivity.getActivityName()); } List<HistoricActivityInstance> activityInstances = historyService.createHistoricActivityInstanceQuery() .processInstanceId(instanceId).orderByHistoricActivityInstanceStartTime().asc().list(); List<String> activitiIds = new ArrayList<String>(); List<String> flowIds = new ArrayList<String>(); /*獲取流程走過的線*/ flowIds = flowServiceImpl.getHighLightedFlows(processDefinition, activityInstances); /*獲取流程走過的節點*/ for (HistoricActivityInstance hai : activityInstances) { activitiIds.add(hai.getActivityId()); } Context.setProcessEngineConfiguration( (ProcessEngineConfigurationImpl) processEngine.getProcessEngineConfiguration()); /** * 從配置文件中獲取中文配置信息,避免中文亂碼 * processEngine.getProcessEngineConfiguration().getActivityFontName(), * processEngine.getProcessEngineConfiguration().getLabelFontName(), */ InputStream imageStream = new DefaultProcessDiagramGenerator().generateDiagram(bpmnModel, "png", activitiIds, flowIds, processEngine.getProcessEngineConfiguration().getActivityFontName(), processEngine.getProcessEngineConfiguration().getLabelFontName(), "", null, 1.0); return imageStream; } /*getHighLightedFlows方法*/ public List<String> getHighLightedFlows(ProcessDefinitionEntity processDefinitionEntity, List<HistoricActivityInstance> historicActivityInstances) { /*用以保存高亮的線flowId*/ List<String> highFlows = new ArrayList<String>(); /*對歷史流程節點進行遍歷*/ for (int i = 0; i < historicActivityInstances.size() - 1; i++) { /*獲得節點定義的詳細信息*/ ActivityImpl activityImpl = processDefinitionEntity.findActivity(historicActivityInstances.get(i).getActivityId()); /*用以保存後需開始時間相同的節點*/ List<ActivityImpl> sameStartTimeNodes = new ArrayList<ActivityImpl>(); /*將後面第一個節點放在時間相同節點的集合裏*/ ActivityImpl sameActivityImpl1 = processDefinitionEntity.findActivity(historicActivityInstances.get(i + 1).getActivityId()); sameStartTimeNodes.add(sameActivityImpl1); for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) { /*後續第一個節點*/ HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j); /*後續第二個節點*/ HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1); /*若是第一個節點和第二個節點開始時間相同保存*/ if (activityImpl1.getStartTime().equals(activityImpl2.getStartTime())) { ActivityImpl sameActivityImpl2 = processDefinitionEntity.findActivity(activityImpl2.getActivityId()); sameStartTimeNodes.add(sameActivityImpl2); } /*有不相同跳出循環*/ else { break; } } /*取出節點的全部出去的線*/ List<PvmTransition> pvmTransitions = activityImpl.getOutgoingTransitions(); /*對全部的線進行遍歷*/ for (PvmTransition pvmTransition : pvmTransitions) { /*若是取出的線的目標節點存在時間相同的節點裏,保存該線的id,進行高亮顯示*/ ActivityImpl pvmActivityImpl = (ActivityImpl) pvmTransition.getDestination(); if (sameStartTimeNodes.contains(pvmActivityImpl)) { highFlows.add(pvmTransition.getId()); } } } return highFlows; }
注意:
1)流程圖中可能須要處理${currName}相似的變量,代碼中已有寫出,但這些變量替換爲真實數據的前提是環節已辦理,由於已辦理纔會記錄Variables,未辦理的環節須要將這些變量替換爲空字符串,這是一些細節處理。
2) HistoricProcessInstance 獲取歷史數據的前提是:須要配置歷史數據的記錄級別,與此同時,在配置中也能夠處理流程圖中文字的亂碼。
<!-- ProcessEngineConfiguration ProcessEngineConfiguration:用於建立ProcessEngine --> <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"> <!-- 數據源 --> <property name="dataSource" ref="dataSource" /> <!-- activiti數據庫表處理策略 --> <property name="databaseSchemaUpdate" value="true"/> <!-- Activiti默認提供4種歷史級別: 一、none: 不保存任何歷史記錄,能夠提升系統的性能; 二、activity:保存全部的流程實例、任務、活動信息; 三、audit:也是Activiti的默認級別,保存全部的流程實例、任務、活動、表單屬性; 四、full:最完整的歷史記錄,除了包含Audit級別的信息以外還能保存詳細信息,例如:流程變量。 --> <!-- 歷史數據記錄級別 --> <property name="history" value="full"/> <!-- 中文亂碼問題 --> <property name="activityFontName" value="宋體"/> <property name="labelFontName" value="宋體"/> </bean>
流程圖效果:
附:完善後的流程圖貼出來啦:
注意:須要流程圖展現得美觀,畫流程圖的時候就得注意美觀。
說得這裏就完了嗎?沒有,流程數據的展現還沒說呢。
(4)加載流程數據:
流程數據爲何能和流程圖保持一致呢?由於頁面傳回的businessKey是同一個嘛。這種低級的問題我都很差意回答。
切入正題:從流程圖中咱們能夠看出,紅色標註能夠追蹤到一系列環節,也就意味着這一系列環節的數據有章可循。恰好,有個叫HistoricActivityInstance的對象。
/*查詢流程歷史記錄*/ List<HistoricActivityInstance> history = historyService.createHistoricActivityInstanceQuery() /*過濾條件*/ .processInstanceId(processId) /*執行查詢*/ .list();
而後呢?把這個history扔個前端去循環遍歷,有什麼屬性本身翻翻,至於如何發請求拿到後臺的數據,呵呵,差一點又回答低級問題。
注意:
(1)須要區別businessKey和processId。HistoricProcessInstance.getId()就是processId了,但他沒有getProcessId()方法哦。
(2)怎麼樣知道這些「未知」的對象中都有哪些變量或者方法呢?對象之間又怎麼聯繫呢?答案是反覆試驗幾遍,試驗的方法就是傳說的中的「斷點」。
總結:
爲何要總結?經歷的時候只是開闊眼界,總結和反思才能開始成長。
有人會問,爲何不在流程圖上顯示更多信息呢?好比鼠標通過時,顯示時間,處理人等等。我想說,試了你就知道了。還有人會問,流程圖好醜,爲何不用vml或者svg依賴數據畫圖呢?我想說,你不光有錢,還有勢,還有時間,還有精力,說不定還臉大。
設計和代碼一樣重要,缺一不可。
若有問題,隨時聯繫我,網名即QQ。聯繫不到我,就說明文章沒看完。