activiti7移除了静态方法创建ProcessDiagramGenerator,需要创建DefaultProcessDiagramGenerator实例
依赖:

<properties> <batik-transcoder.version>1.17</batik-transcoder.version> <batik-codec.version>1.17</batik-codec.version> <activiti-json-converter.version>7.1.0.M6</activiti-json-converter.version> <activiti-spring-boot-starter.version>7.1.0.M6</activiti-spring-boot-starter.version> </properties> <dependencies> <!-- 流程图设计 --> <dependency> <groupId>org.apache.xmlgraphics</groupId> <artifactId>batik-transcoder</artifactId> <version>${batik-transcoder.version}</version> </dependency> <dependency> <groupId>org.apache.xmlgraphics</groupId> <artifactId>batik-codec</artifactId> <version>${batik-codec.version}</version> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-json-converter</artifactId> <version>${activiti-json-converter.version}</version> </dependency> <!-- activiti --> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>${activiti-spring-boot-starter.version}</version> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-image-generator</artifactId> <version>${activiti-spring-boot-starter.version}</version> </dependency> </dependencies>
参数移除了imageType、customClassLoader,生成的文件格式为svg,在响应给客户端流程图的时候,可以设置响应类型
response.setContentType("image/svg+xml");
IOUtils.copy(is, response.getOutputStream());
new PNGTranscoder().transcode(new TranscoderInput(is), new TranscoderOutput(response.getOutputStream()));
is为生成的文件流。
具体实现代码:
ProcessDiagramGenerator

1 import org.activiti.bpmn.model.BpmnModel; 2 3 import java.awt.*; 4 import java.io.InputStream; 5 import java.util.List; 6 import java.util.Set; 7 8 /** 9 * @author penglibo 10 * @date 2022-11-24 09:11:02 11 * @since jdk 1.8 12 */ 13 14 public interface ProcessDiagramGenerator extends org.activiti.image.ProcessDiagramGenerator { 15 16 /** 17 * 生成流程图 18 * @param bpmnModel 模型 19 * @param highLightedActivities 高亮已经执行流程节点ID集合 20 * @param highLightedFlows 高亮流程已发生流转的线id集合 21 * @param activityFontName 22 * @param labelFontName 23 * @param annotationFontName 24 * @param colors 流程图颜色定义,这里固定写死的,[0]new Color(0, 205, 0)-绿色-已经运行后的流程;[1]new Color(255, 0, 0)-红色-当前正在执行的流程; 25 * @param activityIds 当前激活的节点 26 * @return 27 */ 28 InputStream generateDiagram(BpmnModel bpmnModel, 29 List<String> highLightedActivities, 30 List<String> highLightedFlows, 31 String activityFontName, 32 String labelFontName, 33 String annotationFontName, 34 Color[] colors, 35 Set<String> activityIds); 36 }
ProcessDiagramGeneratorImpl

