Activiti 生成流程图和已审批、待审批节点高亮

Activiti6.0 流程图和节点高亮

采用Java+Element + Activiti6.0

如图:
图中绿色为已经执行的节点,红色为当前正在执行的节点。
在这里插入图片描述
前端

<div class="approve_content" v-if="active==2">
        <div class="approve_div">
            <el-alert
                    size="large"
                    type="info"
                    effect="light"
                    :closable="false"
                    show-icon
            >
                        <span style="cursor: pointer" slot="title"
                              @click="imgClick('${request.contextPath}/approval/task/processImg/' +pid)">
                            审批流程图
                        </span>
                <span style="cursor: pointer"
                      @click="imgClick('${request.contextPath}/approval/task/processImg/' +pid)">
                            点击查看审批流程图
                        </span>
            </el-alert>
        </div>
 imgClick: function (config, title) {
            let defTitle = title || "查看流程";
            let that = this;
            let img = '<img src="' + config + '" style="max-width:900px" >'
            top.layer.open({
                area: ['900px', that.height],
                type: 1,
                offset: 'rb',
                anim: 7,
                shadeClose: false,
                closeAnim: "layer-anim-08",
                title: defTitle,
                content: img,
                maxmin: true,
                full: function (dom) {
                    $(dom).css("top", "60px");
                    let $content = $(dom).find("div.layui-layer-content");
                    $content.height($content.height() - 60);
                    $content.find("img").removeAttr("style");
                },
                restore: function (dom) {
                    let $content = $(dom).find("div.layui-layer-content");
                    $content.height($content.height() + 60);
                    $content.find("img").css("max-width", "900px");
                }
            });
        },

后端

 /**
     * 获取流程图片
     *
     * @param processId
     * @param response
     */
    @RequestMapping("processImg/{processId}")
    public void viewProcessImg(@PathVariable String processId, HttpServletResponse response) {
        try {
            byte[] processImage = approvalTaskService.getProcessImage(processId);
            OutputStream outputStream = response.getOutputStream();
            InputStream in = new ByteArrayInputStream(processImage);
            IOUtils.copy(in, outputStream);
        } catch (Exception e) {
            log.error("viewProcessImg---- {}", e);
        }

    }
 /**
     * 追踪流程图片
     *
     * @param processInstanceId
     * @return
     * @throws Exception
     */
    @Override
    public byte[] getProcessImage(String processInstanceId) throws Exception {
        // 获取历史流程实例
        HistoricProcessInstance historicProcessInstance =
                historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (historicProcessInstance == null) {
            throw new Exception();
        } else {
            // 获取流程定义
            ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService
                    .getProcessDefinition(historicProcessInstance.getProcessDefinitionId());

            // 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序
            List<HistoricActivityInstance> historicActivityInstanceList =
                    historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
                            .orderByHistoricActivityInstanceId().desc().list();
            // 已执行的节点ID集合
            List<String> executedActivityIdList = new ArrayList<>();
            /*@SuppressWarnings("unused")
            int index = 1;
            for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
                if (index == 1) {
                    executedActivityIdList.add(activityInstance.getActivityId() );
                } else {
                    executedActivityIdList.add(activityInstance.getActivityId());
                }
                index++;
            }*/
            for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
                if ("userTask".equals(activityInstance.getActivityType()) && StringUtils.isNull(activityInstance.getEndTime())) {
                    executedActivityIdList.add(activityInstance.getActivityId() + "#");
                } else {
                    executedActivityIdList.add(activityInstance.getActivityId());
                }
            }
            // 获取流程图图像字符流
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());

            // 已执行flow的集和
            List<String> executedFlowIdList = getHighLightedFlows(bpmnModel, historicActivityInstanceList);

            ProcessDiagramGenerator processDiagramGenerator =
                    processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
            InputStream imageStream = processDiagramGenerator.generateDiagram(bpmnModel, "png", executedActivityIdList,
                    executedFlowIdList, "SimHei", "SimHei", "SimHei", null, 1.0);

            byte[] buffer = new byte[imageStream.available()];
            imageStream.read(buffer);
            imageStream.close();
            return buffer;
        }
    }
 /**
     * 获取已经流转的线
     *
     * @param bpmnModel
     * @param historicActivityInstances
     * @return
     */
    private static 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 = null;
        FlowNode targetFlowNode = null;
        // 遍历已完成的活动实例,从每个实例的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;
    }

补充源码知识

