最新工作流引擎之activiti6.x、activiti5.x实时流程图追踪与高亮显示两种方式实现

在activiti5.x的追踪流程节点查找,可以用ActivityImpl这个类来实现,但是在activiti6版本,pvm包整个类包都被删除,再也没有ActivityImpl这个类。本章节就是教大家如何用activiti6来生成2种方式实时流程图追踪

第一种方式:

1.1 生成流程图核心类

主要是利用ProcessDiagramGenerator这个接口实现类,去实现generateDiagram这个方法。默认可以采用ProcessDiagramGenerator的子类

DefaultProcessDiagramGenerator调用generateDiagram实现流程图。当然也可以自定义相应的子类CustomProcessDiagramGenerator去定制化实现

1.2 生成流程图核心方法

public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, List<String> highLightedFlows, 
      String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor;

1.3  关注的是bpmnmodel,highLightedActivities,highLightedFlows

1.3 关注 bpmnmodel,highLightedActivities,highLightedFlows, 这三个字段值 bpmnmodel:bpmn模型,这样调用

BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());

调用默认api就可以获取 highLightedActivities(需要高亮的执行流程节点集合的获取)、highLightedFlows(需要高亮流程连接线集合的获取)

activiti6如何获取这两个字段 activiti6获取highLightedActivities核心代码片段

// 获取历史流程实例
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            // 获取流程中已经执行的节点,按照执行先后顺序排序
            List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
                    .orderByHistoricActivityInstanceId().asc().list();
            // 高亮已经执行流程节点ID集合
            List<String> highLightedActivitiIds = new ArrayList<>();
            for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                highLightedActivitiIds.add(historicActivityInstance.getActivityId());
            }

 

 

activiti6获取highLightedFlows核心代码片段

/**
     * 获取已经流转的线
     * 
     * @param bpmnModel
     * @param historicActivityInstances
     * @return
     */
    private static List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
        // 高亮流程已发生流转的线id集合
        List<String> highLightedFlowIds = new ArrayList<>();
        // 全部活动节点
        List<FlowNode> historicActivityNodes = new ArrayList<>();
        // 已完成的历史活动节点
        List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();

        for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
            FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
            historicActivityNodes.add(flowNode);
            if (historicActivityInstance.getEndTime() != null) {
                finishedActivityInstances.add(historicActivityInstance);
            }
        }

        FlowNode currentFlowNode = null;
        FlowNode targetFlowNode = null;
        // 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
        for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
            // 获得当前活动对应的节点信息及outgoingFlows信息
            currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
            List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();

            /**
             * 遍历outgoingFlows并找到已已流转的 满足如下条件认为已已流转: 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
             */
            if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
                // 遍历历史活动节点,找到匹配流程目标节点的
                for (SequenceFlow sequenceFlow : sequenceFlows) {
                    targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
                    if (historicActivityNodes.contains(targetFlowNode)) {
                        highLightedFlowIds.add(targetFlowNode.getId());
                    }
                }
            } else {
                List<Map<String, Object>> tempMapList = new ArrayList<>();
                for (SequenceFlow sequenceFlow : sequenceFlows) {
                    for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                        if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
                            Map<String, Object> map = new HashMap<>();
                            map.put("highLightedFlowId", sequenceFlow.getId());
                            map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
                            tempMapList.add(map);
                        }
                    }
                }

                if (!CollectionUtils.isEmpty(tempMapList)) {
                    // 遍历匹配的集合,取得开始时间最早的一个
                    long earliestStamp = 0L;
                    String highLightedFlowId = null;
                    for (Map<String, Object> map : tempMapList) {
                        long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
                        if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {
                            highLightedFlowId = map.get("highLightedFlowId").toString();
                            earliestStamp = highLightedFlowStartTime;
                        }
                    }

                    highLightedFlowIds.add(highLightedFlowId);
                }

            }

        }
        return highLightedFlowIds;
    }

 

activiti6生成流程图代码