import org.activiti.bpmn.model.Process; import org.activiti.bpmn.model.*; import org.activiti.image.impl.DefaultProcessDiagramCanvas; import org.activiti.image.impl.DefaultProcessDiagramGenerator; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.awt.*; import java.io.InputStream; import java.util.Collections; import java.util.List; import java.util.Set; /** * @author penglibo * @date 2023-11-24 10:02:33 * @since jdk 1.8 */ public class ProcessDiagramGeneratorImpl extends DefaultProcessDiagramGenerator implements ProcessDiagramGenerator { /** * {@link DefaultProcessDiagramGenerator#generateProcessDiagram(BpmnModel, List, List, String, String, String)} */ public ProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, List<String> highLightedActivities, List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName, Color[] colors, Set<String> currIds) { if (null == highLightedActivities) { highLightedActivities = Collections.emptyList(); } if (null == highLightedFlows) { highLightedFlows = Collections.emptyList(); } prepareBpmnModel(bpmnModel); ProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, activityFontName, labelFontName, annotationFontName); // Draw pool shape, if process is participant in collaboration for (Pool pool : bpmnModel.getPools()) { GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId()); processDiagramCanvas.drawPoolOrLane(pool.getId(), pool.getName(), graphicInfo); } // Draw lanes for (Process process : bpmnModel.getProcesses()) { for (Lane lane : process.getLanes()) { GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(lane.getId()); processDiagramCanvas.drawPoolOrLane(lane.getId(), lane.getName(), graphicInfo); } } // 绘制活动及其序列流,这里添加了colors, currIds for (Process process : bpmnModel.getProcesses()) { List<FlowNode> flowNodeList = process.findFlowElementsOfType(FlowNode.class); for (FlowNode flowNode : flowNodeList) { drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedActivities, highLightedFlows, colors, currIds); } } // Draw artifacts for (Process process : bpmnModel.getProcesses()) { for (Artifact artifact : process.getArtifacts()) { drawArtifact(processDiagramCanvas, bpmnModel, artifact); } List<SubProcess> subProcesses = process.findFlowElementsOfType(SubProcess.class, true); if (subProcesses != null) { for (SubProcess subProcess : subProcesses) { for (Artifact subProcessArtifact : subProcess.getArtifacts()) { drawArtifact(processDiagramCanvas, bpmnModel, subProcessArtifact); } } } } return processDiagramCanvas; } /** * {@link DefaultProcessDiagramGenerator#drawActivity(DefaultProcessDiagramCanvas, BpmnModel, FlowNode, List, List)} */ protected void drawActivity(ProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode, List<String> highLightedActivities, List<String> highLightedFlows, Color[] colors, Set<String> currIds) { ActivityDrawInstruction drawInstruction = activityDrawInstructions.get(flowNode.getClass()); if (drawInstruction != null) { drawInstruction.draw(processDiagramCanvas, bpmnModel, flowNode); // Gather info on the multi instance marker boolean multiInstanceSequential = false, multiInstanceParallel = false, collapsed = false; if (flowNode instanceof Activity) { Activity activity = (Activity) flowNode; MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = activity.getLoopCharacteristics(); if (multiInstanceLoopCharacteristics != null) { multiInstanceSequential = multiInstanceLoopCharacteristics.isSequential(); multiInstanceParallel = !multiInstanceSequential; } } // Gather info on the collapsed marker GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); if (flowNode instanceof SubProcess) { collapsed = graphicInfo.getExpanded() != null && !graphicInfo.getExpanded(); } else if (flowNode instanceof CallActivity) { collapsed = true; } // if (scaleFactor == 1.0) { // Actually draw the markers processDiagramCanvas.drawActivityMarkers((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(), multiInstanceSequential, multiInstanceParallel, collapsed); // } // Draw highlighted activities // historicActivityInstance里取得的list中,最后一个节点就是当前节点,前面的节点都是已完成的节点 if (highLightedActivities.contains(flowNode.getId())) { if (!CollectionUtils.isEmpty(currIds) && currIds.contains(flowNode.getId()) && !(flowNode instanceof Gateway)) { // 非结束节点,并且是当前节点 drawHighLight((flowNode instanceof StartEvent), processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()), colors[1]); } else { // 普通节点 drawHighLight((flowNode instanceof StartEvent) || (flowNode instanceof EndEvent), processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()), colors[0]); } } } // Outgoing transitions of activity for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) { String flowId = sequenceFlow.getId(); boolean highLighted = (highLightedFlows.contains(flowId)); String defaultFlow = null; if (flowNode instanceof Activity) { defaultFlow = ((Activity) flowNode).getDefaultFlow(); } else if (flowNode instanceof Gateway) { defaultFlow = ((Gateway) flowNode).getDefaultFlow(); } boolean isDefault = false; if (defaultFlow != null && defaultFlow.equalsIgnoreCase(flowId)) { isDefault = true; } boolean drawConditionalIndicator = sequenceFlow.getConditionExpression() != null && !(flowNode instanceof Gateway); String sourceRef = sequenceFlow.getSourceRef(); String targetRef = sequenceFlow.getTargetRef(); FlowElement sourceElement = bpmnModel.getFlowElement(sourceRef); FlowElement targetElement = bpmnModel.getFlowElement(targetRef); List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(flowId); if (graphicInfoList != null && graphicInfoList.size() > 0) { graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, targetElement, graphicInfoList); int[] xPoints = new int[graphicInfoList.size()]; int[] yPoints = new int[graphicInfoList.size()]; for (int i = 1; i < graphicInfoList.size(); i++) { GraphicInfo graphicInfo = graphicInfoList.get(i); GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1); if (i == 1) { xPoints[0] = (int) previousGraphicInfo.getX(); yPoints[0] = (int) previousGraphicInfo.getY(); } xPoints[i] = (int) graphicInfo.getX(); yPoints[i] = (int) graphicInfo.getY(); } // 画高亮线 // processDiagramCanvas.drawSequenceflow(xPoints, yPoints, false, isDefault, highLighted, colors[0]); processDiagramCanvas.drawSequenceflow(xPoints, yPoints, drawConditionalIndicator, isDefault, highLighted, colors[0]); // 绘制序列流标签 GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(flowId); if (labelGraphicInfo != null) { processDiagramCanvas.drawLabel(sequenceFlow.getName(), labelGraphicInfo, false); } else { // 解决流程图连线名称不显示的BUG GraphicInfo lineCenter = getLineCenter(graphicInfoList); processDiagramCanvas.drawLabel(highLighted, sequenceFlow.getName(), lineCenter, Math.abs(xPoints[1] - xPoints[0]) >= 5); } } } // Nested elements if (flowNode instanceof FlowElementsContainer) { for (FlowElement nestedFlowElement : ((FlowElementsContainer) flowNode).getFlowElements()) { if (nestedFlowElement instanceof FlowNode) { drawActivity(processDiagramCanvas, bpmnModel, (FlowNode) nestedFlowElement, highLightedActivities, highLightedFlows); } } } } protected void drawHighLight(boolean isStartOrEnd, ProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo, Color color) { processDiagramCanvas.drawHighLight(isStartOrEnd, (int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(), color); } /** * 拷贝DefaultProcessDiagramCanvas方法 * @return {@link org.activiti.image.impl.DefaultProcessDiagramGenerator#initProcessDiagramCanvas */ protected static ProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String activityFontName, String labelFontName, String annotationFontName) { // 我们需要计算最大值,以了解图像整体的大小 double minX = Double.MAX_VALUE; double maxX = 0; double minY = Double.MAX_VALUE; double maxY = 0; for (Pool pool : bpmnModel.getPools()) { GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId()); minX = graphicInfo.getX(); maxX = graphicInfo.getX() + graphicInfo.getWidth(); minY = graphicInfo.getY(); maxY = graphicInfo.getY() + graphicInfo.getHeight(); } List<FlowNode> flowNodes = gatherAllFlowNodes(bpmnModel); for (FlowNode flowNode : flowNodes) { GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); // width if (flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth() > maxX) { maxX = flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth(); } if (flowNodeGraphicInfo.getX() < minX) { minX = flowNodeGraphicInfo.getX(); } // height if (flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight() > maxY) { maxY = flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight(); } if (flowNodeGraphicInfo.getY() < minY) { minY = flowNodeGraphicInfo.getY(); } for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) { List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId()); if (graphicInfoList != null) { for (GraphicInfo graphicInfo : graphicInfoList) { // width if (graphicInfo.getX() > maxX) { maxX = graphicInfo.getX(); } if (graphicInfo.getX() < minX) { minX = graphicInfo.getX(); } // height if (graphicInfo.getY() > maxY) { maxY = graphicInfo.getY(); } if (graphicInfo.getY() < minY) { minY = graphicInfo.getY(); } } } } } List<Artifact> artifacts = gatherAllArtifacts(bpmnModel); for (Artifact artifact : artifacts) { GraphicInfo artifactGraphicInfo = bpmnModel.getGraphicInfo(artifact.getId()); if (artifactGraphicInfo != null) { // width if (artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth() > maxX) { maxX = artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth(); } if (artifactGraphicInfo.getX() < minX) { minX = artifactGraphicInfo.getX(); } // height if (artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight() > maxY) { maxY = artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight(); } if (artifactGraphicInfo.getY() < minY) { minY = artifactGraphicInfo.getY(); } } List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId()); if (graphicInfoList != null) { for (GraphicInfo graphicInfo : graphicInfoList) { // width if (graphicInfo.getX() > maxX) { maxX = graphicInfo.getX(); } if (graphicInfo.getX() < minX) { minX = graphicInfo.getX(); } // height if (graphicInfo.getY() > maxY) { maxY = graphicInfo.getY(); } if (graphicInfo.getY() < minY) { minY = graphicInfo.getY(); } } } } int nrOfLanes = 0; for (Process process : bpmnModel.getProcesses()) { for (Lane l : process.getLanes()) { nrOfLanes++; GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(l.getId()); // // width if (graphicInfo.getX() + graphicInfo.getWidth() > maxX) { maxX = graphicInfo.getX() + graphicInfo.getWidth(); } if (graphicInfo.getX() < minX) { minX = graphicInfo.getX(); } // height if (graphicInfo.getY() + graphicInfo.getHeight() > maxY) { maxY = graphicInfo.getY() + graphicInfo.getHeight(); } if (graphicInfo.getY() < minY) { minY = graphicInfo.getY(); } } } // Special case, see https://activiti.atlassian.net/browse/ACT-1431 if (flowNodes.isEmpty() && bpmnModel.getPools().isEmpty() && nrOfLanes == 0) { // Nothing to show minX = 0; minY = 0; } return new ProcessDiagramCanvas( (int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY, activityFontName, labelFontName, annotationFontName); } @Override public InputStream generateDiagram(BpmnModel bpmnModel, List<String> highLightedActivities, List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName, Color[] colors, Set<String> activityIds) { ProcessDiagramCanvas processDiagramCanvas = generateProcessDiagram(bpmnModel, highLightedActivities, highLightedFlows, activityFontName, labelFontName, annotationFontName, colors, activityIds); return processDiagramCanvas.generateImage(); } @Override public InputStream generateDiagram(BpmnModel bpmnModel, String activityFontName, String labelFontName, String annotationFontName) { return generateDiagram(bpmnModel, Collections.<String>emptyList(), Collections.<String>emptyList(), activityFontName, labelFontName, annotationFontName, new Color[]{Color.BLACK, Color.BLACK}, null); } }
ProcessDiagramCanvas