/**
    * 使用流程的图表交换信息生成给定流程定义的图表。
    *
    * @param bpmnModel
    * bpmn 模型获取图表
    * @param imageType
    * 要生成的图像的类型。
    * @param highLightedActivity
    *活动突出
    * @param highLightedFlows
    * 流动以突出显示
    * @param activityFontName
    * 覆盖默认的活动字体
    * @param labelFontName
    * 覆盖默认标签字体
    * @param customClassLoader
    *提供用于检索图标图像的自定义类加载器
    */ 
public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, List<String> highLightedFlows, 
      String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor);
 
 
import org.activiti.bpmn.model.AssociationDirection;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.image.impl.DefaultProcessDiagramCanvas;
 
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
 
public class CFProcessDiagramCanvasExt extends DefaultProcessDiagramCanvas {
 
 
    //定义连线颜色为蓝色
    protected static Color HIGHLIGHT_SequenceFlow_COLOR = Color.green;
 
    public CFProcessDiagramCanvasExt(int width, int height, int minX, int minY, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
        super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
    }
 
    /**
     * 重写绘制连线的方式,设置绘制颜色
     */
    @Override
    public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType, AssociationDirection associationDirection, boolean highLighted, double scaleFactor) {
        Paint originalPaint = this.g.getPaint();
        Stroke originalStroke = this.g.getStroke();
        this.g.setPaint(CONNECTION_COLOR);
        if (connectionType.equals("association")) {
            this.g.setStroke(ASSOCIATION_STROKE);
        } else if (highLighted) {
            this.g.setPaint(HIGHLIGHT_SequenceFlow_COLOR);
            this.g.setStroke(HIGHLIGHT_FLOW_STROKE);
        }
 
        for (int i = 1; i < xPoints.length; ++i) {
            Integer sourceX = xPoints[i - 1];
            Integer sourceY = yPoints[i - 1];
            Integer targetX = xPoints[i];
            Integer targetY = yPoints[i];
            java.awt.geom.Line2D.Double line = new java.awt.geom.Line2D.Double((double) sourceX, (double) sourceY, (double) targetX, (double) targetY);
            this.g.draw(line);
        }
 
        java.awt.geom.Line2D.Double line;
        if (isDefault) {
            line = new java.awt.geom.Line2D.Double((double) xPoints[0], (double) yPoints[0], (double) xPoints[1], (double) yPoints[1]);
            this.drawDefaultSequenceFlowIndicator(line, scaleFactor);
        }
 
        if (conditional) {
            line = new java.awt.geom.Line2D.Double((double) xPoints[0], (double) yPoints[0], (double) xPoints[1], (double) yPoints[1]);
            this.drawConditionalSequenceFlowIndicator(line, scaleFactor);
        }
 
        if (associationDirection.equals(AssociationDirection.ONE) || associationDirection.equals(AssociationDirection.BOTH)) {
            line = new java.awt.geom.Line2D.Double((double) xPoints[xPoints.length - 2], (double) yPoints[xPoints.length - 2], (double) xPoints[xPoints.length - 1], (double) yPoints[xPoints.length - 1]);
            this.drawArrowHead(line, scaleFactor);
        }
 
        if (associationDirection.equals(AssociationDirection.BOTH)) {
            line = new java.awt.geom.Line2D.Double((double) xPoints[1], (double) yPoints[1], (double) xPoints[0], (double) yPoints[0]);
            this.drawArrowHead(line, scaleFactor);
        }
 
        this.g.setPaint(originalPaint);
        this.g.setStroke(originalStroke);
    }
 
    @Override
    public void drawHighLight(int x, int y, int width, int height) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        this.g.setPaint(Color.GREEN);
        this.g.setStroke(THICK_TASK_BORDER_STROKE);
 
        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
        this.g.draw(rect);
 
        this.g.setPaint(originalPaint);
        this.g.setStroke(originalStroke);
    }
 
    public void drawHighLight(int x, int y, int width, int height, Color color) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        this.g.setPaint(color);
        this.g.setStroke(THICK_TASK_BORDER_STROKE);
 
        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
        this.g.draw(rect);
 
        this.g.setPaint(originalPaint);
        this.g.setStroke(originalStroke);
    }
 
    @Override
    public void drawLabel(String text, GraphicInfo graphicInfo, boolean centered) {
        float interline = 1.0f;
 
        // text
        if (text != null && text.length() > 0) {
            Paint originalPaint = g.getPaint();
            Font originalFont = g.getFont();
 
            g.setPaint(Color.BLACK);
            LABEL_FONT = new Font(labelFontName, Font.ITALIC, 12);
            g.setFont(LABEL_FONT);
            int wrapWidth = 200;
            int textY = (int) graphicInfo.getY();
 
            // TODO: use drawMultilineText()
            AttributedString as = new AttributedString(text);
            as.addAttribute(TextAttribute.FOREGROUND, g.getPaint());
            as.addAttribute(TextAttribute.FONT, g.getFont());
            AttributedCharacterIterator aci = as.getIterator();
            FontRenderContext frc = new FontRenderContext(null, true, false);
            LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);
 
            while (lbm.getPosition() < text.length()) {
                TextLayout tl = lbm.nextLayout(wrapWidth);
                textY += tl.getAscent();
                Rectangle2D bb = tl.getBounds();
                double tX = graphicInfo.getX();
                if (centered) {
                    tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2);
                }
                tl.draw(g, (float) tX, textY);
                textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent();
            }
 
            // restore originals
            g.setFont(originalFont);
            g.setPaint(originalPaint);
        }
    }
}