/**
     * 根据流程实例Id,获取实时流程图片
     * 
     * @param processInstanceId
     * @param outputStream
     * @return
     */
    public static void getFlowImgByInstanceId(String processInstanceId, OutputStream outputStream) {
        try {
            if (StringUtils.isEmpty(processInstanceId)) {
                logger.error("processInstanceId is null");
                return;
            }
            // 获取历史流程实例
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            // 获取流程中已经执行的节点,按照执行先后顺序排序
            List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
                    .orderByHistoricActivityInstanceId().asc().list();
            // 高亮已经执行流程节点ID集合
            List<String> highLightedActivitiIds = new ArrayList<>();
            for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                highLightedActivitiIds.add(historicActivityInstance.getActivityId());
            }

            List<HistoricProcessInstance> historicFinishedProcessInstances = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).finished()
                    .list();
            ProcessDiagramGenerator processDiagramGenerator = null;
            // 如果还没完成,流程图高亮颜色为绿色,如果已经完成为红色
            if (!CollectionUtils.isEmpty(historicFinishedProcessInstances)) {
                // 如果不为空,说明已经完成
                processDiagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
            } else {
                processDiagramGenerator = new CustomProcessDiagramGenerator();
            }

            BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
            // 高亮流程已发生流转的线id集合
            List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances);

            // 使用默认配置获得流程图表生成器,并生成追踪图片字符流
            InputStream imageStream = processDiagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitiIds, highLightedFlowIds, "宋体", "微软雅黑", "黑体", null, 2.0);

            // 输出图片内容
            byte[] b = new byte[1024];
            int len;
            while ((len = imageStream.read(b, 0, 1024)) != -1) {
                outputStream.write(b, 0, len);
            }
        } catch (Exception e) {
            logger.error("processInstanceId" + processInstanceId + "生成流程图失败,原因:" + e.getMessage(), e);
        }

    }

activiti5.x生成流程图代码

/**
     * 根据流程实例Id,获取实时流程图片
     * 
     * @param processInstanceId
     * @return
     */
    public static InputStream getFlowImgByInstantId(String processInstanceId) {
        if (StringUtils.isEmpty(processInstanceId)) {
            return null;
        }
        // 获取流程图输入流
        InputStream inputStream = null;
        // 查询历史
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (historicProcessInstance.getEndTime() != null) { // 该流程已经结束
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(historicProcessInstance.getProcessDefinitionId())
                    .singleResult();
            inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getDiagramResourceName());
        } else {
            // 查询当前的流程实例
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
            ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity) repositoryService.createProcessDefinitionQuery()
                    .processDefinitionId(processInstance.getProcessDefinitionId()).singleResult();
            List<String> highLightedFlows = new ArrayList<String>();
            List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
                    .orderByHistoricActivityInstanceStartTime().asc().list();
            List<String> historicActivityInstanceList = new ArrayList<String>();
            for (HistoricActivityInstance hai : historicActivityInstances) {
                historicActivityInstanceList.add(hai.getActivityId());
            }
            List<String> highLightedActivities = runtimeService.getActiveActivityIds(processInstanceId);
            historicActivityInstanceList.addAll(highLightedActivities);
            for (ActivityImpl activity : processDefinitionEntity.getActivities()) {
                int index = historicActivityInstanceList.indexOf(activity.getId());
                if (index >= 0 && index + 1 < historicActivityInstanceList.size()) {
                    List<PvmTransition> pvmTransitionList = activity.getOutgoingTransitions();
                    for (PvmTransition pvmTransition : pvmTransitionList) {
                        String destinationFlowId = pvmTransition.getDestination().getId();
                        if (destinationFlowId.equals(historicActivityInstanceList.get(index + 1))) {
                            highLightedFlows.add(pvmTransition.getId());
                        }
                    }
                }
            }
            ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
            List<String> activeActivityIds = new ArrayList<String>();
            List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
            for (org.activiti.engine.task.Task task : tasks) {
                activeActivityIds.add(task.getTaskDefinitionKey());
            }
            inputStream = diagramGenerator.generateDiagram(bpmnModel, "png", activeActivityIds, highLightedFlows, "宋体", "宋体", null, null, 1.0);
        }
        return inputStream;
    }

