activiti7.1.0.M6流程图预览和进度高亮查看

发布时间 2023-12-07 14:41:00作者: 夏目co

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>
View Code

参数移除了imageType、customClassLoader,生成的文件格式为svg,在响应给客户端流程图的时候,可以设置响应类型

response.setContentType("image/svg+xml");
IOUtils.copy(is, response.getOutputStream());
或者把svg转换为png
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 }
View Code
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);
    }

}
View Code
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 }
View Code

查看流程图代码:

        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 }
View Code

查看流程进度代码:

 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()));
View Code

效果:

源码仓库地址:

https://gitee.com/cmmplb/spring-boot-activiti