dangsn
2023-08-02 846cfb6ad413aeff93b3181e0bf69f53c6434001
新增通用生命周期变更事件
已修改1个文件
已添加4个文件
1295 ■■■■■ 文件已修改
Source/UBCS/ubcs-ops-api/ubcs-flow-api/src/main/java/com/vci/ubcs/flow/core/dto/FlowStatusDTO.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-ops-api/ubcs-flow-api/src/main/java/com/vci/ubcs/flow/core/vo/FlowTaskHisVO.java 345 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/constant/FlowEngineConstant.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/envent/FlowStatusListener.java 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/utils/FlowableUtils.java 727 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-ops-api/ubcs-flow-api/src/main/java/com/vci/ubcs/flow/core/dto/FlowStatusDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
package com.vci.ubcs.flow.core.dto;
import com.vci.ubcs.flow.core.vo.FlowTaskHisVO;
import java.util.List;
import java.util.Map;
/**
 * æµç¨‹çš„æ‰§è¡Œä¿¡æ¯
 * @author dangsn
 * @Date 2023/07/31
 */
public class FlowStatusDTO implements java.io.Serializable{
    /**
     * åºåˆ—化
     */
    private static final long serialVersionUID = 7539176109676429282L;
    /**
     * ä¸šåŠ¡ç±»åž‹
     */
    private String btmType;
    /**
     * æ•°æ®çš„主键
     */
    private List<String> oids;
    /**
     * å˜é‡çš„内容
     */
    private Map<String,Object> variableMap;
    /**
     * æ‰§è¡ŒåŽ†å²
     */
    private List<FlowTaskHisVO> taskHisVOList;
    public String getBtmType() {
        return btmType;
    }
    public void setBtmType(String btmType) {
        this.btmType = btmType;
    }
    public List<String> getOids() {
        return oids;
    }
    public void setOids(List<String> oids) {
        this.oids = oids;
    }
    public Map<String, Object> getVariableMap() {
        return variableMap;
    }
    public void setVariableMap(Map<String, Object> variableMap) {
        this.variableMap = variableMap;
    }
    public List<FlowTaskHisVO> getTaskHisVOList() {
        return taskHisVOList;
    }
    public void setTaskHisVOList(List<FlowTaskHisVO> taskHisVOList) {
        this.taskHisVOList = taskHisVOList;
    }
    @Override
    public String toString() {
        return "FlowStatusDTO{" +
            "btmType='" + btmType + '\'' +
            ", oids=" + oids +
            ", variableMap=" + variableMap +
            ", taskHisVOList=" + taskHisVOList +
            '}';
    }
}
Source/UBCS/ubcs-ops-api/ubcs-flow-api/src/main/java/com/vci/ubcs/flow/core/vo/FlowTaskHisVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,345 @@
package com.vci.ubcs.flow.core.vo;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
 * æµç¨‹ä»»åŠ¡åŽ†å²æ˜¾ç¤ºå¯¹è±¡
 * @author dangsn
 * @date 2023/07/31
 */
