實現效果:java
版本聲明:Activiti 5.22.0mysql
需求:Activiti原生代碼在展現動態流程圖時,僅支持高亮當前節點,且高亮顏色爲紅色,如何才能實現這樣一個需求:要求在展現動態流程圖時,走過的歷史節點顯示爲綠色(顏色可配置),當前節點高亮爲紅色(顏色可配置)?下面講述下思路。spring
一、重寫ProcessDiagramGenerator接口中的generateDiagram方法,增長color參數:(currIds爲支持並行網關節點增長)sql
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); }
二、在Activiti配置中注入自定義的CustomProcessDiagramGeneratorI(此處爲SpringBoot方式):shell
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); } }
三、實現自定義的CustomProcessDiagramGenerator類:canvas
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); } }
四、重寫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()); } } }
總結:核心思想就是重寫Activiti生成動態流程圖的方法(須要重寫畫節點、畫線的方法),增長顏色配置參數。 鑑於有小夥伴私信找我要自定義的常量類,現將WorkflowConstants類奉上:ide
package com.fengunion.scf.data.workflow.common.constant; import com.google.common.collect.Lists; import java.awt.Color; import java.util.List; /** * 變量類 *@author jjd **/ public final class WorkflowConstants { /**businessKey**/ public static final String WORKLOW_BUSINESS_KEY = "businessKey"; /**按鈕網關**/ public static final String WAY_TYPE = "wayType"; /**按鈕網關**/ public static final String WAY_TYPE_PREFIX = "way_type_"; /**項目id**/ public static final String PROJECT_ID = "projectId"; /**核心企業Id變量**/ public static final String CORE_ENTERPRISE_ID="coreEnterpriseId"; /**鏈屬企業Id變量**/ public static final String CHAIN_ENTERPRISE_ID="chainEnterpriseId"; /**銀行企業Id變量**/ public static final String BANK_ENTERPRISE_ID="bankEnterpriseId"; /**保理公司Id變量**/ public static final String BAOLI_ENTERPRISE_ID="baoliEnterpriseId"; /**立帳開立企業Id變量**/ public static final String START_ENTERPRISE_ID="startEnterpriseId"; /**立帳合做企業Id變量**/ public static final String PARTNER_ENTERPRISE_ID="partnerEnterpriseId"; /**母公司企業id**/ public static final String PARENT_ENTERPRISE_ID="parentEnterpriseId"; /**指定簽收企業id**/ public static final String RECEIVE_ENTERPRISE_ID ="receiveEnterpriseId"; /**轉出方企業Id變量**/ public static final String TRANSFER_ENTERPRISE_ID="transEnterpriseId"; /**指定簽收企業id**/ public static final String AC_TASK_ID ="acTaskId"; /**企業全部角色**/ public static final String ENT_ALL_ROLE ="all"; /**運營全部角色**/ public static final String OPER_ALL_ROLE ="oper"; /**流程定義緩存時間**/ public static final int PROCESS_DEFINITION_CACHE = 60; /**流程實例激活**/ public static final int PROCESS_INSTANCE_ACTIVE = 1; /**流程實例掛起**/ public static final int PROCESS_INSTANCE_SUSPEND = 0; /**讀取圖片**/ public static final String READ_IMAGE = "image"; /**讀取xml**/ public static final String READ_XML = "xml"; /**流程激活**/ public static final Integer ACTIVE_PROCESSDEFINITION = 1; /**流程掛起**/ public static final Integer SUSPEND_PROCESSDEFINITION = 2; /**流程狀態:0-所有,1-正常,2-已掛起**/ public static final int QUERY_ALL = 0; public static final int QUERY_NORMAL = 1; public static final int QUERY_SUSPENDED = 2; /**流程實例狀態:0-所有,1-正常,2-已刪除**/ public static final int INSTANCE_ALL = 0; public static final int INSTANCE_NOT_DELETED = 1; public static final int INSTANCE_DELETED = 2; /** 系統管理員ID **/ public static final String INTERFACE_SYSTEM_ID = "-1"; /** 系統管理員名稱 **/ public static final String INTERFACE_SYSTEM_NAME = "系統操做"; /** 流程部署類型:1-啓動並激活,2-啓動即掛起 **/ public static final int PROCESS_START_ACTIVE = 1; public static final int PROCESS_START_SUSPEND = 2; /** 用於標識流程項目配置信息校驗結果:1:新流程,2:新版本, 3:流程類別有誤 **/ public static final int CHECK_NEW_PROCESS = 1; public static final int CHECK_NEW_VERSION = 2; public static final int CHECK_ERROR_PROCESS_TYPE = 3; /** 默認網關條件值 **/ public static final Integer default_GATEWAY_CONDITION_VALUE = 1; /** 工做流-業務狀態表數據類型:1-工做流狀態,2-業務狀態 **/ public static final Integer PROCESS_STATUS = 1; public static final Integer BIZNESS_STATUS = 2; /** 新增流程時標識:1-直接保存,2-提示覆蓋 **/ public static final Integer PROCESS_STATUS_SAVE = 1; public static final Integer BIZNESS_STATUS_WARN = 2; /** 模板類型標識:1-新建立或直接導入的模板,2-默認模板生成 **/ public static final Integer MODEL_TYPE_1 = 1; public static final Integer MODEL_TYPE_2 = 2; /** 查詢流程定義標識:1-查詢最新版本流程定義,2-查詢全部版本 **/ public static final Integer QUERY_PROCESS_LATEST_VERSION = 1; public static final Integer QUERY_PROCESS_ALL_VERSION = 2; /** 按鈕網關 經過1 */ public static final String WAY_TYPE_PASS = "1"; /** 按鈕網關 駁回或結束0 */ public static final String WAY_TYPE_REJECT = "0"; /** 按鈕網關 退回2 */ public static final String WAY_TYPE_BACK = "2"; /**任務參數爲空**/ public static final int TASK_CHECK_PARAM_NULL = -1; /**任務已辦理**/ public static final int TASK_CHECK_COMPLETED = 1; /**無權限辦理**/ public static final int TASK_CHECK_NO_PERMISSIONS= 2; /**任務校驗經過**/ public static final int TASK_CHECK_PASS = 0; /** 動態流程圖顏色定義 **/ public static final Color COLOR_NORMAL = new Color(0, 205, 0); public static final Color COLOR_CURRENT = new Color(255, 0, 0); /** 定義生成流程圖時的邊距(像素) **/ public static final int PROCESS_PADDING = 5; /** 定義新版業務進度查詢包含的流程類型 **/ public static final List<ProcessTypeEnum> INCLUDE_PROCEE_TYPE = Lists.newArrayList( ProcessTypeEnum.BUILD_ACCOUNT_APPLY, ProcessTypeEnum.CREDIT_LETTER_APPLY, ProcessTypeEnum.CREDIT_LETTER_TRANSFER, ProcessTypeEnum.CREDIT_LETTER_CASH); }
五、調用畫圖service層oop
/** * @讀取動態流程圖 */ public void readProcessImg(String processInstanceId, HttpServletResponse response) throws IOException { if (StringUtils.isBlank(processInstanceId)) { logger.error("參數爲空"); } HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery() .processInstanceId(processInstanceId).singleResult(); BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); ProcessDefinitionEntity definitionEntity = (ProcessDefinitionEntity) repositoryService .getProcessDefinition(processInstance.getProcessDefinitionId()); List<HistoricActivityInstance> highLightedActivitList = historyService.createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list(); // 高亮環節id集合 List<String> highLightedActivitis = new ArrayList<String>(); // 高亮線路id集合 List<String> highLightedFlows = getHighLightedFlows(definitionEntity, highLightedActivitList); for (HistoricActivityInstance tempActivity : highLightedActivitList) { String activityId = tempActivity.getActivityId(); highLightedActivitis.add(activityId); } Set<String> currIds = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).list() .stream().map(e->e.getActivityId()).collect(Collectors.toSet()); CustomProcessDiagramGeneratorI diagramGenerator = (CustomProcessDiagramGeneratorI) processEngineConfiguration .getProcessDiagramGenerator(); InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitis, highLightedFlows, "宋體", "宋體", "宋體", null, 1.0, new Color[] { WorkflowConstants.COLOR_NORMAL, WorkflowConstants.COLOR_CURRENT }, currIds); // 輸出資源內容到相應對象 byte[] b = new byte[1024]; int len; while ((len = imageStream.read(b, 0, 1024)) != -1) { response.getOutputStream().write(b, 0, len); } }
六、getHighLightedFlows方法調整爲以下:(註釋部分)字體
/** * 獲取須要高亮的線 * * @param processDefinitionEntity * @param historicActivityInstances * @return */ private List<String> getHighLightedFlows(ProcessDefinitionEntity processDefinitionEntity, List<HistoricActivityInstance> historicActivityInstances) { List<String> highFlows = new ArrayList<>();// 用以保存高亮的線flowId for (int i = 0; i < historicActivityInstances.size() - 1; i++) {// 對歷史流程節點進行遍歷 ActivityImpl activityImpl = processDefinitionEntity .findActivity(historicActivityInstances.get(i).getActivityId());// 獲得節點定義的詳細信息 List<ActivityImpl> sameStartTimeNodes = new ArrayList<>();// 用以保存後需開始時間相同的節點 ActivityImpl sameActivityImpl1 = processDefinitionEntity .findActivity(historicActivityInstances.get(i + 1).getActivityId()); // 將後面第一個節點放在時間相同節點的集合裏 sameStartTimeNodes.add(sameActivityImpl1); for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) { HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 後續第一個節點 HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 後續第二個節點 if (Math.abs(activityImpl1.getStartTime().getTime()-activityImpl2.getStartTime().getTime()) < 200) { // if (activityImpl1.getStartTime().equals(activityImpl2.getStartTime())) { // 若是第一個節點和第二個節點開始時間相同保存 ActivityImpl sameActivityImpl2 = processDefinitionEntity .findActivity(activityImpl2.getActivityId()); sameStartTimeNodes.add(sameActivityImpl2); } else { // 有不相同跳出循環 break; } } List<PvmTransition> pvmTransitions = activityImpl.getOutgoingTransitions();// 取出節點的全部出去的線 for (PvmTransition pvmTransition : pvmTransitions) { // 對全部的線進行遍歷 ActivityImpl pvmActivityImpl = (ActivityImpl) pvmTransition.getDestination(); // 若是取出的線的目標節點存在時間相同的節點裏,保存該線的id,進行高亮顯示 if (sameStartTimeNodes.contains(pvmActivityImpl)) { highFlows.add(pvmTransition.getId()); } } } return highFlows; }
附20190319日修改內容: 鑑於有小夥伴向我反映並行網關的高亮顯示有問題,在他的熱心幫助下,我修改了部分代碼,以支持並行網關,在此感謝! 感謝你們的關注!