设计思路:前端
(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。联系不到我,就说明文章没看完。