1 import org.activiti.bpmn.model.AssociationDirection; 2 import org.activiti.bpmn.model.GraphicInfo; 3 import org.activiti.image.impl.DefaultProcessDiagramCanvas; 4 5 import java.awt.*; 6 import java.awt.font.FontRenderContext; 7 import java.awt.font.LineBreakMeasurer; 8 import java.awt.font.TextAttribute; 9 import java.awt.font.TextLayout; 10 import java.awt.geom.Line2D; 11 import java.awt.geom.Rectangle2D; 12 import java.awt.geom.RoundRectangle2D; 13 import java.text.AttributedCharacterIterator; 14 import java.text.AttributedString; 15 16 /** 17 * @author penglibo 18 * @date 2023-11-24 10:35:13 19 * @since jdk 1.8 20 */ 21 22 public class ProcessDiagramCanvas extends DefaultProcessDiagramCanvas { 23 24 protected static Color LABEL_COLOR = new Color(0, 0, 0); 25 26 /** 动态流程图颜色定义 **/ 27 public static final Color COLOR_NORMAL = new Color(0, 205, 0); 28 29 public static final Color COLOR_CURRENT = new Color(255, 0, 0); 30 31 //font 32 protected String labelFontName = "宋体"; 33 34 /** 35 * 移除了String imageType和ClassLoader customClassLoader 36 */ 37 public ProcessDiagramCanvas(int width, int height, int minX, int minY, String activityFontName, String labelFontName, String annotationFontName) { 38 super(width, height, minX, minY, activityFontName, labelFontName, annotationFontName); 39 } 40 41 /** 42 * {@link DefaultProcessDiagramCanvas#drawHighLight(int, int, int, int)} 43 */ 44 public void drawHighLight(boolean isStartOrEnd, 45 int x, 46 int y, 47 int width, 48 int height, 49 Color color) { 50 Paint originalPaint = g.getPaint(); 51 Stroke originalStroke = g.getStroke(); 52 53 // 这里是高亮的颜色 54 g.setPaint(color); 55 g.setStroke(MULTI_INSTANCE_STROKE); 56 if (isStartOrEnd) { 57 // 开始、结束节点画圆 58 g.drawOval(x, y, width, height); 59 } else { 60 // 非开始、结束节点画圆角矩形 61 RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 5, 5); 62 g.draw(rect); 63 } 64 g.setPaint(originalPaint); 65 g.setStroke(originalStroke); 66 } 67 68 /** 69 * {@link DefaultProcessDiagramCanvas#drawSequenceflow(int[], int[], boolean, boolean, boolean)} 70 */ 71 public void drawSequenceflow(int[] xPoints, 72 int[] yPoints, 73 boolean conditional, 74 boolean isDefault, 75 boolean highLighted, 76 Color color) { 77 drawConnection(xPoints, 78 yPoints, 79 conditional, 80 isDefault, 81 "sequenceFlow", 82 AssociationDirection.ONE, 83 highLighted, 84 color); 85 } 86 87 /** 88 * {@link DefaultProcessDiagramCanvas#drawConnection(int[], int[], boolean, boolean, String, AssociationDirection, boolean)} 89 */ 90 public void drawConnection(int[] xPoints, 91 int[] yPoints, 92 boolean conditional, 93 boolean isDefault, 94 String connectionType, 95 AssociationDirection associationDirection, 96 boolean highLighted, 97 Color color) { 98 99 Paint originalPaint = g.getPaint(); 100 Stroke originalStroke = g.getStroke(); 101 102 g.setPaint(CONNECTION_COLOR); 103 if ("association".equals(connectionType)) { 104 g.setStroke(ASSOCIATION_STROKE); 105 } else if (highLighted) { 106 // 设置高亮颜色 107 g.setPaint(color); 108 g.setStroke(HIGHLIGHT_FLOW_STROKE); 109 } 110 111 for (int i = 1; i < xPoints.length; i++) { 112 int sourceX = xPoints[i - 1]; 113 int sourceY = yPoints[i - 1]; 114 int targetX = xPoints[i]; 115 int targetY = yPoints[i]; 116 Line2D.Double line = new Line2D.Double(sourceX, sourceY, targetX, targetY); 117 g.draw(line); 118 } 119 120 if (isDefault) { 121 Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]); 122 drawDefaultSequenceFlowIndicator(line); 123 } 124 125 if (conditional) { 126 Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]); 127 drawConditionalSequenceFlowIndicator(line); 128 } 129 130 if (associationDirection.equals(AssociationDirection.ONE) 131 || associationDirection.equals(AssociationDirection.BOTH)) { 132 Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2], yPoints[xPoints.length - 2], 133 xPoints[xPoints.length - 1], yPoints[xPoints.length - 1]); 134 drawArrowHead(line); 135 } 136 if (associationDirection.equals(AssociationDirection.BOTH)) { 137 Line2D.Double line = new Line2D.Double(xPoints[1], yPoints[1], xPoints[0], yPoints[0]); 138 drawArrowHead(line); 139 } 140 g.setPaint(originalPaint); 141 g.setStroke(originalStroke); 142 } 143 144 /** 145 * {@link DefaultProcessDiagramCanvas#drawLabel(String, GraphicInfo, boolean)} 146 */ 147 public void drawLabel(boolean highLighted, String text, GraphicInfo graphicInfo, boolean centered) { 148 float interline = 1.0f; 149 150 // text 151 if (text != null && text.length() > 0) { 152 Paint originalPaint = g.getPaint(); 153 Font originalFont = g.getFont(); 154 if (highLighted) { 155 g.setPaint(COLOR_NORMAL); 156 } else { 157 g.setPaint(LABEL_COLOR); 158 } 159 g.setFont(new Font(labelFontName, Font.BOLD, 10)); 160 161 int wrapWidth = 100; 162 int textY = (int) graphicInfo.getY(); 163 164 AttributedString as = new AttributedString(text); 165 as.addAttribute(TextAttribute.FOREGROUND, g.getPaint()); 166 as.addAttribute(TextAttribute.FONT, g.getFont()); 167 AttributedCharacterIterator aci = as.getIterator(); 168 FontRenderContext frc = new FontRenderContext(null, true, false); 169 LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc); 170 171 while (lbm.getPosition() < text.length()) { 172 TextLayout tl = lbm.nextLayout(wrapWidth); 173 textY += tl.getAscent(); 174 Rectangle2D bb = tl.getBounds(); 175 double tX = graphicInfo.getX(); 176 if (centered) { 177 tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2); 178 } 179 tl.draw(g, (float) tX, textY); 180 textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent(); 181 } 182 183 // restore originals 184 g.setFont(originalFont); 185 g.setPaint(originalPaint); 186 } 187 } 188 }
查看流程图代码:
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); // activiti7移除了静态方法创建,需要DefaultProcessDiagramGenerator实例。 // ProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator(); // 由于是创建的新实例,这里的DiagramGenerator就不用注入到配置类里面了,当然ActivitiConfiguration配置类也移除了set的方法。 ProcessDiagramGeneratorImpl diagramGenerator = new ProcessDiagramGeneratorImpl(); InputStream is = diagramGenerator.generateDiagram(bpmnModel, "宋体", "宋体", "宋体"); try { HttpServletResponse response = ServletUtil.getResponse(); // 响应svg到客户端 // response.setContentType("image/svg+xml"); // IOUtils.copy(is, response.getOutputStream()); // 转换svg为png响应 response.setContentType("image/png"); new PNGTranscoder().transcode(new TranscoderInput(is), new TranscoderOutput(response.getOutputStream())); } catch (Exception e) { throw new RuntimeException(e); }
效果:
流程进度高亮
ActivitiUtil