实现颜色自定义

重写ProcessDiagramGenerator接口中的generateDiagram方法,增加color参数:

package com.fengunion.scf.data.workflow.manager;

import java.awt.Color;
import java.io.InputStream;
import java.util.List;
import java.util.Set;

import org.activiti.bpmn.model.BpmnModel;
import org.activiti.image.ProcessDiagramGenerator;

public interface CustomProcessDiagramGeneratorI extends ProcessDiagramGenerator {
    InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
            List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName,
            ClassLoader customClassLoader, double scaleFactor, Color[] colors, Set<String> currIds);
}

2、在Activiti配置中注入自定义的CustomProcessDiagramGeneratorI(此处为SpringBoot方式):

import java.util.ArrayList;
import java.util.List;

import org.activiti.engine.impl.interceptor.SessionFactory;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import com.fengunion.scf.data.workflow.manager.CustomGroupEntityManagerFactory;
import com.fengunion.scf.data.workflow.manager.CustomProcessDiagramGeneratorI;
import com.fengunion.scf.data.workflow.manager.CustomUserEntityManagerFactory;
import com.fengunion.scf.data.workflow.manager.ProcessHistoryManagerSessionFactory;


@Configuration
public class ActivitiConfiguration implements ProcessEngineConfigurationConfigurer{
    
    @Autowired
    private CustomUserEntityManagerFactory customUserEntityManagerFactory;
    
    @Autowired
    private CustomGroupEntityManagerFactory customGroupEntityManagerFactory;
    
    @Autowired
    private ProcessHistoryManagerSessionFactory processHistoryManagerSessionFactory;
    
    @Autowired
    private CustomProcessDiagramGeneratorI customProcessDiagramGeneratorI;
    
    @Override
    public void configure(SpringProcessEngineConfiguration processEngineConfiguration) {
        // TODO Auto-generated method stub
        //processEngineConfiguration.setDataSource(dataSource);
        processEngineConfiguration.setDatabaseSchemaUpdate("none");// none true
        processEngineConfiguration.setDatabaseType("mysql");

        //processEngineConfiguration.setTransactionManager(transactionManager);

        // 流程图字体
        processEngineConfiguration.setActivityFontName("宋体");
        processEngineConfiguration.setAnnotationFontName("宋体");
        processEngineConfiguration.setLabelFontName("宋体");

        processEngineConfiguration.setJpaHandleTransaction(false);
        processEngineConfiguration.setJpaCloseEntityManager(false);
        //
//      processEngineConfiguration.setMailServerHost(mailProperty.getMailServerHost());
//      processEngineConfiguration.setMailServerUsername(mailProperty.getMailServerUsername());
//      processEngineConfiguration.setMailServerPassword(mailProperty.getMailServerPassword());
//      processEngineConfiguration.setMailServerPort(mailProperty.getMailServerPort());
        //
        processEngineConfiguration.setJobExecutorActivate(false);
        processEngineConfiguration.setAsyncExecutorEnabled(false);
        //processEngineConfiguration.setAsyncExecutorActivate(false);
        //自定义用户和组
        List<SessionFactory> customSessionFactories = new ArrayList<>();
        customSessionFactories.add(customUserEntityManagerFactory);
        customSessionFactories.add(customGroupEntityManagerFactory);
        customSessionFactories.add(processHistoryManagerSessionFactory);
        processEngineConfiguration.setCustomSessionFactories(customSessionFactories);
        
    //自定义流程图样式
    processEngineConfiguration.setProcessDiagramGenerator(customProcessDiagramGeneratorI);
    }
}