public class FlowTaskHisVO implements java.io.Serializable{
    /**
     * ç¦æ­¢ä¿®æ”¹è¿™ä¸ªå€¼
     */
    private static final long serialVersionUID = -5305339505741718337L;
    /**
     * åŽ†å²è®°å½•ä¸»é”®
     */
    private String oid;
    /**
     * æ´»åЍ䏻键
     */
    private String activityId;
    /**
     * ä»»åŠ¡åç§°
     */
    private String activityName;
    /**
     * ä»»åŠ¡ç±»åž‹
     */
    private String activityType;
    /**
     * æµç¨‹å®šä¹‰ä¸»é”®
     */
    private String processDefinitionId;
    /**
     * æµç¨‹å®žä¾‹ä¸»é”®
     */
    private String processInstanceId;
    /**
     * æµç¨‹çš„名称
     */
    private String processName;
    /**
     * æµç¨‹æ¨¡æ¿åç§°
     */
    private String processDefinitionName;
    /**
     * æ‰§è¡Œä¸»é”®
     */
    private String executionId;
    /**
     * ä»»åС䏻键
     */
    private String taskId;
    /**
     * è°ƒç”¨æµç¨‹ä¸»é”®
     */
    private String calledProcessInstanceId;
    /**
     * æ‰§è¡Œäººè´¦å·
     */
    private String assignee;
    /**
     * æ‰§è¡Œäººå§“名
     */
    private String assigneeName;
    /**
     * å¼€å§‹æ—¶é—´
     */
    private Date startTime;
    /**
     * å®Œæˆæ—¶é—´
     */
    private Date endTime;
    /**
     * è€—æ—¶
     */
    private Long durationInMillis;
    /**
     * åˆ é™¤åŽŸå› 
     */
    private String deleteReason;
    /**
     * ç§Ÿæˆ·ä¸»é”®
     */
    private String tenantId;
    /**
     * å®¡æ‰¹æ„è§
     */
    private String description;
    /**
     * å˜é‡
     */
    private Map<String,Object> variablesMap;
    /**
     * å¤šæ¡æ•°æ®æ—¶å…³è”的业务数据主键
     */
    private List<String> linkBusinessOids;
    /**
     * æ˜¾ç¤ºè·¯å¾„
     */
    private String displayUrl;
    /**
     * å‘起流程的业务类型
     */
    private String btmType;
    public String getProcessName() {
        return processName;
    }
    public void setProcessName(String processName) {
        this.processName = processName;
    }
    public String getProcessDefinitionName() {
        return processDefinitionName;
    }
    public void setProcessDefinitionName(String processDefinitionName) {
        this.processDefinitionName = processDefinitionName;
    }
    public String getBtmType() {
        return btmType;
    }
    public void setBtmType(String btmType) {
        this.btmType = btmType;
    }
    public String getOid() {
        return oid;
    }
    public void setOid(String oid) {
        this.oid = oid;
    }
    public List<String> getLinkBusinessOids() {
        return linkBusinessOids;
    }
    public void setLinkBusinessOids(List<String> linkBusinessOids) {
        this.linkBusinessOids = linkBusinessOids;
    }
    public String getDisplayUrl() {
        return displayUrl;
    }
    public void setDisplayUrl(String displayUrl) {
        this.displayUrl = displayUrl;
    }
    public String getActivityId() {
        return activityId;
    }
    public void setActivityId(String activityId) {
        this.activityId = activityId;
    }
    public String getActivityName() {
        return activityName;
    }
    public void setActivityName(String activityName) {
        this.activityName = activityName;
    }
    public String getActivityType() {
        return activityType;
    }
    public void setActivityType(String activityType) {
        this.activityType = activityType;
    }
    public String getProcessDefinitionId() {
        return processDefinitionId;
    }
    public void setProcessDefinitionId(String processDefinitionId) {
        this.processDefinitionId = processDefinitionId;
    }
    public String getProcessInstanceId() {
        return processInstanceId;
    }
    public void setProcessInstanceId(String processInstanceId) {
        this.processInstanceId = processInstanceId;
    }
    public String getExecutionId() {
        return executionId;
    }
    public void setExecutionId(String executionId) {
        this.executionId = executionId;
    }
    public String getTaskId() {
        return taskId;
    }
    public void setTaskId(String taskId) {
        this.taskId = taskId;
    }
    public String getCalledProcessInstanceId() {
        return calledProcessInstanceId;
    }
    public void setCalledProcessInstanceId(String calledProcessInstanceId) {
        this.calledProcessInstanceId = calledProcessInstanceId;
    }
    public String getAssignee() {
        return assignee;
    }
    public void setAssignee(String assignee) {
        this.assignee = assignee;
    }
    public String getAssigneeName() {
        return assigneeName;
    }
    public void setAssigneeName(String assigneeName) {
        this.assigneeName = assigneeName;
    }
    public Date getStartTime() {
        return startTime;
    }
    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }
    public Date getEndTime() {
        return endTime;
    }
    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }
    public Long getDurationInMillis() {
        return durationInMillis;
    }
    public void setDurationInMillis(Long durationInMillis) {
        this.durationInMillis = durationInMillis;
    }
    public String getDeleteReason() {
        return deleteReason;
    }
    public void setDeleteReason(String deleteReason) {
        this.deleteReason = deleteReason;
    }
    public String getTenantId() {
        return tenantId;
    }
    public void setTenantId(String tenantId) {
        this.tenantId = tenantId;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public Map<String, Object> getVariablesMap() {
        return variablesMap;
    }
    public void setVariablesMap(Map<String, Object> variablesMap) {
        this.variablesMap = variablesMap;
    }
    @Override
    public String toString() {
        return "FlowTaskHisVO{" +
            "oid='" + oid + '\'' +
            ", activityId='" + activityId + '\'' +
            ", activityName='" + activityName + '\'' +
            ", activityType='" + activityType + '\'' +
            ", processDefinitionId='" + processDefinitionId + '\'' +
            ", processInstanceId='" + processInstanceId + '\'' +
            ", processName='" + processName + '\'' +
            ", processDefinitionName='" + processDefinitionName + '\'' +
            ", executionId='" + executionId + '\'' +
            ", taskId='" + taskId + '\'' +
            ", calledProcessInstanceId='" + calledProcessInstanceId + '\'' +
            ", assignee='" + assignee + '\'' +
            ", assigneeName='" + assigneeName + '\'' +
            ", startTime=" + startTime +
            ", endTime=" + endTime +
            ", durationInMillis=" + durationInMillis +
            ", deleteReason='" + deleteReason + '\'' +
            ", tenantId='" + tenantId + '\'' +
            ", description='" + description + '\'' +
            ", variablesMap=" + variablesMap +
            ", linkBusinessOids=" + linkBusinessOids +
            ", displayUrl='" + displayUrl + '\'' +
            ", btmType='" + btmType + '\'' +
            '}';
    }
}
Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/constant/FlowEngineConstant.java
@@ -51,4 +51,24 @@
    String USR_TASK = "userTask";
    /**
     * çŠ¶æ€å€¼
     */
    String STATUS_VALUE = "statusValue";
    /**
     * è¿œç¨‹è°ƒç”¨çš„地址
     */
    String REMOTE_METHOD = "remoteMethod";
    /**
     * ä¸»é”®
     */
    String OIDS = "oids";
    /**
     * ä¸šåŠ¡ç±»åž‹
     */
    String BTMTYPE = "btmtype";
}
Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/envent/FlowStatusListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,123 @@
package com.vci.ubcs.flow.engine.envent;
import com.vci.ubcs.flow.core.constant.ProcessConstant;
import com.vci.ubcs.flow.core.dto.FlowStatusDTO;
import com.vci.ubcs.flow.engine.constant.FlowEngineConstant;
import com.vci.ubcs.flow.engine.utils.FlowableUtils;
import com.vci.ubcs.starter.exception.VciBaseException;
import com.vci.ubcs.starter.web.util.LangBaseUtil;
import com.vci.ubcs.starter.web.util.VciBaseUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.engine.HistoryService;
import org.flowable.engine.TaskService;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.engine.impl.el.FixedValue;
import org.flowable.task.service.delegate.DelegateTask;
import org.springblade.core.jwt.JwtUtil;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Slf4j
@Component
public class FlowStatusListener implements TaskListener, ApplicationContextAware {
    /**
     * è¿œç¨‹è°ƒç”¨åœ°å€ã€‚切记:名称要与流程中定义的一样
     */
    private FixedValue remoteMethod;
    /**
     * çŠ¶æ€å€¼ã€‚åˆ‡è®°ï¼šåç§°è¦ä¸Žæµç¨‹ä¸­å®šä¹‰çš„ä¸€æ ·
     */
    private FixedValue statusValue;
    private static  ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext arg0) throws BeansException {
        applicationContext = arg0;
    }
    @Override
    public void notify(DelegateTask delegateTask) {
        Map taskVariable = delegateTask.getVariables();
        boolean pass = (boolean) taskVariable.get(ProcessConstant.PASS_KEY);
        //获取状态修改信息
        if(pass){
            String restURL = remoteMethod.getExpressionText();
            String status = statusValue.getExpressionText();
            //获取业务数据信息
            String oids = (String) taskVariable.get(FlowEngineConstant.OIDS);
            String btmType = (String) taskVariable.get(FlowEngineConstant.BTMTYPE);
            taskVariable.put(FlowEngineConstant.REMOTE_METHOD,restURL);
            taskVariable.put(FlowEngineConstant.STATUS_VALUE,status);
            if(StringUtils.isEmpty(oids)){
                throw new VciBaseException("执行状态修改事件时,业务数据oid为空!");
            }
            if(StringUtils.isEmpty(btmType)){
                throw new VciBaseException("执行状态修改事件时,业务类型btmType为空!");
            }
            if(StringUtils.isEmpty(restURL)){
                throw new VciBaseException("执行状态修改事件时,远程调用地址为空!");
            }
            if(StringUtils.isEmpty(status)){
                throw new VciBaseException("执行状态修改事件时,状态为空!");
            }
            HistoryService historyService = applicationContext.getBean(HistoryService.class);
            TaskService taskService = applicationContext.getBean(TaskService.class);
            FlowStatusDTO flowStatusDTO = new FlowStatusDTO();
            flowStatusDTO.setBtmType(btmType);
            flowStatusDTO.setOids(VciBaseUtil.str2List(oids));
            flowStatusDTO.setVariableMap(taskVariable);
            flowStatusDTO.setTaskHisVOList(FlowableUtils.listTaskHistory(delegateTask.getProcessInstanceId(),historyService,taskService));
            String token = JwtUtil.getToken(WebUtil.getRequest().getHeader(TokenConstant.HEADER));
            HttpComponentsClientHttpRequestFactory requestFactory=new HttpComponentsClientHttpRequestFactory();
            requestFactory.setReadTimeout(300000);
            requestFactory.setConnectionRequestTimeout(300000);
            requestFactory.setConnectTimeout(300000);
            RestTemplate restTemplate = new RestTemplate(requestFactory);
            HttpHeaders headers = new HttpHeaders();
            headers.add(TokenConstant.HEADER,token);
            headers.setContentType(MediaType.APPLICATION_JSON);
            HttpEntity httpEntity = new HttpEntity<>(flowStatusDTO,headers);
            Map<String, Object> result = null;
            try {
                result = restTemplate.postForObject(restURL, httpEntity, Map.class);
            } catch (HttpClientErrorException e) {
                throw new VciBaseException(LangBaseUtil.getErrorMsg(e),new String[]{},e);
            }catch (Throwable e){
                throw new VciBaseException(LangBaseUtil.getErrorMsg(e),new String[]{},e);
            }
            if(result == null){
                throw new VciBaseException("业务事件时候没有返回值,不确定是否执行成功");
            }
            if(CollectionUtils.isEmpty(result) && !(Boolean) result.get("success")){
                throw new VciBaseException((String) result.get("message"));
            }
        }
    }
}
Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/utils/FlowableUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,727 @@
package com.vci.ubcs.flow.engine.utils;
import com.vci.ubcs.flow.core.utils.TaskUtil;
import com.vci.ubcs.flow.core.vo.FlowTaskHisVO;
import com.vci.ubcs.flow.engine.constant.FlowEngineConstant;
import com.vci.ubcs.starter.exception.VciBaseException;
import com.vci.ubcs.starter.web.util.VciBaseUtil;
import com.vci.ubcs.system.user.cache.UserCache;
import com.vci.ubcs.system.user.entity.User;
import com.vci.ubcs.system.user.vo.UserVO;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.*;
import org.flowable.engine.HistoryService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.flowable.engine.task.Comment;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author weidy
 * @date 2021/4/2
 */