自定义样式生成流程图思路

activiti提供的样式可能不是特别美观,它的api目前只能改字体大小。如果遇到改颜色等,可以通过重写ProcessDiagramCanvas这个类,并实现ProcessDiagramGenerator这个类的接口

样图:

 

 

第2种方式生成:

2.1 样式类一:class CustomProcessDiagramCanvas

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
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.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;

import org.activiti.bpmn.model.AssociationDirection;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.engine.ActivitiException;
import org.activiti.image.exception.ActivitiImageException;
import org.activiti.image.util.ReflectUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Represents a canvas on which BPMN 2.0 constructs can be drawn. Some of the icons used are licensed under a Creative Commons Attribution 2.5 License, see
 * http://www.famfamfam.com/lab/icons/silk/
 * 
 * @see org.activiti.engine.impl.bpmn.diagram.DefaultProcessDiagramGenerator
 * @author Joram Barrez
 */
public class CustomProcessDiagramCanvas {
	
	protected static final Logger LOGGER = LoggerFactory.getLogger(CustomProcessDiagramCanvas.class);
	
	public enum SHAPE_TYPE {
		Rectangle, Rhombus, Ellipse
	}
	
	// Predefined sized
	protected static final int		ARROW_WIDTH						= 5;
	protected static final int		CONDITIONAL_INDICATOR_WIDTH		= 16;
	protected static final int		DEFAULT_INDICATOR_WIDTH			= 10;
	protected static final int		MARKER_WIDTH					= 12;
	protected static final int		FONT_SIZE						= 11;
	protected static final int		FONT_SPACING					= 2;
	protected static final int		TEXT_PADDING					= 3;
	protected static final int		ANNOTATION_TEXT_PADDING			= 7;
	protected static final int		LINE_HEIGHT						= FONT_SIZE + FONT_SPACING;
	
	// Colors
	protected static Color			TASK_BOX_COLOR					= new Color(249, 249, 249);
	protected static Color			SUBPROCESS_BOX_COLOR			= new Color(255, 255, 255);
	protected static Color			EVENT_COLOR						= new Color(255, 255, 255);
	protected static Color			CONNECTION_COLOR				= new Color(88, 88, 88);
	protected static Color			CONDITIONAL_INDICATOR_COLOR		= new Color(255, 255, 255);
	// protected static Color HIGHLIGHT_COLOR = Color.RED;
	protected static Color			HIGHLIGHT_COLOR					= Color.GREEN;
	protected static Color			LABEL_COLOR						= new Color(112, 146, 190);
	protected static Color			TASK_BORDER_COLOR				= new Color(187, 187, 187);
	protected static Color			EVENT_BORDER_COLOR				= new Color(88, 88, 88);
	protected static Color			SUBPROCESS_BORDER_COLOR			= new Color(0, 0, 0);
	
	// Fonts
	protected static Font			LABEL_FONT						= null;
	protected static Font			ANNOTATION_FONT					= new Font("Arial", Font.PLAIN, FONT_SIZE);
	protected static Font			TASK_FONT						= new Font("Arial", Font.PLAIN, FONT_SIZE);
	