3、实现自定义的CustomProcessDiagramGenerator类:

package com.fengunion.scf.data.workflow.manager;

import org.activiti.bpmn.model.*;
import org.activiti.bpmn.model.Process;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Set;

@Component
public class CustomProcessDiagramGenerator extends DefaultProcessDiagramGenerator implements CustomProcessDiagramGeneratorI{
    //预初始化流程图绘制,大大提升了系统启动后首次查看流程图的速度
    static {
        new CustomProcessDiagramCanvas(10,10,0,0,"png", "宋体","宋体","宋体",null);
    }
    
    public CustomProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType,
            List<String> highLightedActivities, List<String> highLightedFlows, String activityFontName,
            String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor,
            Color [] colors, Set<String> currIds) {
        
        if(null == highLightedActivities) {
            highLightedActivities = Collections.<String>emptyList();
        }
        if(null == highLightedFlows) {
            highLightedFlows = Collections.<String>emptyList();
        }
        
        prepareBpmnModel(bpmnModel);
        
        CustomProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
        
        // Draw pool shape, if process is participant in collaboration
        for (Pool pool : bpmnModel.getPools()) {
          GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
          processDiagramCanvas.drawPoolOrLane(pool.getName(), graphicInfo);
        }
        
        // Draw lanes
        for (Process process : bpmnModel.getProcesses()) {
          for (Lane lane : process.getLanes()) {
            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(lane.getId());
            processDiagramCanvas.drawPoolOrLane(lane.getName(), graphicInfo);
          }
        }
        
        // Draw activities and their sequence-flows
        for (Process process: bpmnModel.getProcesses()) {
            List<FlowNode> flowNodeList= process.findFlowElementsOfType(FlowNode.class);
          for (FlowNode flowNode : flowNodeList) {
              drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedActivities, highLightedFlows, scaleFactor, 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;
    }

    protected void drawActivity(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode,
            List<String> highLightedActivities, List<String> highLightedFlows, double scaleFactor, 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
          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, scaleFactor, colors[0]);
            
            // Draw sequenceflow label
//          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, scaleFactor);
            }
          }
        }
    }
    protected void drawHighLight(boolean isStartOrEnd, CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo, Color color) {
      processDiagramCanvas.drawHighLight(isStartOrEnd, (int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(), color);
    }
    
    protected static CustomProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String imageType,
              String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
            
            // We need to calculate maximum values to know how big the image will be in its entirety
            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 CustomProcessDiagramCanvas((int) maxX + 10,(int) maxY + 10, (int) minX, (int) minY, 
                imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
          }

    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
            List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName,
            ClassLoader customClassLoader, double scaleFactor, Color[] colors, Set<String> currIds) {
        CustomProcessDiagramCanvas customProcessDiagramCanvas = generateProcessDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, 
                activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor,colors, currIds);
        BufferedImage bufferedImage = customProcessDiagramCanvas.generateBufferedImage(imageType);
        ByteArrayOutputStream bs = new ByteArrayOutputStream();  
        ImageOutputStream imOut;
        try {
            imOut = ImageIO.createImageOutputStream(bs);
            ImageIO.write(bufferedImage, "PNG", imOut);
        } catch (IOException e) {
            e.printStackTrace();
        }  
          
        InputStream is = new ByteArrayInputStream(bs.toByteArray());
        return is;
    }
    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
        return generateDiagram(bpmnModel, imageType, Collections.<String>emptyList(), Collections.<String>emptyList(), 
            activityFontName, labelFontName, annotationFontName, customClassLoader, 1.0, new Color[] {Color.BLACK, Color.BLACK}, null);
    }
    
}

4、重写DefaultProcessDiagramCanvas类drawHighLight和drawSequenceflow方法,实现走过的历史节点高亮功能:、

package com.fengunion.scf.data.workflow.manager;

import java.awt.Color;
import java.awt.Font;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;

import javax.imageio.ImageIO;

import org.activiti.bpmn.model.AssociationDirection;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.image.exception.ActivitiImageException;
import org.activiti.image.impl.DefaultProcessDiagramCanvas;
import org.activiti.image.util.ReflectUtil;

import com.fengunion.scf.data.workflow.common.constant.WorkflowConstants;

