师傅:上面我们已经跑通了整个流程了,但是对于用户而言,是不是很想知道这个流程都是怎么流转的,都需要什么人进行审批,目前到哪个环节了。
悟纤:这个需求应该很有必要呐,不然用户对于无期望的等待会很难受呢。
师傅:你等妹子也会很难受吗?
悟纤:师傅你好坏也。
师傅:好了,不逗你了,咱们赶紧进入正题吧。
前言
这一节我们要讲一下流程跟踪与流程图展示,怎么是流程跟踪,你还不知道嘛,不可能也,我们之前有一张漂亮的图片,你没有细看吧。
很好看吧?矣矣,放错了,是这张:
我们在使用activiti-app的时候,在流程的过程中有一个按钮【Show diagram】查看流程当前流转状态。
那么在项目路中怎么展示这个流程图呐?整个思路也是很简单的。
一、流程跟踪与流程图展示
1.1 说明
这个我们是在前面的文章
《「Activiti精品悟纤出品」开个一个简单的SpringBoot activiti应用》
接着往上进行添加代码的,所以如果你没有,你懂得….
大致的思路就是:
(1)获取流程引擎
(2)获取流程引擎配置实现类
(3)通过流程引擎配置实现类获取图片默认生成器ProcessDiagramGenerator
(4)获取流程模型BpmnModel
(5)创建需要高亮展示的节点的集合,元素为节点ID值
(6)调用ProcessDiagramGenerator的generateDiagram获取生成图片的输入流
(7)客户端展示
1.2 添加依赖
添加Activiti生成流程图的依赖:
<!-- Activiti生成流程图 --><dependency> <groupId>org.activiti</groupId> <artifactId>activiti-image-generator</artifactId> <version>7.1.0.M6</version></dependency>
1.3 Activiti工作流工具类
我们新建一个Activiti工作流工具类,我们先看下源码:
package com.kfit.springbootactiviti7demo.controller;
import java.util.ArrayList;import java.util.List;import org.activiti.bpmn.model.BpmnModel;import org.activiti.bpmn.model.FlowNode;import org.activiti.bpmn.model.SequenceFlow;import org.activiti.engine.history.HistoricActivityInstance;import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
/** * <p>Activiti工作流工具类</p> * @author 悟纤【公众号SpringBoot】 */public class ActivitiUtils {
/** * <p>获取流程走过的线</p> * @param bpmnModel 流程对象模型 * @param processDefinitionEntity 流程定义对象 * @param historicActivityInstances 历史流程已经执行的节点,并已经按执行的先后顺序排序 * @return List<String> 流程走过的线 * @author 悟纤【公众号SpringBoot】 */ public static List<String> getHighLightedFlows(BpmnModel bpmnModel, ProcessDefinitionEntity processDefinitionEntity, List<HistoricActivityInstance> historicActivityInstances) { // 用以保存高亮的线flowId List<String> highFlows = new ArrayList<String>(); if(historicActivityInstances == null || historicActivityInstances.size() == 0) return highFlows;
// 遍历历史节点 for (int i = 0; i < historicActivityInstances.size() - 1; i++) { // 取出已执行的节点 HistoricActivityInstance activityImpl_ = historicActivityInstances.get(i);
// 用以保存后续开始时间相同的节点 List<FlowNode> sameStartTimeNodes = new ArrayList<FlowNode>();
// 获取下一个节点(用于连线) FlowNode sameActivityImpl = getNextFlowNode(bpmnModel, historicActivityInstances, i, activityImpl_);// FlowNode sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i + 1).getActivityId()); // 将后面第一个节点放在时间相同节点的集合里 if(sameActivityImpl != null) sameStartTimeNodes.add(sameActivityImpl); // 循环后面节点,看是否有与此后继节点开始时间相同的节点,有则添加到后继节点集合 for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) { HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 后续第一个节点 HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 后续第二个节点 if (activityImpl1.getStartTime().getTime() != activityImpl2.getStartTime().getTime()) break; // 如果第一个节点和第二个节点开始时间相同保存 FlowNode sameActivityImpl2 = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityImpl2.getActivityId()); sameStartTimeNodes.add(sameActivityImpl2); } // 得到节点定义的详细信息 FlowNode activityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i).getActivityId()); // 取出节点的所有出去的线,对所有的线进行遍历 List<SequenceFlow> pvmTransitions = activityImpl.getOutgoingFlows(); for (SequenceFlow pvmTransition : pvmTransitions) { // 获取节点 FlowNode pvmActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(pvmTransition.getTargetRef()); // 不是后继节点 if(!sameStartTimeNodes.contains(pvmActivityImpl)) continue; // 如果取出的线的目标节点存在时间相同的节点里,保存该线的id,进行高亮显示 highFlows.add(pvmTransition.getId()); } } //返回高亮的线 return highFlows; }
/** * <p>获取下一个节点信息</p> * @param bpmnModel 流程模型 * @param historicActivityInstances 历史节点 * @param i 当前已经遍历到的历史节点索引(找下一个节点从此节点后) * @param activityImpl_ 当前遍历到的历史节点实例 * @return FlowNode 下一个节点信息 * @author 悟纤【公众号SpringBoot】 */ private static FlowNode getNextFlowNode(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances, int i, HistoricActivityInstance activityImpl_) { // 保存后一个节点 FlowNode sameActivityImpl = null; // 如果当前节点不是用户任务节点,则取排序的下一个节点为后续节点 if(!"userTask".equals(activityImpl_.getActivityType())) { // 是最后一个节点,没有下一个节点 if(i == historicActivityInstances.size()) return sameActivityImpl; // 不是最后一个节点,取下一个节点为后继节点 sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i + 1).getActivityId());// 找到紧跟在后面的一个节点 // 返回 return sameActivityImpl; } // 遍历后续节点,获取当前节点后续节点 for (int k = i + 1; k <= historicActivityInstances.size() - 1; k++) { // 后续节点 HistoricActivityInstance activityImp2_ = historicActivityInstances.get(k); // 都是userTask,且主节点与后续节点的开始时间相同,说明不是真实的后继节点 if("userTask".equals(activityImp2_.getActivityType()) && activityImpl_.getStartTime().getTime() == activityImp2_.getStartTime().getTime()) continue; // 找到紧跟在后面的一个节点 sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(k).getActivityId()); break; } return sameActivityImpl; }}
说明:
这里主要是获取流程走过的节点,代码中已经添加了很详细的注释,这里不过多进行解释。
1.4 输出图像
在LeaveController中进行编写输出图像的方法,主要使用HttpServletResponse输出流,这个图片流是通过activiti的processDiagramGenerator进行构建的,具体的源码如下:
/** * <p>输出图像</p> * @param response 响应实体* @param bpmnModel 图像对象* @param flowIds 已执行的线集合* @param executedActivityIdList void 已执行的节点ID集合* @author 悟纤【公众号SpringBoot】 */ private void outputImg(HttpServletResponse response, BpmnModel bpmnModel, List<String> flowIds, List<String> executedActivityIdList) { InputStream imageStream = null; ProcessDiagramGenerator processDiagramGenerator = new DefaultProcessDiagramGenerator(); try { imageStream = processDiagramGenerator.generateDiagram(bpmnModel, executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", true, "png"); // 输出资源内容到相应对象 byte[] b = new byte[1024]; int len; while ((len = imageStream.read(b, 0, 1024)) != -1) { response.getOutputStream().write(b, 0, len); } response.getOutputStream().flush(); }catch(Exception e) { e.printStackTrace(); } finally { // 流关闭 if(imageStream != null){ try { imageStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
1.5 查看当前流程图
好了,到这里就差不多了,最后在LeaveController编写一个可以访问的链接即可,当然在这里也有一些代码需要编写,如下源码如下:
添加Activiti的RepositoryService,用于获取流程模型:
//org.activiti.engine.impl.RepositoryServiceImpl@Autowiredprivate RepositoryService repositoryService;
具体的源码如下:
/** * <p>查看当前流程图</p> * @param instanceId 流程实例 * @param response void 响应 * @author 悟纤【公众号SpringBoot】 */@ResponseBody@RequestMapping(value="/showImg")public void showImg(String instanceId, HttpServletResponse response) { /* * 参数校验 */ System.out.println("查看完整流程图!流程实例ID:"+instanceId); if(StringUtils.isBlank(instanceId)) return;
/* * 获取流程实例 */ HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).singleResult(); if(processInstance == null) { System.out.println("流程实例ID:"+instanceId+"没查询到流程实例!"); return; }
// 根据流程对象获取流程对象模型 BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
/* * 查看已执行的节点集合 * 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序 */ // 构造历史流程查询 HistoricActivityInstanceQuery historyInstanceQuery = historyService.createHistoricActivityInstanceQuery().processInstanceId(instanceId); // 查询历史节点 List<HistoricActivityInstance> historicActivityInstanceList = historyInstanceQuery.orderByHistoricActivityInstanceStartTime().asc().list(); if(historicActivityInstanceList == null || historicActivityInstanceList.size() == 0) { System.out.println("流程实例ID:"+instanceId+"没有历史节点信息!"); outputImg(response, bpmnModel, null, null); return; } // 已执行的节点ID集合(将historicActivityInstanceList中元素的activityId字段取出封装到executedActivityIdList) List<String> executedActivityIdList = historicActivityInstanceList.stream().map(item -> item.getActivityId()).collect(Collectors.toList());
/* * 获取流程走过的线 */ // 获取流程定义 ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition(processInstance.getProcessDefinitionId()); List<String> flowIds = ActivitiUtils.getHighLightedFlows(bpmnModel, processDefinition, historicActivityInstanceList);
/* * 输出图像,并设置高亮 */ outputImg(response, bpmnModel, flowIds, executedActivityIdList);}
1.6 测试
好了来测试下,访问地址:
http://127.0.0.1:8080/leave/showImg?instanceId=9e74a46a-eb65-11ea-a6ae-f27b0fd3aed5
这里有一个实例ID,这个id是我们在访问
http://127.0.0.1:8080/leave/start?jobNumber=A1001
在浏览器端看到的示例id,最终的结果是:
红色代表已经是到结束的节点或者正在进行中的节点。
二、悟纤小结
师傅:好了,到这里就实现了,至于能够能够改变流程图的颜色呐,是可以的,需要重写底层一些东西,这个徒儿你自己实现吧,不然都给你搞定了,你以后自己都不会搞了。
悟纤:师傅,你这是要考验我的解决问题的能力吧。
师傅:师傅领进门,修行靠自身。
悟纤:好的,我加油搞。
师傅:嗯,那你给大家总结下这节的内容吧。
悟纤:乐意至极。
(1)流程图跟踪和流程图展示的核心就是使用activiti提供的ProcessDiagramGenerator进行生成图片,对于数据的话,需要通过获取activiti的数据节点进行绘制。
我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。
à悟空学院:https://t.cn/Rg3fKJD
SpringBoot视频:http://t.cn/A6ZagYTi
Spring Cloud视频:http://t.cn/A6ZagxSR
SpringBoot Shiro视频:http://t.cn/A6Zag7IV
SpringBoot交流平台:https://t.cn/R3QDhU0
SpringData和JPA视频:http://t.cn/A6Zad1OH
SpringSecurity5.0视频:http://t.cn/A6ZadMBe
ShardingJDBC分库分表:http://t.cn/A6ZarrqS
分布式事务解决方案:http://t.cn/A6ZaBnIr
JVM内存模型调优实战:http://t.cn/A6wWMVqG
Spring入门到精通:https://t.cn/A6bFcDh4