实现高亮显示流程图节点和连线,大致效果如下:
样子不怎么美观,但基本功能已经实现了。整体代码如下:
/**
* 根据流程实例Id,获取实时流程图片
*
* @param processInstanceId
* @param response
* @return
*/
@GetMapping("/viewProcessImage")
public void viewProcessImage(String processInstanceId, HttpServletResponse response) {
try {
if (StringUtils.isEmpty(processInstanceId)) {
System.out.println("processInstanceId is null");
return;
}
// 获取历史流程实例
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
// 获取流程中已经执行的节点,按照执行先后顺序排序
List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceId().asc().list();
// 高亮已经执行流程节点ID集合
List<String> highLightedActivitiIds = new ArrayList<>();
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
highLightedActivitiIds.add(historicActivityInstance.getActivityId());
}
BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
// 高亮流程已发生流转的线id集合
List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances);
// 使用默认配置获得流程图表生成器,并生成追踪图片字符流
ProcessDiagramGenerator processDiagramGenerator = processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
InputStream imageStream = processDiagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitiIds, highLightedFlowIds, "宋体", "微软雅黑", "黑体", null, 2.0);
OutputStream outputStream = response.getOutputStream();
// 输出图片内容
byte[] b = new byte[1024];
int len;
while ((len = imageStream.read(b, 0, 1024)) != -1) {
outputStream.write(b, 0, len);
}
} catch (Exception e) {
System.out.println("processInstanceId" + processInstanceId + "生成流程图失败,原因:" + e.getMessage());
}
}
从上述代码也可以发现,实现高亮显示流程图中已流转的节点和线,大体分为以下几个步骤:
- 查询出已经流转的节点ID集合highLightedActivitiIds
- 查询出已经流转的连线ID集合highLightedFlowIds
- 利用activiti中的ProcessDiagramGenerator对象的generateDiagram方法生成需要的图片
这里面的难点在于连线的获取,获取已经流转的连线的方法:
/**
* 获取已经流转的线
*
* @param bpmnModel
* @param historicActivityInstances
* @return
*/
private List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
// 高亮流程已发生流转的线id集合
List<String> highLightedFlowIds = new ArrayList<>();
// 全部活动节点
List<FlowNode> historicActivityNodes = new ArrayList<>();
// 已完成的历史活动节点
List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
historicActivityNodes.add(flowNode);
// 结束时间不为空,则是已完成节点
if (historicActivityInstance.getEndTime() != null) {
finishedActivityInstances.add(historicActivityInstance);
}
}
FlowNode currentFlowNode;
FlowNode targetFlowNode;
// 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
// 获得当前活动对应的节点信息及outgoingFlows信息
currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();
/**
* 遍历outgoingFlows并找到已流转的 满足如下条件认为已已流转:
* 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转
* 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
*/
if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
// 遍历历史活动节点,找到匹配流程目标节点的
for (SequenceFlow sequenceFlow : sequenceFlows) {
targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
if (historicActivityNodes.contains(targetFlowNode)) {
highLightedFlowIds.add(targetFlowNode.getId());
}
}
} else {
List<Map<String, Object>> tempMapList = new ArrayList<>();
for (SequenceFlow sequenceFlow : sequenceFlows) {
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
Map<String, Object> map = new HashMap<>();
map.put("highLightedFlowId", sequenceFlow.getId());
map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
tempMapList.add(map);
}
}
}
if (!CollectionUtils.isEmpty(tempMapList)) {
// 遍历匹配的集合,取得开始时间最早的一个
long earliestStamp = 0L;
String highLightedFlowId = null;
for (Map<String, Object> map : tempMapList) {
long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {
highLightedFlowId = map.get("highLightedFlowId").toString();
earliestStamp = highLightedFlowStartTime;
}
}
highLightedFlowIds.add(highLightedFlowId);
}
}
}
return highLightedFlowIds;
}
需要高亮显示的连线,连线的两端的节点,至少有一个是已完成的节点,否则不应该高亮显示该连线。但其实,高亮显示连线是存在问题的,因为对于有退回操作并且有多条分支的流程图而言,而每次走的路线又不一样的时候,应该高亮显示第一次走的线,还是最后一次走的线,还是两者都高亮显示?目前是取最早经过的那条连线。而对于排他性网关之后的节点也是同样的道理,应该高亮显示哪个节点?