public class FlowableUtils {
    /**
     * æ ¹æ®èŠ‚ç‚¹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
     * @param source èŠ‚ç‚¹
     * @return åŒ…含的线
     */
    public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
        List<SequenceFlow> sequenceFlows = null;
        if (source instanceof Task) {
            sequenceFlows = ((Task) source).getIncomingFlows();
        } else if (source instanceof Gateway) {
            sequenceFlows = ((Gateway) source).getIncomingFlows();
        } else if (source instanceof SubProcess) {
            sequenceFlows = ((SubProcess) source).getIncomingFlows();
        } else if (source instanceof StartEvent) {
            sequenceFlows = ((StartEvent) source).getIncomingFlows();
        } else if (source instanceof EndEvent) {
            sequenceFlows = ((EndEvent) source).getIncomingFlows();
        }
        return sequenceFlows;
    }
    /**
     * æ ¹æ®èŠ‚ç‚¹ï¼ŒèŽ·å–å‡ºå£è¿žçº¿
     * @param source èŠ‚ç‚¹
     * @return å‡ºå£çš„线
     */
    public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
        List<SequenceFlow> sequenceFlows = null;
        if (source instanceof Task) {
            sequenceFlows = ((Task) source).getOutgoingFlows();
        } else if (source instanceof Gateway) {
            sequenceFlows = ((Gateway) source).getOutgoingFlows();
        } else if (source instanceof SubProcess) {
            sequenceFlows = ((SubProcess) source).getOutgoingFlows();
        } else if (source instanceof StartEvent) {
            sequenceFlows = ((StartEvent) source).getOutgoingFlows();
        } else if (source instanceof EndEvent) {
            sequenceFlows = ((EndEvent) source).getOutgoingFlows();
        }
        return sequenceFlows;
    }
    /**
     * èŽ·å–å…¨éƒ¨èŠ‚ç‚¹åˆ—è¡¨ï¼ŒåŒ…å«å­æµç¨‹èŠ‚ç‚¹
     * @param flowElements èŠ‚ç‚¹
     * @param allElements æ‰€æœ‰çš„节点
     * @return èŠ‚ç‚¹çš„å†…å®¹
     */
    public static Collection<FlowElement> getAllElements(Collection<FlowElement> flowElements, Collection<FlowElement> allElements) {
        allElements = allElements == null ? new ArrayList<>() : allElements;
        for (FlowElement flowElement : flowElements) {
            allElements.add(flowElement);
            if (flowElement instanceof SubProcess) {
                // ç»§ç»­æ·±å…¥å­æµç¨‹ï¼Œè¿›ä¸€æ­¥èŽ·å–å­æµç¨‹
                allElements = FlowableUtils.getAllElements(((SubProcess) flowElement).getFlowElements(), allElements);
            }
        }
        return allElements;
    }
    /**
     * è¿­ä»£èŽ·å–çˆ¶çº§ä»»åŠ¡èŠ‚ç‚¹åˆ—è¡¨ï¼Œå‘å‰æ‰¾
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param userTaskList å·²æ‰¾åˆ°çš„用户任务节点
     * @return
     */
    public static List<UserTask> iteratorFindParentUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        // å¦‚果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            userTaskList = iteratorFindParentUserTasks(source.getSubProcess(), hasSequenceFlow, userTaskList);
        }
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // ç±»åž‹ä¸ºç”¨æˆ·èŠ‚ç‚¹ï¼Œåˆ™æ–°å¢žçˆ¶çº§èŠ‚ç‚¹
                if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
                    userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
                    continue;
                }
                // ç±»åž‹ä¸ºå­æµç¨‹ï¼Œåˆ™æ·»åŠ å­æµç¨‹å¼€å§‹èŠ‚ç‚¹å‡ºå£å¤„ç›¸è¿žçš„èŠ‚ç‚¹
                if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
                    // èŽ·å–å­æµç¨‹ç”¨æˆ·ä»»åŠ¡èŠ‚ç‚¹
                    List<UserTask> childUserTaskList = findChildProcessUserTasks((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
                    // å¦‚果找到节点,则说明该线路找到节点,不继续向下找,反之继续
                    if (childUserTaskList != null && childUserTaskList.size() > 0) {
                        userTaskList.addAll(childUserTaskList);
                        continue;
                    }
                }
                // ç»§ç»­è¿­ä»£
                // æ³¨æ„ï¼šå·²ç»ç»è¿‡çš„节点与连线都应该用浅拷贝出来的对象
                // æ¯”如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
                userTaskList = iteratorFindParentUserTasks(sequenceFlow.getSourceFlowElement(), new HashSet<>(hasSequenceFlow), userTaskList);
            }
        }
        return userTaskList;
    }
    /**
     * æ ¹æ®æ­£åœ¨è¿è¡Œçš„任务节点,迭代获取子级任务节点列表,向后找
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param runActiveIdList æ­£åœ¨è¿è¡Œçš„任务 Key,用于校验任务节点是否是正在运行的节点
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param flowElementList éœ€è¦æ’¤å›žçš„用户任务列表
     * @return èŠ‚ç‚¹çš„ä¿¡æ¯
     */
    public static List<FlowElement> iteratorFindChildUserTasks(FlowElement source, List<String> runActiveIdList, Set<String> hasSequenceFlow, List<FlowElement> flowElementList) {
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        flowElementList = flowElementList == null ? new ArrayList<>() : flowElementList;
        // å¦‚果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
        if (source instanceof EndEvent && source.getSubProcess() != null) {
            flowElementList = iteratorFindChildUserTasks(source.getSubProcess(), runActiveIdList, hasSequenceFlow, flowElementList);
        }
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å‡ºå£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // å¦‚果为用户任务类型,或者为网关
                // æ´»åŠ¨èŠ‚ç‚¹ID åœ¨è¿è¡Œçš„任务中存在,添加
                if ((sequenceFlow.getTargetFlowElement() instanceof UserTask || sequenceFlow.getTargetFlowElement() instanceof Gateway) && runActiveIdList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
                    flowElementList.add(sequenceFlow.getTargetFlowElement());
                    continue;
                }
                // å¦‚果节点为子流程节点情况,则从节点中的第一个节点开始获取
                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
                    List<FlowElement> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runActiveIdList, hasSequenceFlow, null);
                    // å¦‚果找到节点,则说明该线路找到节点,不继续向下找,反之继续
                    if (childUserTaskList != null && childUserTaskList.size() > 0) {
                        flowElementList.addAll(childUserTaskList);
                        continue;
                    }
                }
                // ç»§ç»­è¿­ä»£
                // æ³¨æ„ï¼šå·²ç»ç»è¿‡çš„节点与连线都应该用浅拷贝出来的对象
                // æ¯”如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
                flowElementList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runActiveIdList, new HashSet<>(hasSequenceFlow), flowElementList);
            }
        }
        return flowElementList;
    }
    /**
     * è¿­ä»£èŽ·å–å­æµç¨‹ç”¨æˆ·ä»»åŠ¡èŠ‚ç‚¹
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param userTaskList éœ€è¦æ’¤å›žçš„用户任务列表
     * @return ä»»åŠ¡
     */
    public static List<UserTask> findChildProcessUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å‡ºå£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // å¦‚果为用户任务类型,且任务节点的 Key æ­£åœ¨è¿è¡Œçš„任务中存在,添加
                if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
                    userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
                    continue;
                }
                // å¦‚果节点为子流程节点情况,则从节点中的第一个节点开始获取
                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
                    List<UserTask> childUserTaskList = findChildProcessUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
                    // å¦‚果找到节点,则说明该线路找到节点,不继续向下找,反之继续
                    if (childUserTaskList != null && childUserTaskList.size() > 0) {
                        userTaskList.addAll(childUserTaskList);
                        continue;
                    }
                }
                // ç»§ç»­è¿­ä»£
                // æ³¨æ„ï¼šå·²ç»ç»è¿‡çš„节点与连线都应该用浅拷贝出来的对象
                // æ¯”如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
                userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(), new HashSet<>(hasSequenceFlow), userTaskList);
            }
        }
        return userTaskList;
    }
    /**
     * ä»ŽåŽå‘前寻路,获取所有脏线路上的点
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param passRoads å·²ç»ç»è¿‡çš„点集合
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param targets ç›®æ ‡è„çº¿è·¯ç»ˆç‚¹
     * @param dirtyRoads ç¡®å®šä¸ºè„æ•°æ®çš„点,因为不需要重复,因此使用 set å­˜å‚¨
     * @return è·¯çº¿
     */
    public static Set<String> iteratorFindDirtyRoads(FlowElement source, List<String> passRoads, Set<String> hasSequenceFlow, List<String> targets, Set<String> dirtyRoads) {
        passRoads = passRoads == null ? new ArrayList<>() : passRoads;
        dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        // å¦‚果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            dirtyRoads = iteratorFindDirtyRoads(source.getSubProcess(), passRoads, hasSequenceFlow, targets, dirtyRoads);
        }
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // æ–°å¢žç»è¿‡çš„路线
                passRoads.add(sequenceFlow.getSourceFlowElement().getId());
                // å¦‚果此点为目标点,确定经过的路线为脏线路,添加点到脏线路中,然后找下个连线
                if (targets.contains(sequenceFlow.getSourceFlowElement().getId())) {
                    dirtyRoads.addAll(passRoads);
                    continue;
                }
                // å¦‚果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
                if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
                    dirtyRoads = findChildProcessAllDirtyRoad((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, dirtyRoads);
                    // æ˜¯å¦å­˜åœ¨å­æµç¨‹ä¸Šï¼Œtrue æ˜¯ï¼Œfalse å¦
                    Boolean isInChildProcess = dirtyTargetInChildProcess((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, targets, null);
                    if (isInChildProcess) {
                        // å·²åœ¨å­æµç¨‹ä¸Šæ‰¾åˆ°ï¼Œè¯¥è·¯çº¿ç»“束
                        continue;
                    }
                }
                // ç»§ç»­è¿­ä»£
                // æ³¨æ„ï¼šå·²ç»ç»è¿‡çš„节点与连线都应该用浅拷贝出来的对象
                // æ¯”如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
                dirtyRoads = iteratorFindDirtyRoads(sequenceFlow.getSourceFlowElement(), new ArrayList<>(passRoads), new HashSet<>(hasSequenceFlow), targets, dirtyRoads);
            }
        }
        return dirtyRoads;
    }
    /**
     * è¿­ä»£èŽ·å–å­æµç¨‹è„è·¯çº¿
     * è¯´æ˜Žï¼Œå‡å¦‚回退的点就是子流程,那么也肯定会回退到子流程最初的用户任务节点,因此子流程中的节点全是脏路线
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param dirtyRoads ç¡®å®šä¸ºè„æ•°æ®çš„点,因为不需要重复,因此使用 set å­˜å‚¨
     * @return è·¯çº¿
     */
    public static Set<String> findChildProcessAllDirtyRoad(FlowElement source, Set<String> hasSequenceFlow, Set<String> dirtyRoads) {
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads;
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å‡ºå£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // æ·»åŠ è„è·¯çº¿
                dirtyRoads.add(sequenceFlow.getTargetFlowElement().getId());
                // å¦‚果节点为子流程节点情况,则从节点中的第一个节点开始获取
                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
                    dirtyRoads = findChildProcessAllDirtyRoad((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, dirtyRoads);
                }
                // ç»§ç»­è¿­ä»£
                // æ³¨æ„ï¼šå·²ç»ç»è¿‡çš„节点与连线都应该用浅拷贝出来的对象
                // æ¯”如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
                dirtyRoads = findChildProcessAllDirtyRoad(sequenceFlow.getTargetFlowElement(), new HashSet<>(hasSequenceFlow), dirtyRoads);
            }
        }
        return dirtyRoads;
    }
    /**
     * åˆ¤æ–­è„è·¯çº¿ç»“束节点是否在子流程上
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param targets åˆ¤æ–­è„è·¯çº¿èŠ‚ç‚¹æ˜¯å¦å­˜åœ¨å­æµç¨‹ä¸Šï¼Œåªè¦å­˜åœ¨ä¸€ä¸ªï¼Œè¯´æ˜Žè„è·¯çº¿åªåˆ°å­æµç¨‹ä¸ºæ­¢
     * @param inChildProcess æ˜¯å¦å­˜åœ¨å­æµç¨‹ä¸Šï¼Œtrue æ˜¯ï¼Œfalse å¦
     * @return æ˜¯å¦
     */
    public static Boolean dirtyTargetInChildProcess(FlowElement source, Set<String> hasSequenceFlow, List<String> targets, Boolean inChildProcess) {
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        inChildProcess = inChildProcess == null ? false : inChildProcess;
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å‡ºå£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (sequenceFlows != null && !inChildProcess) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // å¦‚果发现目标点在子流程上存在,说明只到子流程为止
                if (targets.contains(sequenceFlow.getTargetFlowElement().getId())) {
                    inChildProcess = true;
                    break;
                }
                // å¦‚果节点为子流程节点情况,则从节点中的第一个节点开始获取
                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
                    inChildProcess = dirtyTargetInChildProcess((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, targets, inChildProcess);
                }
                // ç»§ç»­è¿­ä»£
                // æ³¨æ„ï¼šå·²ç»ç»è¿‡çš„节点与连线都应该用浅拷贝出来的对象
                // æ¯”如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
                inChildProcess = dirtyTargetInChildProcess(sequenceFlow.getTargetFlowElement(), new HashSet<>(hasSequenceFlow), targets, inChildProcess);
            }
        }
        return inChildProcess;
    }
    /**
     * è¿­ä»£ä»ŽåŽå‘前扫描,判断目标节点相对于当前节点是否是串行
     * ä¸å­˜åœ¨ç›´æŽ¥å›žé€€åˆ°å­æµç¨‹ä¸­çš„æƒ…况,但存在从子流程出去到父流程情况
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param isSequential æ˜¯å¦ä¸²è¡Œ
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param targetKsy ç›®æ ‡èŠ‚ç‚¹
     * @return æ˜¯å¦
     */
    public static Boolean iteratorCheckSequentialReferTarget(FlowElement source, String targetKsy, Set<String> hasSequenceFlow, Boolean isSequential) {
        isSequential = isSequential == null ? true : isSequential;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        // å¦‚果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            isSequential = iteratorCheckSequentialReferTarget(source.getSubProcess(), targetKsy, hasSequenceFlow, isSequential);
        }
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // å¦‚果目标节点已被判断为并行,后面都不需要执行,直接返回
                if (isSequential == false) {
                    break;
                }
                // è¿™æ¡çº¿è·¯å­˜åœ¨ç›®æ ‡èŠ‚ç‚¹ï¼Œè¿™æ¡çº¿è·¯å®Œæˆï¼Œè¿›å…¥ä¸‹ä¸ªçº¿è·¯
                if (targetKsy.equals(sequenceFlow.getSourceFlowElement().getId())) {
                    continue;
                }
                if (sequenceFlow.getSourceFlowElement() instanceof StartEvent) {
                    isSequential = false;
                    break;
                }
                // å¦åˆ™å°±ç»§ç»­è¿­ä»£
                // æ³¨æ„ï¼šå·²ç»ç»è¿‡çš„节点与连线都应该用浅拷贝出来的对象
                // æ¯”如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
                isSequential = iteratorCheckSequentialReferTarget(sequenceFlow.getSourceFlowElement(), targetKsy, new HashSet<>(hasSequenceFlow), isSequential);
            }
        }
        return isSequential;
    }
    /**
     * ä»ŽåŽå‘前寻路,获取到达节点的所有路线
     * ä¸å­˜åœ¨ç›´æŽ¥å›žé€€åˆ°å­æµç¨‹ï¼Œä½†æ˜¯å­˜åœ¨å›žé€€åˆ°çˆ¶çº§æµç¨‹çš„æƒ…况
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param passRoads å·²ç»ç»è¿‡çš„点集合
     * @param roads è·¯çº¿
     * @return ä»»åŠ¡
     */
    public static List<List<UserTask>> findRoad(FlowElement source, List<UserTask> passRoads, Set<String> hasSequenceFlow, List<List<UserTask>> roads) {
        passRoads = passRoads == null ? new ArrayList<>() : passRoads;
        roads = roads == null ? new ArrayList<>() : roads;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        // å¦‚果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            roads = findRoad(source.getSubProcess(), passRoads, hasSequenceFlow, roads);
        }
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        if (sequenceFlows != null && sequenceFlows.size() != 0) {
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // æ·»åŠ ç»è¿‡è·¯çº¿
                if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
                    passRoads.add((UserTask) sequenceFlow.getSourceFlowElement());
                }
                // ç»§ç»­è¿­ä»£
                // æ³¨æ„ï¼šå·²ç»ç»è¿‡çš„节点与连线都应该用浅拷贝出来的对象
                // æ¯”如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
                roads = findRoad(sequenceFlow.getSourceFlowElement(), new ArrayList<>(passRoads), new HashSet<>(hasSequenceFlow), roads);
            }
        } else {
            // æ·»åŠ è·¯çº¿
            roads.add(passRoads);
        }
        return roads;
    }
    /**
     * åŽ†å²èŠ‚ç‚¹æ•°æ®æ¸…æ´—ï¼Œæ¸…æ´—æŽ‰åˆå›žæ»šå¯¼è‡´çš„è„æ•°æ®
     * @param allElements å…¨éƒ¨èŠ‚ç‚¹ä¿¡æ¯
     * @param historicActivityIdList åŽ†å²ä»»åŠ¡å®žä¾‹ä¿¡æ¯ï¼Œæ•°æ®é‡‡ç”¨å¼€å§‹æ—¶é—´å‡åº
     * @return åŽ†å²æ•°æ®
     */
    public static List<String> historicTaskInstanceClean(Collection<FlowElement> allElements, List<HistoricActivityInstance> historicActivityIdList) {
        // ä¼šç­¾èŠ‚ç‚¹æ”¶é›†
        List<String> multiTask = new ArrayList<>();
        allElements.forEach(flowElement -> {
            if (flowElement instanceof UserTask) {
                // å¦‚果该节点的行为为会签行为,说明该节点为会签节点
                if (((UserTask) flowElement).getBehavior() instanceof ParallelMultiInstanceBehavior || ((UserTask) flowElement).getBehavior() instanceof SequentialMultiInstanceBehavior) {
                    multiTask.add(flowElement.getId());
                }
            }
        });
        // å¾ªçŽ¯æ”¾å…¥æ ˆï¼Œæ ˆ LIFO:后进先出
        Stack<HistoricActivityInstance> stack = new Stack<>();
        historicActivityIdList.forEach(item -> stack.push(item));
        // æ¸…洗后的历史任务实例
        List<String> lastHistoricTaskInstanceList = new ArrayList<>();
        // ç½‘关存在可能只走了部分分支情况,且还存在跳转废弃数据以及其他分支数据的干扰,因此需要对历史节点数据进行清洗
        // ä¸´æ—¶ç”¨æˆ·ä»»åŠ¡ key
        StringBuilder userTaskKey = null;
        // ä¸´æ—¶è¢«åˆ æŽ‰çš„任务 key,存在并行情况
        List<String> deleteKeyList = new ArrayList<>();
        // ä¸´æ—¶è„æ•°æ®çº¿è·¯
        List<Set<String>> dirtyDataLineList = new ArrayList<>();
        // ç”±æŸä¸ªç‚¹è·³åˆ°ä¼šç­¾ç‚¹,此时出现多个会签实例对应 1 ä¸ªè·³è½¬æƒ…况,需要把这些连续脏数据都找到
        // ä¼šç­¾ç‰¹æ®Šå¤„理下标
        int multiIndex = -1;
        // ä¼šç­¾ç‰¹æ®Šå¤„理 key
        StringBuilder multiKey = null;
        // ä¼šç­¾ç‰¹æ®Šå¤„理操作标识
        boolean multiOpera = false;
        while (!stack.empty()) {
            // ä»Žè¿™é‡Œå¼€å§‹ userTaskKey éƒ½è¿˜æ˜¯ä¸Šä¸ªæ ˆçš„ key
            // æ˜¯å¦æ˜¯è„æ•°æ®çº¿è·¯ä¸Šçš„点
            final boolean[] isDirtyData = {false};
            for (Set<String> oldDirtyDataLine : dirtyDataLineList) {
                if (oldDirtyDataLine.contains(stack.peek().getActivityId())) {
                    isDirtyData[0] = true;
                }
            }
            // åˆ é™¤åŽŸå› ä¸ä¸ºç©ºï¼Œè¯´æ˜Žä»Žè¿™æ¡æ•°æ®å¼€å§‹å›žè·³æˆ–è€…å›žé€€çš„
            // MI_END:会签完成后,其他未签到节点的删除原因,不在处理范围内
            if (stack.peek().getDeleteReason() != null && !stack.peek().getDeleteReason().equals("MI_END")) {
                // å¯ä»¥ç†è§£ä¸ºè„çº¿è·¯èµ·ç‚¹
                String dirtyPoint = "";
                if (stack.peek().getDeleteReason().indexOf("Change activity to ") >= 0) {
                    dirtyPoint = stack.peek().getDeleteReason().replace("Change activity to ", "");
                }
                // ä¼šç­¾å›žé€€åˆ é™¤åŽŸå› æœ‰ç‚¹ä¸åŒ
                if (stack.peek().getDeleteReason().indexOf("Change parent activity to ") >= 0) {
                    dirtyPoint = stack.peek().getDeleteReason().replace("Change parent activity to ", "");
                }
                FlowElement dirtyTask = null;
                // èŽ·å–å˜æ›´èŠ‚ç‚¹çš„å¯¹åº”çš„å…¥å£å¤„è¿žçº¿
                // å¦‚果是网关并行回退情况,会变成两条脏数据路线,效果一样
                for (FlowElement flowElement : allElements) {
                    if (flowElement.getId().equals(stack.peek().getActivityId())) {
                        dirtyTask = flowElement;
                    }
                }
                // èŽ·å–è„æ•°æ®çº¿è·¯
                Set<String> dirtyDataLine = FlowableUtils.iteratorFindDirtyRoads(dirtyTask, null, null, Arrays.asList(dirtyPoint.split(",")), null);
                // è‡ªå·±æœ¬èº«ä¹Ÿæ˜¯è„çº¿è·¯ä¸Šçš„点,加进去
                dirtyDataLine.add(stack.peek().getActivityId());
                //logger.info(stack.peek().getActivityId() + "点脏路线集合:" + dirtyDataLine);
                // æ˜¯å…¨æ–°çš„需要添加的脏线路
                boolean isNewDirtyData = true;
                for (int i = 0; i < dirtyDataLineList.size(); i++) {
                    // å¦‚果发现他的上个节点在脏线路内,说明这个点可能是并行的节点,或者连续驳回
                    // è¿™æ—¶ï¼Œéƒ½ä»¥ä¹‹å‰çš„脏线路节点为标准,只需合并脏线路即可,也就是路线补全
                    if (dirtyDataLineList.get(i).contains(userTaskKey.toString())) {
                        isNewDirtyData = false;
                        dirtyDataLineList.get(i).addAll(dirtyDataLine);
                    }
                }
                // å·²ç¡®å®šæ—¶å…¨æ–°çš„脏线路
                if (isNewDirtyData) {
                    // deleteKey å•一路线驳回到并行,这种同时生成多个新实例记录情况,这时 deleteKey å…¶å®žæ˜¯ç”±å¤šä¸ªå€¼ç»„成
                    // æŒ‰ç…§é€»è¾‘,回退后立刻生成的实例记录就是回退的记录
                    // è‡³äºŽé©³å›žæ‰€ç”Ÿæˆçš„ Key,直接从删除原因中获取,因为存在驳回到并行的情况
                    deleteKeyList.add(dirtyPoint + ",");
                    dirtyDataLineList.add(dirtyDataLine);
                }
                // æ·»åŠ åŽï¼ŒçŽ°åœ¨è¿™ä¸ªç‚¹å˜æˆè„çº¿è·¯ä¸Šçš„ç‚¹äº†
                isDirtyData[0] = true;
            }
            // å¦‚果不是脏线路上的点,说明是有效数据,添加历史实例 Key
            if (!isDirtyData[0]) {
                lastHistoricTaskInstanceList.add(stack.peek().getActivityId());
            }
            // æ ¡éªŒè„çº¿è·¯æ˜¯å¦ç»“束
            for (int i = 0; i < deleteKeyList.size(); i ++) {
                // å¦‚果发现脏数据属于会签,记录下下标与对应 Key,以备后续比对,会签脏数据范畴开始
                if (multiKey == null && multiTask.contains(stack.peek().getActivityId())
                        && deleteKeyList.get(i).contains(stack.peek().getActivityId())) {
                    multiIndex = i;
                    multiKey = new StringBuilder(stack.peek().getActivityId());
                }
                // ä¼šç­¾è„æ•°æ®å¤„理,节点退回会签清空
                // å¦‚果在会签脏数据范畴中发现 Key改变,说明会签脏数据在上个节点就结束了,可以把会签脏数据删掉
                if (multiKey != null && !multiKey.toString().equals(stack.peek().getActivityId())) {
                    deleteKeyList.set(multiIndex , deleteKeyList.get(multiIndex).replace(stack.peek().getActivityId() + ",", ""));
                    multiKey = null;
                    // ç»“束进行下校验删除
                    multiOpera = true;
                }
                // å…¶ä»–脏数据处理
                // å‘现该路线最后一条脏数据,说明这条脏数据线路处理完了,删除脏数据信息
                // è„æ•°æ®äº§ç”Ÿçš„æ–°å®žä¾‹ä¸­æ˜¯å¦åŒ…含这条数据
                if (multiKey == null && deleteKeyList.get(i).contains(stack.peek().getActivityId())) {
                    // åˆ é™¤åŒ¹é…åˆ°çš„部分
                    deleteKeyList.set(i , deleteKeyList.get(i).replace(stack.peek().getActivityId() + ",", ""));
                }
                // å¦‚果每组中的元素都以匹配过,说明脏数据结束
                if ("".equals(deleteKeyList.get(i))) {
                    // åŒæ—¶åˆ é™¤è„æ•°æ®
                    deleteKeyList.remove(i);
                    dirtyDataLineList.remove(i);
                    break;
                }
            }
            // ä¼šç­¾æ•°æ®å¤„理需要在循环外处理,否则可能导致溢出
            // ä¼šç­¾çš„æ•°æ®è‚¯å®šæ˜¯ä¹‹å‰æ”¾è¿›åŽ»çš„æ‰€ä»¥ç†è®ºä¸Šä¸ä¼šæº¢å‡ºï¼Œä½†è¿˜æ˜¯æ ¡éªŒä¸‹
            if (multiOpera && deleteKeyList.size() > multiIndex && "".equals(deleteKeyList.get(multiIndex))) {
                // åŒæ—¶åˆ é™¤è„æ•°æ®
                deleteKeyList.remove(multiIndex);
                dirtyDataLineList.remove(multiIndex);
                multiIndex = -1;
                multiOpera = false;
            }
            // pop() æ–¹æ³•与 peek() æ–¹æ³•不同,在返回值的同时,会把值从栈中移除
            // ä¿å­˜æ–°çš„ userTaskKey åœ¨ä¸‹ä¸ªå¾ªçŽ¯ä¸­ä½¿ç”¨
            userTaskKey = new StringBuilder(stack.pop().getActivityId());
        }
        //logger.info("清洗后的历史节点数据:" + lastHistoricTaskInstanceList);
        return lastHistoricTaskInstanceList;
    }
    /**
     * èŽ·å–ç›¸å…³çš„åŽ†å²
     * @param processInstanceId æµç¨‹å®žä¾‹çš„主键
     * @param historyService åŽ†å²æœåŠ¡
     * @param taskService ä»»åŠ¡æœåŠ¡
     * @return åŽ†å²çš„ä¿¡æ¯
     */
    public static List<FlowTaskHisVO> listTaskHistory(String processInstanceId, HistoryService historyService, TaskService taskService){
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if(processInstance == null || StringUtils.isBlank(processInstance.getId())){
            throw new VciBaseException("流程未找到");
        }
        List<HistoricActivityInstance> hisList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType("userTask").orderByHistoricActivityInstanceEndTime().desc().list();
        Map<String,FlowTaskHisVO> hisVOList = new HashMap<>();
        if(!CollectionUtils.isEmpty(hisList)){
            List<HistoricActivityInstance> allowTaskInstances = new ArrayList<>();
            Map<String,List<Comment>> taskOidCommentsMap = new HashMap<>();
            for(int i = 0 ; i < hisList.size(); i ++){
                HistoricActivityInstance his = hisList.get(i);
                //我们找当时的审批信息,这样可以判断是否为不同意
                List<Comment> comments = taskService.getTaskComments(his.getTaskId());
                if(!CollectionUtils.isEmpty(comments)){
                    taskOidCommentsMap.put(his.getId(),comments);
                    //查询
                }//第一个可能没有
                allowTaskInstances.add(his);
            }
            if(!CollectionUtils.isEmpty(allowTaskInstances)){
                List<HistoricVariableInstance> variableInstances = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).list();
                Map<String,Object> variableMap = switchVariable(variableInstances);
                Set<Long> userIds = new HashSet<>();
                Set<String> processInstanceIds = new HashSet<>();
                for(int i = 0 ; i < allowTaskInstances.size() ; i ++){
                    HistoricActivityInstance activityInstance = allowTaskInstances.get(i);
                    if(!hisVOList.containsKey(activityInstance.getId())) {
                        FlowTaskHisVO hisVO = new FlowTaskHisVO();
                        hisVO.setOid(activityInstance.getId());
                        hisVO.setActivityId(activityInstance.getActivityId());
                        hisVO.setActivityName(activityInstance.getActivityName());
                        hisVO.setActivityType(activityInstance.getActivityType());
                        hisVO.setProcessDefinitionId(activityInstance.getProcessDefinitionId());
                        hisVO.setProcessInstanceId(activityInstance.getProcessInstanceId());
                        hisVO.setExecutionId(activityInstance.getExecutionId());
                        hisVO.setTaskId(activityInstance.getTaskId());
                        hisVO.setCalledProcessInstanceId(activityInstance.getCalledProcessInstanceId());
                        hisVO.setAssignee(activityInstance.getAssignee());
                        if (StringUtils.isNotBlank(hisVO.getAssignee())) {
                            userIds.add(TaskUtil.getUserId(hisVO.getAssignee()));
                        }
                        hisVO.setStartTime(activityInstance.getStartTime());
                        hisVO.setEndTime(activityInstance.getEndTime());
                        hisVO.setDurationInMillis(activityInstance.getDurationInMillis());
                        hisVO.setDeleteReason(activityInstance.getDeleteReason());
                        hisVO.setTenantId(activityInstance.getTenantId());
                        hisVO.setVariablesMap(variableMap);
                        hisVO.setBtmType(variableMap.getOrDefault(FlowEngineConstant.BTMTYPE,"").toString());
                        hisVO.setLinkBusinessOids(VciBaseUtil.str2List(variableMap.getOrDefault(FlowEngineConstant.OIDS,"").toString()));
                        List<Comment> comments = taskOidCommentsMap.getOrDefault(activityInstance.getId(),new ArrayList<>());
                        hisVO.setDescription(comments.stream().map(s->{
                            String fullMsg = s.getFullMessage();
                            if(StringUtils.isNotBlank(fullMsg) && fullMsg.contains(":")){
                                fullMsg = fullMsg.substring(fullMsg.indexOf(":")+1);
                            }
                            return fullMsg;
                        }).collect(Collectors.joining(";")));
                        processInstanceIds.add(activityInstance.getProcessInstanceId());
                        hisVO.setProcessName(processInstance.getName());
                        hisVO.setProcessDefinitionName(processInstance.getProcessDefinitionName());
                        hisVOList.put(hisVO.getActivityId(), hisVO);
                    }
                }
                if(!CollectionUtils.isEmpty(userIds) && !CollectionUtils.isEmpty(hisVOList)){
                    List<User> userList = new ArrayList<>();
                    for(Long userId : userIds){
                        userList.add(UserCache.getUser(userId));
                    }
                    if(!CollectionUtils.isEmpty(userList)){
                        Map<Long, User> userVOMap = userList.stream().collect(Collectors.toMap(s -> s.getId(), t -> t, (o1, o2) -> o1));
                        hisVOList.values().forEach(history->{
                            List<String> thisUserIds = VciBaseUtil.str2List(TaskUtil.getTaskUser(history.getAssignee()));
                            List<String> thisUserNames = new ArrayList<>();
                            if(!CollectionUtils.isEmpty(thisUserIds)) {
                                thisUserIds.forEach(userId -> {
                                    thisUserNames.add(userVOMap.getOrDefault(userId, new User()).getName());
                                });
                            }
                            history.setAssigneeName(thisUserNames.stream().collect(Collectors.joining(",")));
                        });
                    }
                }
            }
        }
        return hisVOList.values().stream().collect(Collectors.toList());
    }
    /**
     * è½¬æ¢å˜é‡
     * @param variableInstances å˜é‡çš„实例
     * @return å˜é‡
     */
    public static Map<String,Object> switchVariable(List<HistoricVariableInstance> variableInstances){
        Map<String,Object> variableMap = new HashMap<>();
        if(!CollectionUtils.isEmpty(variableInstances)){
            variableMap = variableInstances.stream().collect(Collectors.toMap(s->s.getVariableName(),t->t.getValue() == null?"":t.getValue()));
        }
        return variableMap;
    }
}