1 import org.activiti.bpmn.model.BpmnModel; 2 import org.activiti.bpmn.model.FlowNode; 3 import org.activiti.bpmn.model.SequenceFlow; 4 import org.activiti.engine.history.HistoricActivityInstance; 5 import org.springframework.util.CollectionUtils; 6 7 import java.util.ArrayList; 8 import java.util.HashMap; 9 import java.util.List; 10 import java.util.Map; 11 12 /** 13 * @author penglibo 14 * @date 2023-11-24 17:29:08 15 * @since jdk 1.8 16 */ 17 public class ActivitiUtil { 18 19 /** 20 * 获取已经流转的线 21 * @param bpmnModel 流程图模板 22 * @param historicActivityInstances 历史实例 23 */ 24 public static List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) { 25 // 高亮流程已发生流转的线id集合 26 List<String> highLightedFlowIds = new ArrayList<>(); 27 // 全部活动节点 28 List<FlowNode> historicActivityNodes = new ArrayList<>(); 29 // 已完成的历史活动节点 30 List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>(); 31 32 for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) { 33 FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true); 34 historicActivityNodes.add(flowNode); 35 if (historicActivityInstance.getEndTime() != null) { 36 finishedActivityInstances.add(historicActivityInstance); 37 } 38 } 39 40 FlowNode currentFlowNode = null; 41 FlowNode targetFlowNode = null; 42 // 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的 43 for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) { 44 // 获得当前活动对应的节点信息及outgoingFlows信息 45 currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true); 46 List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows(); 47 48 /** 49 * 遍历outgoingFlows并找到已已流转的 满足如下条件认为已已流转: 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转 50 */ 51 if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) { 52 // 遍历历史活动节点,找到匹配流程目标节点的 53 for (SequenceFlow sequenceFlow : sequenceFlows) { 54 targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true); 55 if (historicActivityNodes.contains(targetFlowNode)) { 56 highLightedFlowIds.add(targetFlowNode.getId()); 57 } 58 } 59 } else { 60 List<Map<String, Object>> tempMapList = new ArrayList<>(); 61 for (SequenceFlow sequenceFlow : sequenceFlows) { 62 for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) { 63 if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) { 64 Map<String, Object> map = new HashMap<>(); 65 map.put("highLightedFlowId", sequenceFlow.getId()); 66 map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime()); 67 tempMapList.add(map); 68 } 69 } 70 } 71 72 if (!CollectionUtils.isEmpty(tempMapList)) { 73 // 遍历匹配的集合,取得开始时间最早的一个 74 long earliestStamp = 0L; 75 String highLightedFlowId = null; 76 for (Map<String, Object> map : tempMapList) { 77 long highLightedFlowStartTime = Long.parseLong(map.get("highLightedFlowStartTime").toString()); 78 if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) { 79 highLightedFlowId = map.get("highLightedFlowId").toString(); 80 earliestStamp = highLightedFlowStartTime; 81 } 82 } 83 84 highLightedFlowIds.add(highLightedFlowId); 85 } 86 87 } 88 89 } 90 return highLightedFlowIds; 91 } 92 }
查看流程进度代码:

1 // 定义businessKey,一般为流程实例key与实际业务数据的结合 2 String businessKey = apply.getDefKey() + ":" + apply.getId(); 3 // 如果流程结束(驳回),当前流程实例为空 4 ProcessInstance process = runtimeService.createProcessInstanceQuery() 5 .processDefinitionKey(apply.getDefKey()) 6 .processInstanceBusinessKey(businessKey) 7 .singleResult(); 8 9 // 获取历史流程实例来查询流程进度 10 HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult(); 11 if (null == processInstance) { 12 log.error("流程信息不存在"); 13 return; 14 } 15 // 获取流程中已经执行的节点,按照执行先后顺序排序 16 List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery() 17 // 这里,如果流程结束的话,process会为空,所以查询历史流程,这样也能看到结束的流程进度信息。 18 .processInstanceId(null == process ? processInstance.getId() : process.getId()) 19 .orderByHistoricActivityInstanceStartTime().asc().list(); 20 BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); 21 // highLightedActivities(需要高亮的执行流程节点集合的获取)以及 22 // highLightedFlows(需要高亮流程连接线集合的获取) 23 // 高亮流程已发生流转的线id集合 24 List<String> highLightedFlowIds = ActivitiUtil.getHighLightedFlows(bpmnModel, historicActivityInstances); 25 26 // 高亮已经执行流程节点ID集合 27 List<String> highLightedActivitiIds = historicActivityInstances.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toList()); 28 29 Set<String> activityIds = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).list() 30 .stream().map(org.activiti.engine.runtime.Execution::getActivityId).collect(Collectors.toSet()); 31 // activiti7移除了静态方法创建,需要DefaultProcessDiagramGenerator实例 32 // ProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator(); 33 // 由于是创建的新实例,这里的DiagramGenerator就不用注入到配置类里面了,当然ActivitiConfiguration配置类也移除了set的方法。 34 ProcessDiagramGeneratorImpl diagramGenerator = new ProcessDiagramGeneratorImpl(); 35 // 使用默认配置获得流程图表生成器,并生成追踪图片字符流 36 InputStream is = diagramGenerator.generateDiagram(bpmnModel, 37 highLightedActivitiIds, 38 highLightedFlowIds, 39 "宋体", 40 "微软雅黑", 41 "黑体", 42 new Color[]{ProcessDiagramCanvas.COLOR_NORMAL, ProcessDiagramCanvas.COLOR_CURRENT}, 43 activityIds 44 ); 45 46 HttpServletResponse response = ServletUtil.getResponse(); 47 48 // 响应svg到客户端 49 // response.setContentType("image/svg+xml"); 50 // IOUtils.copy(is, response.getOutputStream()); 51 52 // 转换svg为png响应 53 response.setContentType("image/png"); 54 new PNGTranscoder().transcode(new TranscoderInput(is), new TranscoderOutput(response.getOutputStream()));
效果:
bpmn-js
流程设计:
- 官网:https://bpmn.io/
- 仓库:https://github.com/bpmn-io/bpmn-js
- 仓库:https://github.com/bpmn-io/bpmn-js

流程进度高亮:

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