background
Record the functions commonly used in project requirements and their implementation methods. This is the second part, which will continue to be recorded later. This article is based on activiti6 0.0 principle: business data must be separated from approval process data, such as notes, documents, etc.
[process tracking] obtain the process definition diagram
The acquisition code is as follows:
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("leave") .singleResult(); String diagramResourceName = processDefinition.getDiagramResourceName(); InputStream imageStream = repositoryService.getResourceAsStream( processDefinition.getDeploymentId(), diagramResourceName); Copy code
1. Automatically generate flow definition diagram
If only xml Definition, if there is no picture defined, Activiti The process engine automatically generates an image,The generated code may be garbled. Copy code
The generated schematic diagram is as follows, from the official website
Garbled code problem solving:
@Configuration public class ActivitiConfig implements ProcessEngineConfigurationConfigurer { /** * Solve the problem of image garbled generated by workflow * * @param processEngineConfiguration processEngineConfiguration */ @Override public void configure(SpringProcessEngineConfiguration processEngineConfiguration) { processEngineConfiguration.setActivityFontName("Song typeface"); processEngineConfiguration.setAnnotationFontName("Song typeface"); processEngineConfiguration.setLabelFontName("Song typeface"); } } Copy code
reference resources: blog.csdn.net/qq_27291799... Turn off automatic generation: if, for some reason, it is not necessary or unnecessary to generate a process definition picture during deployment, you need to use iscreatediagrarondeploy in the properties configured by the process engine:
<property name="createDiagramOnDeploy" value="false" /> Copy code
The process definition picture will not be generated now.
2. Provide customized pictures during deployment
Deployment method:
repositoryService.createDeployment() .key("leave") .name("Leave process") .addClasspathResource("processes/leave.bpmn20.xml") .addClasspathResource("processes/leave.png") .deploy(); Next, you can API To get the process definition picture resource: ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("leave") .singleResult(); Copy code
[process tracking] obtain the highlighted activity flow chart
1. Highlight the node in progress
Pay attention to setting the font, otherwise it will be garbled
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("leave") .singleResult(); BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());// Model List<String> highLightedActivities = runtimeService.getActiveActivityIds("2501");// Highlight node List<String> highLightedFlows = new ArrayList<>(); // Highlight connector ProcessDiagramGenerator processDiagramGenerator = new DefaultProcessDiagramGenerator(); InputStream png = processDiagramGenerator.generateDiagram (bpmnModel, "png",highLightedActivities, highLightedFlows, "Song typeface", "Microsoft YaHei ", "Blackbody", null, 2.0); Copy code
The example on the official website only highlights the nodes in progress by default
2. Highlight the node in progress + the processed connection line
Let's add the highlight of the connection line and all the processed nodes
List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .orderByHistoricActivityInstanceId().asc().list(); List<String> highLightedFlows = getHighLightedFlows(bpmnModel, historicActivityInstances);// Get processed connector Copy code
reference resources: 791202.com/2020/03/30/...
/** * Get the transferred lines * * @param bpmnModel * @param historicActivityInstances * @return */ private static List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) { // Highlight the line id collection of the process flow List<String> highLightedFlowIds = new ArrayList<>(); // All active nodes List<FlowNode> historicActivityNodes = new ArrayList<>(); // Completed historical activity node 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; // Traverse the completed activity instances and find the executed activity from the outgoingFlows of each instance for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) { // Obtain the node information and outgoingFlows information corresponding to the current activity currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true); List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows(); /** * Traverse the outgoingFlows and find those that have been transferred. It is considered to have been transferred if the following conditions are met: 1 If the current node is a parallel gateway or a compatible gateway, all nodes that can be found in the historical activity through outgoingFlows are transferred 2 The current node is different from the above two types. The earliest flow node found through outgoingFlows is regarded as a valid flow */ if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) { // Traverse the historical activity node to find the matching process target node 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)) { // Traverse the matching set and get the one with the earliest start time 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; } Copy code
3. Highlight the node in progress + processed connector + processed node
// Get the nodes that have been executed in the process and sort them according to the execution order List<HistoricActivityInstance> historicActivityInstances = historyService. createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .orderByHistoricActivityInstanceId().asc().list(); // Highlight the process node ID collection that has been executed List<String> highLightedActivitiIds = new ArrayList<>(); for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) { highLightedActivitiIds.add(historicActivityInstance.getActivityId()); } Copy code
4. Custom business color matching
The principle of drawing is to call the following methods and expand one wave.
ProcessDiagramGenerator processDiagramGenerator = new DefaultProcessDiagramGenerator(); processDiagramGenerator.generateDiagram() Copy code
The core method is defaultprocessdiagramgenerator Drawactivity rewrite it
protected void drawActivity(DefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode, List<String> highLightedActivities, List<String> highLightedFlows, double scaleFactor) { Copy code
All codes are as follows: 2 classes are involved:
LakerProcessDiagramCanvas
Inherits DefaultProcessDiagramCanvas
- Override defines these 2 values
HIGHLIGHT_COLOR = Color.cyan;// Default color, previously red THICK_TASK_BORDER_STROKE = new BasicStroke(6.0f);// Border width before 3.0 Copy code
- A new method for passing in colors is defined
public void drawHighLightColor(int x, int y, int width, int height, Color color) { Paint originalPaint = g.getPaint(); Stroke originalStroke = g.getStroke(); g.setPaint(color); g.setStroke(THICK_TASK_BORDER_STROKE); RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20); g.draw(rect); g.setPaint(originalPaint); g.setStroke(originalStroke); } Copy code
LakerProcessDiagramGenerator
Inherits the DefaultProcessDiagramGenerator
- Rewritten initProcessDiagramCanvas to return to our extended LakerProcessDiagramCanvas
return new LakerProcessDiagramCanvas((int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader); Copy code
- Rewrites drawActivity, and the last node is displayed in custom color
// Draw the highlighted node TODO if (highLightedActivities.contains(flowNode.getId())) { if (highLightedActivities.get(highLightedActivities.size() - 1).equalsIgnoreCase(flowNode.getId())) { LakerProcessDiagramCanvas lakerProcessDiagramCanvas = ((LakerProcessDiagramCanvas) processDiagramCanvas); lakerProcessDiagramCanvas.drawHighLightColor((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(), Color.YELLOW); } else { processDiagramCanvas.drawHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight()); } } Copy code
All source code
- Inherit DefaultProcessDiagramCanvas
package com.laker.workflow.controller; import org.activiti.image.impl.DefaultProcessDiagramCanvas; import java.awt.*; import java.awt.geom.RoundRectangle2D; public class LakerProcessDiagramCanvas extends DefaultProcessDiagramCanvas { public LakerProcessDiagramCanvas(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); HIGHLIGHT_COLOR = Color.cyan; THICK_TASK_BORDER_STROKE = new BasicStroke(6.0f); } public LakerProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType) { super(width, height, minX, minY, imageType); HIGHLIGHT_COLOR = Color.cyan; THICK_TASK_BORDER_STROKE = new BasicStroke(6.0f); } public void drawHighLightColor(int x, int y, int width, int height, Color color) { Paint originalPaint = g.getPaint(); Stroke originalStroke = g.getStroke(); g.setPaint(color); g.setStroke(THICK_TASK_BORDER_STROKE); RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20); g.draw(rect); g.setPaint(originalPaint); g.setStroke(originalStroke); } } Copy code
- Inherit DefaultProcessDiagramGenerator
package com.laker.workflow.controller; import org.activiti.bpmn.model.Process; import org.activiti.bpmn.model.*; import org.activiti.image.impl.DefaultProcessDiagramCanvas; import org.activiti.image.impl.DefaultProcessDiagramGenerator; import java.awt.*; import java.awt.image.BufferedImage; import java.util.List; public class LakerProcessDiagramGenerator extends DefaultProcessDiagramGenerator { private BufferedImage processDiagram; @Override protected DefaultProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor) { { prepareBpmnModel(bpmnModel); DefaultProcessDiagramCanvas 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 (FlowNode flowNode : bpmnModel.getProcesses().get(0).findFlowElementsOfType(FlowNode.class)) { drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedActivities, highLightedFlows, scaleFactor); } for (Process process : bpmnModel.getProcesses()) { for (FlowNode flowNode : process.findFlowElementsOfType(FlowNode.class)) { drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedActivities, highLightedFlows, scaleFactor); } } // 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; } } @Override protected void drawActivity(DefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode, List<String> highLightedActivities, List<String> highLightedFlows, double scaleFactor) { { 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 the highlighted node TODO if (highLightedActivities.contains(flowNode.getId())) { if (highLightedActivities.get(highLightedActivities.size() - 1).equalsIgnoreCase(flowNode.getId())) { LakerProcessDiagramCanvas lakerProcessDiagramCanvas = ((LakerProcessDiagramCanvas) processDiagramCanvas); lakerProcessDiagramCanvas.drawHighLightColor((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(), Color.YELLOW); } else { processDiagramCanvas.drawHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight()); } } } // Outgoing transitions of activity for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) { boolean highLighted = (highLightedFlows.contains(sequenceFlow.getId())); 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(sequenceFlow.getId())) { 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(sequenceFlow.getId()); 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, drawConditionalIndicator, isDefault, highLighted, scaleFactor); // Draw sequenceflow label GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(sequenceFlow.getId()); if (labelGraphicInfo != null) { processDiagramCanvas.drawLabel(sequenceFlow.getName(), labelGraphicInfo, false); } } } // 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 static DefaultProcessDiagramCanvas 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 LakerProcessDiagramCanvas((int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader); } } Copy code
reference resources: blog.csdn.net/u010740917/...
[process tracking] the task flow path of the process
List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery() .processInstanceId("2515") .orderByHistoricTaskInstanceEndTime().asc().list(); Copy code
SELECT DISTINCT RES.* FROM ACT_HI_TASKINST RES WHERE RES.PROC_INST_ID_ = '2515' ORDER BY RES.END_TIME_ ASC LIMIT '2147483647' OFFSET '0' Copy code
[process tracking] set the process title
For example, the process title is "Zhang San's leave form"
runtimeService.setProcessInstanceName(processInstance.getId(),"Zhang San's leave slip"); When querying runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); Copy code
act_ru_execution,act_hi_procinst [name_] Field
Author: lakernote
Link: https://juejin.cn/post/6906322920855830542
Source: Nuggets
The copyright belongs to the author. For commercial reprint, please contact the author for authorization, and for non-commercial reprint, please indicate the source.