	// Strokes
	protected static Stroke			THICK_TASK_BORDER_STROKE		= new BasicStroke(3.0f);
	protected static Stroke			GATEWAY_TYPE_STROKE				= new BasicStroke(3.0f);
	protected static Stroke			END_EVENT_STROKE				= new BasicStroke(3.0f);
	protected static Stroke			MULTI_INSTANCE_STROKE			= new BasicStroke(1.3f);
	protected static Stroke			EVENT_SUBPROCESS_STROKE			= new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[] { 1.0f }, 0.0f);
	protected static Stroke			NON_INTERRUPTING_EVENT_STROKE	= new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[] { 4.0f, 3.0f }, 0.0f);
	protected static Stroke			HIGHLIGHT_FLOW_STROKE			= new BasicStroke(1.3f);
	protected static Stroke			ANNOTATION_STROKE				= new BasicStroke(2.0f);
	protected static Stroke			ASSOCIATION_STROKE				= new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[] { 2.0f, 2.0f }, 0.0f);
	
	// icons
	protected static int			ICON_PADDING					= 5;
	protected static BufferedImage	USERTASK_IMAGE;
	protected static BufferedImage	SCRIPTTASK_IMAGE;
	protected static BufferedImage	SERVICETASK_IMAGE;
	protected static BufferedImage	RECEIVETASK_IMAGE;
	protected static BufferedImage	SENDTASK_IMAGE;
	protected static BufferedImage	MANUALTASK_IMAGE;
	protected static BufferedImage	BUSINESS_RULE_TASK_IMAGE;
	protected static BufferedImage	SHELL_TASK_IMAGE;
	protected static BufferedImage	MULE_TASK_IMAGE;
	protected static BufferedImage	CAMEL_TASK_IMAGE;
	
	protected static BufferedImage	TIMER_IMAGE;
	protected static BufferedImage	COMPENSATE_THROW_IMAGE;
	protected static BufferedImage	COMPENSATE_CATCH_IMAGE;
	protected static BufferedImage	ERROR_THROW_IMAGE;
	protected static BufferedImage	ERROR_CATCH_IMAGE;
	protected static BufferedImage	MESSAGE_THROW_IMAGE;
	protected static BufferedImage	MESSAGE_CATCH_IMAGE;
	protected static BufferedImage	SIGNAL_CATCH_IMAGE;
	protected static BufferedImage	SIGNAL_THROW_IMAGE;
	
	protected int					canvasWidth						= -1;
	protected int					canvasHeight					= -1;
	protected int					minX							= -1;
	protected int					minY							= -1;
	protected BufferedImage			processDiagram;
	protected Graphics2D			g;
	protected FontMetrics			fontMetrics;
	protected boolean				closed;
	protected ClassLoader			customClassLoader;
	protected String				activityFontName				= "Arial";
	protected String				labelFontName					= "Arial";
	
	/**
	 * Creates an empty canvas with given width and height. Allows to specify minimal boundaries on the left and upper side of the canvas. This is useful for diagrams that have
	 * white space there. Everything beneath these minimum values will be cropped. It's also possible to pass a specific font name and a class loader for the icon images.
	 */
	public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, String activityFontName, String labelFontName, ClassLoader customClassLoader) {
		
		this.canvasWidth = width;
		this.canvasHeight = height;
		this.minX = minX;
		this.minY = minY;
		if (activityFontName != null) {
			this.activityFontName = activityFontName;
		}
		if (labelFontName != null) {
			this.labelFontName = labelFontName;
		}
		this.customClassLoader = customClassLoader;
		
		initialize(imageType);
	}
	
	/**
	 * Creates an empty canvas with given width and height. Allows to specify minimal boundaries on the left and upper side of the canvas. This is useful for diagrams that have
	 * white space there (eg Signavio). Everything beneath these minimum values will be cropped.
	 * 
	 * @param minX
	 *            Hint that will be used when generating the image. Parts that fall below minX on the horizontal scale will be cropped.
	 * @param minY
	 *            Hint that will be used when generating the image. Parts that fall below minX on the horizontal scale will be cropped.
	 */
	public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType) {
		this.canvasWidth = width;
		this.canvasHeight = height;
		this.minX = minX;
		this.minY = minY;
		
		initialize(imageType);
	}
	
	public void initialize(String imageType) {
		if ("png".equalsIgnoreCase(imageType)) {
			this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);
		} else {
			this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_RGB);
		}
		
		this.g = processDiagram.createGraphics();
		if ("png".equalsIgnoreCase(imageType) == false) {
			this.g.setBackground(new Color(255, 255, 255, 0));
			this.g.clearRect(0, 0, canvasWidth, canvasHeight);
		}
		
		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);
		
		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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值