public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas {

    protected static Color LABEL_COLOR = new Color(0, 0, 0);

    //font
    protected String activityFontName = "宋体";
    protected String labelFontName = "宋体";
    protected String annotationFontName = "宋体";
    
    private static volatile boolean flag = false;

    public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType) {
        super(width, height, minX, minY, imageType);
    }

    public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType,
            String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
        super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName,
                customClassLoader);
    }

    public void drawHighLight(boolean isStartOrEnd, int x, int y, int width, int height, Color color) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();

        g.setPaint(color);
        g.setStroke(MULTI_INSTANCE_STROKE);
        if (isStartOrEnd) {// 开始、结束节点画圆
            g.drawOval(x, y, width, height);
        } else {// 非开始、结束节点画圆角矩形
            RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 5, 5);
            g.draw(rect);
        }
        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }

    public void drawSequenceflow(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault,
            boolean highLighted, double scaleFactor, Color color) {
        drawConnection(xPoints, yPoints, conditional, isDefault, "sequenceFlow", AssociationDirection.ONE, highLighted,
                scaleFactor, color);
    }

    public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault,
            String connectionType, AssociationDirection associationDirection, boolean highLighted, double scaleFactor,
            Color color) {

        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();

        g.setPaint(CONNECTION_COLOR);
        if (connectionType.equals("association")) {
            g.setStroke(ASSOCIATION_STROKE);
        } else if (highLighted) {
            g.setPaint(color);
            g.setStroke(HIGHLIGHT_FLOW_STROKE);
        }

        for (int i = 1; i < xPoints.length; i++) {
            Integer sourceX = xPoints[i - 1];
            Integer sourceY = yPoints[i - 1];
            Integer targetX = xPoints[i];
            Integer targetY = yPoints[i];
            Line2D.Double line = new Line2D.Double(sourceX, sourceY, targetX, targetY);
            g.draw(line);
        }

        if (isDefault) {
            Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
            drawDefaultSequenceFlowIndicator(line, scaleFactor);
        }

        if (conditional) {
            Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
            drawConditionalSequenceFlowIndicator(line, scaleFactor);
        }

        if (associationDirection.equals(AssociationDirection.ONE)
                || associationDirection.equals(AssociationDirection.BOTH)) {
            Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2], yPoints[xPoints.length - 2],
                    xPoints[xPoints.length - 1], yPoints[xPoints.length - 1]);
            drawArrowHead(line, scaleFactor);
        }
        if (associationDirection.equals(AssociationDirection.BOTH)) {
            Line2D.Double line = new Line2D.Double(xPoints[1], yPoints[1], xPoints[0], yPoints[0]);
            drawArrowHead(line, scaleFactor);
        }
        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }

    public void drawLabel(boolean highLighted, String text, GraphicInfo graphicInfo, boolean centered) {
        float interline = 1.0f;

        // text
        if (text != null && text.length() > 0) {
            Paint originalPaint = g.getPaint();
            Font originalFont = g.getFont();
            if (highLighted) {
                g.setPaint(WorkflowConstants.COLOR_NORMAL);
            } else {
                g.setPaint(LABEL_COLOR);
            }
            g.setFont(new Font(labelFontName, Font.BOLD, 10));

            int wrapWidth = 100;
            int textY = (int) graphicInfo.getY();

            // TODO: use drawMultilineText()
            AttributedString as = new AttributedString(text);
            as.addAttribute(TextAttribute.FOREGROUND, g.getPaint());
            as.addAttribute(TextAttribute.FONT, g.getFont());
            AttributedCharacterIterator aci = as.getIterator();
            FontRenderContext frc = new FontRenderContext(null, true, false);
            LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);

            while (lbm.getPosition() < text.length()) {
                TextLayout tl = lbm.nextLayout(wrapWidth);
                textY += tl.getAscent();
                Rectangle2D bb = tl.getBounds();
                double tX = graphicInfo.getX();
                if (centered) {
                    tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2);
                }
                tl.draw(g, (float) tX, textY);
                textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent();
            }

            // restore originals
            g.setFont(originalFont);
            g.setPaint(originalPaint);
        }
    }

    @Override
    public BufferedImage generateBufferedImage(String imageType) {
        if (closed) {
            throw new ActivitiImageException("ProcessDiagramGenerator already closed");
        }

        // Try to remove white space
        minX = (minX <= WorkflowConstants.PROCESS_PADDING) ? WorkflowConstants.PROCESS_PADDING : minX;
        minY = (minY <= WorkflowConstants.PROCESS_PADDING) ? WorkflowConstants.PROCESS_PADDING : minY;
        BufferedImage imageToSerialize = processDiagram;
        if (minX >= 0 && minY >= 0) {
            imageToSerialize = processDiagram.getSubimage(
                    minX - WorkflowConstants.PROCESS_PADDING,
                    minY - WorkflowConstants.PROCESS_PADDING, 
                    canvasWidth - minX + WorkflowConstants.PROCESS_PADDING,
                    canvasHeight - minY + WorkflowConstants.PROCESS_PADDING);
        }
        return imageToSerialize;
    }

    @Override
    public void initialize(String imageType) {  
        this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);    
        this.g = processDiagram.createGraphics();

        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setPaint(Color.black);
        
        Font font = new Font(activityFontName, Font.BOLD, FONT_SIZE);
        g.setFont(font);   
        this.fontMetrics = g.getFontMetrics();      
        
        LABEL_FONT = new Font(labelFontName, Font.ITALIC, 10);
        ANNOTATION_FONT = new Font(annotationFontName, Font.PLAIN, FONT_SIZE);
        //优化加载速度
        if(flag) {
            return;
        }
        try {
            USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/userTask.png", customClassLoader));
            SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/scriptTask.png", customClassLoader));
            SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/serviceTask.png", customClassLoader));
            RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/receiveTask.png", customClassLoader));
            SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/sendTask.png", customClassLoader));
            MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/manualTask.png", customClassLoader));
            BUSINESS_RULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/businessRuleTask.png", customClassLoader));
            SHELL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/shellTask.png", customClassLoader));
            CAMEL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/camelTask.png", customClassLoader));
            MULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/muleTask.png", customClassLoader));
            
            TIMER_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/timer.png", customClassLoader));
            COMPENSATE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/compensate-throw.png", customClassLoader));
            COMPENSATE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/compensate.png", customClassLoader));
            ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/error-throw.png", customClassLoader));
            ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/error.png", customClassLoader));
            MESSAGE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/message-throw.png", customClassLoader));
            MESSAGE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/message.png", customClassLoader));
            SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/signal-throw.png", customClassLoader));
            SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/signal.png", customClassLoader));
/*        String baseUrl = Thread.currentThread().getContextClassLoader().getResource("static/img/activiti/").getPath();
          SCRIPTTASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"scriptTask.png"));
          USERTASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"userTask.png"));
          SERVICETASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"serviceTask.png"));
          RECEIVETASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"receiveTask.png"));
          SENDTASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"sendTask.png"));
          MANUALTASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"manualTask.png"));
          BUSINESS_RULE_TASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"businessRuleTask.png"));
          SHELL_TASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"shellTask.png"));
          CAMEL_TASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"camelTask.png"));
          MULE_TASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"muleTask.png"));
          
          TIMER_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"timer.png"));
          COMPENSATE_THROW_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"compensate-throw.png"));
          COMPENSATE_CATCH_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"compensate.png"));
          ERROR_THROW_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"error-throw.png"));
          ERROR_CATCH_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"error.png"));
          MESSAGE_THROW_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"message-throw.png"));
          MESSAGE_CATCH_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"message.png"));
          SIGNAL_THROW_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"signal-throw.png"));
          SIGNAL_CATCH_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"signal.png"));*/
          flag = true;
        } catch (IOException e) {
          flag = false;
          LOGGER.warn("Could not load image for process diagram creation: {}", e.getMessage());
        }  
    }

}

欢迎各位留言交流。
记得点赞哦!

可以的,你可以使用以下代码获取已完成的流程审批结果和原因详情: ``` List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .orderByHistoricActivityInstanceStartTime().asc() .list(); for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) { String activityId = historicActivityInstance.getActivityId(); if (activityId.equals("endEvent")) { // 流程结束节点 String processInstanceId = historicActivityInstance.getProcessInstanceId(); HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); String endActivityInstanceId = historicActivityInstance.getId(); List<HistoricVariableInstance> variableInstances = historyService.createHistoricVariableInstanceQuery() .processInstanceId(processInstanceId) .activityInstanceId(endActivityInstanceId) .list(); for (HistoricVariableInstance variableInstance : variableInstances) { String variableName = variableInstance.getVariableName(); Object value = variableInstance.getValue(); System.out.println("variableName=" + variableName + ", value=" + value); } break; } } ``` 该代码使用 Activiti 的 HistoryService API 获取已完成的流程审批结果和原因详情。具体来说,它获取所有历史活动实例,根据流程结束节点找到对应的历史流程实例,并从该历史流程实例中获取流程变量的值。最终,它打印流程变量的名称和值。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值