From 3602de227d6f1481e0e6d55971adde80fa6ad36b Mon Sep 17 00:00:00 2001 From: jinqiming <45981669@qq.com> Date: Thu, 10 Dec 2020 21:07:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B5=81=E7=A8=8B=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flowable/FlowModelerController.java | 2 +- .../common/constants/FlowConstants.java | 12 + .../config/CustomProcessDiagramCanvas.java | 246 ++++++++++++ .../config/CustomProcessDiagramGenerator.java | 361 ++++++++++++++++++ .../snow/flowable/config/FlowIdGenerator.java | 35 +- .../snow/flowable/config/FlowableConfig.java | 5 +- .../ICustomProcessDiagramGenerator.java | 15 + .../domain/HistoricTaskInstanceVO.java | 3 +- .../flowable/service/FlowableService.java | 5 + .../service/impl/FlowableServiceImpl.java | 143 ++++++- 10 files changed, 820 insertions(+), 7 deletions(-) create mode 100644 snow-flowable/src/main/java/com/snow/flowable/config/CustomProcessDiagramCanvas.java create mode 100644 snow-flowable/src/main/java/com/snow/flowable/config/CustomProcessDiagramGenerator.java create mode 100644 snow-flowable/src/main/java/com/snow/flowable/config/ICustomProcessDiagramGenerator.java diff --git a/snow-admin/src/main/java/com/snow/web/controller/flowable/FlowModelerController.java b/snow-admin/src/main/java/com/snow/web/controller/flowable/FlowModelerController.java index a98b527..3f84613 100644 --- a/snow-admin/src/main/java/com/snow/web/controller/flowable/FlowModelerController.java +++ b/snow-admin/src/main/java/com/snow/web/controller/flowable/FlowModelerController.java @@ -128,7 +128,7 @@ public class FlowModelerController extends BaseController @ResponseBody public void getProcessDiagram(String processInstanceId,HttpServletResponse response) { - flowableService.getProcessDiagram(response,processInstanceId); + flowableService.getFlowableProcessImage(processInstanceId,response); } /** diff --git a/snow-flowable/src/main/java/com/snow/flowable/common/constants/FlowConstants.java b/snow-flowable/src/main/java/com/snow/flowable/common/constants/FlowConstants.java index 562e5c4..6f0cf63 100644 --- a/snow-flowable/src/main/java/com/snow/flowable/common/constants/FlowConstants.java +++ b/snow-flowable/src/main/java/com/snow/flowable/common/constants/FlowConstants.java @@ -1,5 +1,7 @@ package com.snow.flowable.common.constants; +import java.awt.*; + /** * @author qimingjin * @Title: @@ -41,4 +43,14 @@ public class FlowConstants { * 流程开始用户标识 */ public static final String START_USER_ID="startUserId"; + + + + + /** 动态流程图颜色定义 **/ + 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; } diff --git a/snow-flowable/src/main/java/com/snow/flowable/config/CustomProcessDiagramCanvas.java b/snow-flowable/src/main/java/com/snow/flowable/config/CustomProcessDiagramCanvas.java new file mode 100644 index 0000000..b0e9345 --- /dev/null +++ b/snow-flowable/src/main/java/com/snow/flowable/config/CustomProcessDiagramCanvas.java @@ -0,0 +1,246 @@ +package com.snow.flowable.config; + +import com.snow.flowable.common.constants.FlowConstants; +import org.flowable.bpmn.model.AssociationDirection; +import org.flowable.bpmn.model.GraphicInfo; +import org.flowable.image.exception.FlowableImageException; +import org.flowable.image.impl.DefaultProcessDiagramCanvas; +import org.flowable.image.util.ReflectUtil; + +import javax.imageio.ImageIO; +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.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; + +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(FlowConstants.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 FlowableImageException("ProcessDiagramGenerator already closed"); + } + + // Try to remove white space + minX = (minX <= FlowConstants.PROCESS_PADDING) ? FlowConstants.PROCESS_PADDING : minX; + minY = (minY <= FlowConstants.PROCESS_PADDING) ? FlowConstants.PROCESS_PADDING : minY; + BufferedImage imageToSerialize = processDiagram; + if (minX >= 0 && minY >= 0) { + imageToSerialize = processDiagram.getSubimage( + minX - FlowConstants.PROCESS_PADDING, + minY - FlowConstants.PROCESS_PADDING, + canvasWidth - minX + FlowConstants.PROCESS_PADDING, + canvasHeight - minY + FlowConstants.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/flowable/icons/userTask.png", customClassLoader)); + SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/scriptTask.png", customClassLoader)); + SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/serviceTask.png", customClassLoader)); + RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/receiveTask.png", customClassLoader)); + SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/sendTask.png", customClassLoader)); + MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/manualTask.png", customClassLoader)); + BUSINESS_RULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/businessRuleTask.png", customClassLoader)); + SHELL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/shellTask.png", customClassLoader)); + CAMEL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/camelTask.png", customClassLoader)); + MULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/muleTask.png", customClassLoader)); + + TIMER_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/timer.png", customClassLoader)); + COMPENSATE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate-throw.png", customClassLoader)); + COMPENSATE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate.png", customClassLoader)); + ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error-throw.png", customClassLoader)); + ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error.png", customClassLoader)); + MESSAGE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message-throw.png", customClassLoader)); + MESSAGE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message.png", customClassLoader)); + SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal-throw.png", customClassLoader)); + SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/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()); + } + } + + +} diff --git a/snow-flowable/src/main/java/com/snow/flowable/config/CustomProcessDiagramGenerator.java b/snow-flowable/src/main/java/com/snow/flowable/config/CustomProcessDiagramGenerator.java new file mode 100644 index 0000000..53f8acf --- /dev/null +++ b/snow-flowable/src/main/java/com/snow/flowable/config/CustomProcessDiagramGenerator.java @@ -0,0 +1,361 @@ +package com.snow.flowable.config; + +import org.flowable.bpmn.model.*; +import org.flowable.bpmn.model.Process; +import org.flowable.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 ICustomProcessDiagramGenerator{ + //预初始化流程图绘制,大大提升了系统启动后首次查看流程图的速度 + static { + new CustomProcessDiagramCanvas(10,10,0,0,"png", "宋体","宋体","宋体",null); + } + + public CustomProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType, + List highLightedActivities, List highLightedFlows, String activityFontName, + String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, + Color[] colors, Set currIds) { + + if(null == highLightedActivities) { + highLightedActivities = Collections.emptyList(); + } + if(null == highLightedFlows) { + highLightedFlows = Collections.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,scaleFactor); + } + + // Draw lanes + for (Process process : bpmnModel.getProcesses()) { + for (Lane lane : process.getLanes()) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(lane.getId()); + processDiagramCanvas.drawPoolOrLane(lane.getName(), graphicInfo,scaleFactor); + } + } + + // Draw activities and their sequence-flows + for (Process process: bpmnModel.getProcesses()) { + List 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 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 highLightedActivities, List highLightedFlows, double scaleFactor, Color[] colors, Set 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 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= 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,true); + } + } + } + } + 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 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 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 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 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 highLightedActivities, + List highLightedFlows, String activityFontName, String labelFontName, String annotationFontName, + ClassLoader customClassLoader, double scaleFactor, Color[] colors, Set 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; + } + + + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) { + return generateDiagram(bpmnModel, imageType, Collections.emptyList(), Collections.emptyList(), + activityFontName, labelFontName, annotationFontName, customClassLoader, 1.0, new Color[] {Color.BLACK, Color.BLACK}, null); + } + +} diff --git a/snow-flowable/src/main/java/com/snow/flowable/config/FlowIdGenerator.java b/snow-flowable/src/main/java/com/snow/flowable/config/FlowIdGenerator.java index 913612f..ebc4e8c 100644 --- a/snow-flowable/src/main/java/com/snow/flowable/config/FlowIdGenerator.java +++ b/snow-flowable/src/main/java/com/snow/flowable/config/FlowIdGenerator.java @@ -1,14 +1,25 @@ package com.snow.flowable.config; +import cn.hutool.bloomfilter.BitMapBloomFilter; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Snowflake; import cn.hutool.core.thread.ConcurrencyTester; import cn.hutool.core.thread.ExecutorBuilder; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.IdUtil; +import cn.hutool.crypto.asymmetric.RSA; +import cn.hutool.dfa.WordTree; +import cn.hutool.poi.word.Word07Writer; +import com.alibaba.fastjson.JSON; import org.flowable.common.engine.impl.cfg.IdGenerator; import cn.hutool.core.lang.Console; +import java.awt.*; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -29,8 +40,28 @@ public class FlowIdGenerator implements IdGenerator { //很多时候,我们需要简单模拟N个线程调用某个业务测试其并发状况,于是Hutool提供了一个简单的并发测试类——ConcurrencyTester。 public static void main(String[] args) { + RSA rsa = new RSA(); - ThreadPoolExecutor executor = ExecutorBuilder.create(). +//获得私钥 + PrivateKey privateKey = rsa.getPrivateKey(); + +//获得公钥 + PublicKey publicKey = rsa.getPublicKey(); + Console.log("privateKey:{}",privateKey.getEncoded()); + Console.log("publicKey:{}",publicKey.getEncoded()); + + /* Word07Writer writer = new Word07Writer(); + +// 添加段落(标题) + writer.addText(new Font("方正小标宋简体", Font.PLAIN, 22), "我是第一部分", "我是第二部分"); +// 添加段落(正文) + writer.addText(new Font("宋体", Font.PLAIN, 22), "我是正文第一部分", "我是正文第二部分"); +// 写出到文件 + writer.flush(FileUtil.file("d:/wordWrite.docx")); +// 关闭 + writer.close(); +*/ + /* ThreadPoolExecutor executor = ExecutorBuilder.create(). setCorePoolSize(5). setMaxPoolSize(10). setWorkQueue(new LinkedBlockingQueue<>(100)) @@ -46,7 +77,7 @@ public class FlowIdGenerator implements IdGenerator { } } }); - executor.shutdown(); + executor.shutdown();*/ /* ConcurrencyTester tester = ThreadUtil.concurrencyTest(100, () -> { // 测试的逻辑内容 diff --git a/snow-flowable/src/main/java/com/snow/flowable/config/FlowableConfig.java b/snow-flowable/src/main/java/com/snow/flowable/config/FlowableConfig.java index 7369545..1c32dc2 100644 --- a/snow-flowable/src/main/java/com/snow/flowable/config/FlowableConfig.java +++ b/snow-flowable/src/main/java/com/snow/flowable/config/FlowableConfig.java @@ -7,6 +7,7 @@ import org.flowable.common.engine.api.delegate.event.FlowableEventListener; import org.flowable.common.engine.impl.EngineDeployer; import org.flowable.engine.impl.rules.RulesDeployer; import org.flowable.spring.SpringProcessEngineConfiguration; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -26,7 +27,8 @@ import java.util.Map; @Configuration @Slf4j public class FlowableConfig { - + @Autowired + private ICustomProcessDiagramGenerator customProcessDiagramGenerator; @Primary @Bean(name = "processEngineConfiguration") @@ -36,6 +38,7 @@ public class FlowableConfig { configuration.setTransactionManager(transactionManager); configuration.setDatabaseSchemaUpdate("false"); configuration.setAsyncExecutorActivate(true); + configuration.setProcessDiagramGenerator(customProcessDiagramGenerator); //修改id生成器 configuration.setIdGenerator(new FlowIdGenerator()); configuration.setCustomPostDeployers(new ArrayList(){ diff --git a/snow-flowable/src/main/java/com/snow/flowable/config/ICustomProcessDiagramGenerator.java b/snow-flowable/src/main/java/com/snow/flowable/config/ICustomProcessDiagramGenerator.java new file mode 100644 index 0000000..b9f20ce --- /dev/null +++ b/snow-flowable/src/main/java/com/snow/flowable/config/ICustomProcessDiagramGenerator.java @@ -0,0 +1,15 @@ +package com.snow.flowable.config; + +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.image.ProcessDiagramGenerator; + +import java.awt.*; +import java.io.InputStream; +import java.util.List; +import java.util.Set; + +public interface ICustomProcessDiagramGenerator extends ProcessDiagramGenerator { + InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List highLightedActivities, + List highLightedFlows, String activityFontName, String labelFontName, String annotationFontName, + ClassLoader customClassLoader, double scaleFactor, Color[] colors, Set currIds); +} diff --git a/snow-flowable/src/main/java/com/snow/flowable/domain/HistoricTaskInstanceVO.java b/snow-flowable/src/main/java/com/snow/flowable/domain/HistoricTaskInstanceVO.java index f444f25..fb4f944 100644 --- a/snow-flowable/src/main/java/com/snow/flowable/domain/HistoricTaskInstanceVO.java +++ b/snow-flowable/src/main/java/com/snow/flowable/domain/HistoricTaskInstanceVO.java @@ -114,7 +114,7 @@ public class HistoricTaskInstanceVO implements Serializable { */ private Map processVariables; - private List identityLinks; + /* private String scopeId; private String subScopeId; private String scopeType; @@ -150,7 +150,6 @@ public class HistoricTaskInstanceVO implements Serializable { historicTaskInstanceVO.setTaskLocalVariables(t.getTaskLocalVariables()); historicTaskInstanceVO.setProcessVariables(t.getProcessVariables()); - historicTaskInstanceVO.setIdentityLinks(t.getIdentityLinks()); return historicTaskInstanceVO; }).collect(Collectors.toList()); } diff --git a/snow-flowable/src/main/java/com/snow/flowable/service/FlowableService.java b/snow-flowable/src/main/java/com/snow/flowable/service/FlowableService.java index eb2ab64..7ba8a45 100644 --- a/snow-flowable/src/main/java/com/snow/flowable/service/FlowableService.java +++ b/snow-flowable/src/main/java/com/snow/flowable/service/FlowableService.java @@ -159,4 +159,9 @@ public interface FlowableService { * @return */ PageModel getHistoricTaskInstance(HistoricTaskInstanceDTO historicTaskInstanceDTO); + + /** + * 获取流程图像,已执行节点和流程线高亮显示 + */ + void getFlowableProcessImage(String processInstanceId, HttpServletResponse response); } diff --git a/snow-flowable/src/main/java/com/snow/flowable/service/impl/FlowableServiceImpl.java b/snow-flowable/src/main/java/com/snow/flowable/service/impl/FlowableServiceImpl.java index bc88f12..aee7f0b 100644 --- a/snow-flowable/src/main/java/com/snow/flowable/service/impl/FlowableServiceImpl.java +++ b/snow-flowable/src/main/java/com/snow/flowable/service/impl/FlowableServiceImpl.java @@ -13,6 +13,7 @@ import com.snow.common.core.page.PageModel; import com.snow.common.core.text.Convert; import com.snow.common.exception.BusinessException; import com.snow.flowable.common.constants.FlowConstants; +import com.snow.flowable.config.ICustomProcessDiagramGenerator; import com.snow.flowable.domain.*; import com.snow.flowable.enums.FlowFinishedStatusEnum; import com.snow.flowable.service.FlowableService; @@ -32,6 +33,8 @@ import org.flowable.engine.*; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; +import org.flowable.engine.impl.RepositoryServiceImpl; +import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.flowable.engine.repository.*; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.task.Attachment; @@ -52,11 +55,14 @@ import org.springframework.util.StringUtils; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; +import java.awt.*; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.text.SimpleDateFormat; import java.util.*; +import java.util.List; import java.util.stream.Collectors; /** @@ -876,7 +882,142 @@ public class FlowableServiceImpl implements FlowableService { return taskVOList; } - public String getUserNameById(String id){ + public String getUserNameById(String id){ return sysUserService.selectUserById(Long.parseLong(id)).getUserName(); } + + /** + * 获取流程图像,已执行节点和流程线高亮显示 + */ + @Override + public void getFlowableProcessImage(String processInstanceId, HttpServletResponse response) { + log.info("[开始]-获取流程图图像"); + try { + // 获取历史流程实例 + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId).singleResult(); + + if (historicProcessInstance == null) { + throw new BusinessException("获取流程实例ID[" + processInstanceId + "]对应的历史流程实例失败!"); + } + else { + // 获取流程定义 + ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService) + .getDeployedProcessDefinition(historicProcessInstance.getProcessDefinitionId()); + + // 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序 + List historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(processInstanceId).orderByHistoricActivityInstanceId().asc().list(); + + // 已执行的节点ID集合 + List executedActivityIdList = new ArrayList(); + int index = 1; + for (HistoricActivityInstance activityInstance : historicActivityInstanceList) { + executedActivityIdList.add(activityInstance.getActivityId()); + //logger.info("第[" + index + "]个已执行节点=" + activityInstance.getActivityId() + " : " +activityInstance.getActivityName()); + index++; + } + + BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId()); + + // 已执行的线集合 + List flowIds = Lists.newArrayList(); + // 获取流程走过的线 (getHighLightedFlows是下面的方法) + flowIds = getHighLightedFlows(bpmnModel,processDefinition, historicActivityInstanceList); + + Set currIds = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list() + .stream().map(e->e.getActivityId()).collect(Collectors.toSet()); + + ICustomProcessDiagramGenerator diagramGenerator = (ICustomProcessDiagramGenerator) processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator(); + InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", executedActivityIdList, + flowIds, "宋体", "宋体", "宋体", null, 1.0, + new Color[] { FlowConstants.COLOR_NORMAL, FlowConstants.COLOR_CURRENT }, currIds); + + response.setContentType("image/png"); + OutputStream os = response.getOutputStream(); + int bytesRead = 0; + byte[] buffer = new byte[8192]; + while ((bytesRead = imageStream.read(buffer, 0, 8192)) != -1) { + os.write(buffer, 0, bytesRead); + } + os.close(); + imageStream.close(); + } + log.info("[完成]-获取流程图图像"); + } catch (Exception e) { + log.error("【异常】-获取流程图失败!" + e.getMessage()); + throw new BusinessException("获取流程图失败!" + e.getMessage()); + } + } + + private List getHighLightedFlows(BpmnModel bpmnModel,ProcessDefinitionEntity processDefinitionEntity,List historicActivityInstances) { + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //24小时制 + List highFlows = new ArrayList();// 用以保存高亮的线flowId + for (int i = 0; i < historicActivityInstances.size() - 1; i++) { + // 对历史流程节点进行遍历 + // 得到节点定义的详细信息 + FlowNode activityImpl=null; + FlowElement flowElement1 = bpmnModel.getFlowElement(historicActivityInstances.get(i).getActivityId()); + if(flowElement1 instanceof FlowNode){ + activityImpl = (FlowNode)bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i).getActivityId()); + } + // 用以保存后续开始时间相同的节点 + List sameStartTimeNodes = Lists.newArrayList(); + FlowNode sameActivityImpl1 = null; + + HistoricActivityInstance activityImpl_ = historicActivityInstances.get(i);// 第一个节点 + HistoricActivityInstance activityImp2_ ; + + for(int k = i + 1 ; k <= historicActivityInstances.size() - 1; k++) { + activityImp2_ = historicActivityInstances.get(k);// 后续第1个节点 + //都是usertask,且主节点与后续节点的开始时间相同,说明不是真实的后继节点 + if ( activityImpl_.getActivityType().equals("userTask") && activityImp2_.getActivityType().equals("userTask") && + df.format(activityImpl_.getStartTime()).equals(df.format(activityImp2_.getStartTime())) ) + { + + } + else { + FlowElement flowElement = bpmnModel.getFlowElement(historicActivityInstances.get(k).getActivityId()); + if(flowElement instanceof FlowNode){ + sameActivityImpl1 = (FlowNode)bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(k).getActivityId());//找到紧跟在后面的一个节点 + break; + } + } + + } + 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 (df.format(activityImpl1.getStartTime()).equals(df.format(activityImpl2.getStartTime())) ) + {// 如果第一个节点和第二个节点开始时间相同保存 + FlowElement flowElement = bpmnModel.getFlowElement(activityImpl2.getActivityId()); + if(flowElement instanceof FlowNode){ + FlowNode sameActivityImpl2 = (FlowNode)bpmnModel.getMainProcess().getFlowElement(activityImpl2.getActivityId()); + sameStartTimeNodes.add(sameActivityImpl2); + } + } + else + {// 有不相同跳出循环 + break; + } + } + if(!StringUtils.isEmpty(activityImpl)){ + List pvmTransitions = activityImpl.getOutgoingFlows() ; // 取出节点的所有出去的线 + + for (SequenceFlow pvmTransition : pvmTransitions) + {// 对所有的线进行遍历 + FlowElement flowElement = bpmnModel.getFlowElement(pvmTransition.getTargetRef()); + if(flowElement instanceof FlowNode){ + FlowNode pvmActivityImpl = (FlowNode)bpmnModel.getMainProcess().getFlowElement( pvmTransition.getTargetRef());// 如果取出的线的目标节点存在时间相同的节点里,保存该线的id,进行高亮显示 + if (sameStartTimeNodes.contains(pvmActivityImpl)) { + highFlows.add(pvmTransition.getId()); + } + } + } + } + } + return highFlows; + } }