From 846cfb6ad413aeff93b3181e0bf69f53c6434001 Mon Sep 17 00:00:00 2001
From: dangsn <dangsn@chicecm.com>
Date: 星期三, 02 八月 2023 18:14:35 +0800
Subject: [PATCH] 新增通用生命周期变更事件

---
 Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/utils/FlowableUtils.java         |  727 +++++++++++++++++++++++++++++++++
 Source/UBCS/ubcs-ops-api/ubcs-flow-api/src/main/java/com/vci/ubcs/flow/core/vo/FlowTaskHisVO.java      |  345 +++++++++++++++
 Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/constant/FlowEngineConstant.java |   20 
 Source/UBCS/ubcs-ops-api/ubcs-flow-api/src/main/java/com/vci/ubcs/flow/core/dto/FlowStatusDTO.java     |   80 +++
 Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/envent/FlowStatusListener.java   |  123 +++++
 5 files changed, 1,295 insertions(+), 0 deletions(-)

diff --git a/Source/UBCS/ubcs-ops-api/ubcs-flow-api/src/main/java/com/vci/ubcs/flow/core/dto/FlowStatusDTO.java b/Source/UBCS/ubcs-ops-api/ubcs-flow-api/src/main/java/com/vci/ubcs/flow/core/dto/FlowStatusDTO.java
new file mode 100644
index 0000000..ebbba0d
--- /dev/null
+++ b/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 +
+			'}';
+	}
+}
diff --git a/Source/UBCS/ubcs-ops-api/ubcs-flow-api/src/main/java/com/vci/ubcs/flow/core/vo/FlowTaskHisVO.java b/Source/UBCS/ubcs-ops-api/ubcs-flow-api/src/main/java/com/vci/ubcs/flow/core/vo/FlowTaskHisVO.java
new file mode 100644
index 0000000..c06c737
--- /dev/null
+++ b/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 + '\'' +
+			'}';
+	}
+}
diff --git a/Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/constant/FlowEngineConstant.java b/Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/constant/FlowEngineConstant.java
index 509b0b9..5750d2b 100644
--- a/Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/constant/FlowEngineConstant.java
+++ b/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";
+
 }
diff --git a/Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/envent/FlowStatusListener.java b/Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/envent/FlowStatusListener.java
new file mode 100644
index 0000000..f932e49
--- /dev/null
+++ b/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("鎵ц鐘舵�佷慨鏀逛簨浠舵椂锛屼笟鍔℃暟鎹畂id涓虹┖锛�");
+			}
+			if(StringUtils.isEmpty(btmType)){
+				throw new VciBaseException("鎵ц鐘舵�佷慨鏀逛簨浠舵椂锛屼笟鍔$被鍨媌tmType涓虹┖锛�");
+			}
+			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"));
+			}
+
+		}
+	}
+
+
+}
diff --git a/Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/utils/FlowableUtils.java b/Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/utils/FlowableUtils.java
new file mode 100644
index 0000000..087c43d
--- /dev/null
+++ b/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 {
+    /**
+     * 鏍规嵁鑺傜偣锛岃幏鍙栧叆鍙h繛绾�
+     * @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;
+    }
+
+    /**
+     * 鏍规嵁鑺傜偣锛岃幏鍙栧嚭鍙h繛绾�
+     * @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);
+        }
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧叆鍙h繛绾�
+        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;
+                }
+                // 绫诲瀷涓哄瓙娴佺▼锛屽垯娣诲姞瀛愭祦绋嬪紑濮嬭妭鐐瑰嚭鍙e鐩歌繛鐨勮妭鐐�
+                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;
+                    }
+                }
+                // 缁х画杩唬
+                // 娉ㄦ剰锛氬凡缁忕粡杩囩殑鑺傜偣涓庤繛绾块兘搴旇鐢ㄦ祬鎷疯礉鍑烘潵鐨勫璞�
+                // 姣斿鍒嗘敮锛歛->b->c涓巃->d->c锛岃蛋瀹宎->b->c鍚庤蛋鍙︿竴涓矾绾挎槸锛屽凡缁忕粡杩囩殑鑺傜偣搴旇涓嶅寘鍚玜->b->c璺嚎鐨勬暟鎹�
+                userTaskList = iteratorFindParentUserTasks(sequenceFlow.getSourceFlowElement(), new HashSet<>(hasSequenceFlow), userTaskList);
+            }
+        }
+        return userTaskList;
+    }
+
+    /**
+     * 鏍规嵁姝e湪杩愯鐨勪换鍔¤妭鐐癸紝杩唬鑾峰彇瀛愮骇浠诲姟鑺傜偣鍒楄〃锛屽悜鍚庢壘
+     * @param source 璧峰鑺傜偣
+     * @param runActiveIdList 姝e湪杩愯鐨勪换鍔� Key锛岀敤浜庢牎楠屼换鍔¤妭鐐规槸鍚︽槸姝e湪杩愯鐨勮妭鐐�
+     * @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);
+        }
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧嚭鍙h繛绾�
+        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;
+                    }
+                }
+                // 缁х画杩唬
+                // 娉ㄦ剰锛氬凡缁忕粡杩囩殑鑺傜偣涓庤繛绾块兘搴旇鐢ㄦ祬鎷疯礉鍑烘潵鐨勫璞�
+                // 姣斿鍒嗘敮锛歛->b->c涓巃->d->c锛岃蛋瀹宎->b->c鍚庤蛋鍙︿竴涓矾绾挎槸锛屽凡缁忕粡杩囩殑鑺傜偣搴旇涓嶅寘鍚玜->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;
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧嚭鍙h繛绾�
+        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
+
+        if (sequenceFlows != null) {
+            // 寰幆鎵惧埌鐩爣鍏冪礌
+            for (SequenceFlow sequenceFlow: sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                hasSequenceFlow.add(sequenceFlow.getId());
+                // 濡傛灉涓虹敤鎴蜂换鍔$被鍨嬶紝涓斾换鍔¤妭鐐圭殑 Key 姝e湪杩愯鐨勪换鍔′腑瀛樺湪锛屾坊鍔�
+                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;
+                    }
+                }
+                // 缁х画杩唬
+                // 娉ㄦ剰锛氬凡缁忕粡杩囩殑鑺傜偣涓庤繛绾块兘搴旇鐢ㄦ祬鎷疯礉鍑烘潵鐨勫璞�
+                // 姣斿鍒嗘敮锛歛->b->c涓巃->d->c锛岃蛋瀹宎->b->c鍚庤蛋鍙︿竴涓矾绾挎槸锛屽凡缁忕粡杩囩殑鑺傜偣搴旇涓嶅寘鍚玜->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);
+        }
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧叆鍙h繛绾�
+        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);
+                    // 鏄惁瀛樺湪瀛愭祦绋嬩笂锛宼rue 鏄紝false 鍚�
+                    Boolean isInChildProcess = dirtyTargetInChildProcess((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, targets, null);
+                    if (isInChildProcess) {
+                        // 宸插湪瀛愭祦绋嬩笂鎵惧埌锛岃璺嚎缁撴潫
+                        continue;
+                    }
+                }
+                // 缁х画杩唬
+                // 娉ㄦ剰锛氬凡缁忕粡杩囩殑鑺傜偣涓庤繛绾块兘搴旇鐢ㄦ祬鎷疯礉鍑烘潵鐨勫璞�
+                // 姣斿鍒嗘敮锛歛->b->c涓巃->d->c锛岃蛋瀹宎->b->c鍚庤蛋鍙︿竴涓矾绾挎槸锛屽凡缁忕粡杩囩殑鑺傜偣搴旇涓嶅寘鍚玜->b->c璺嚎鐨勬暟鎹�
+                dirtyRoads = iteratorFindDirtyRoads(sequenceFlow.getSourceFlowElement(), new ArrayList<>(passRoads), new HashSet<>(hasSequenceFlow), targets, dirtyRoads);
+            }
+        }
+        return dirtyRoads;
+    }
+
+    /**
+     * 杩唬鑾峰彇瀛愭祦绋嬭剰璺嚎
+     * 璇存槑锛屽亣濡傚洖閫�鐨勭偣灏辨槸瀛愭祦绋嬶紝閭d箞涔熻偗瀹氫細鍥為��鍒板瓙娴佺▼鏈�鍒濈殑鐢ㄦ埛浠诲姟鑺傜偣锛屽洜姝ゅ瓙娴佺▼涓殑鑺傜偣鍏ㄦ槸鑴忚矾绾�
+     * @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;
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧嚭鍙h繛绾�
+        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);
+                }
+                // 缁х画杩唬
+                // 娉ㄦ剰锛氬凡缁忕粡杩囩殑鑺傜偣涓庤繛绾块兘搴旇鐢ㄦ祬鎷疯礉鍑烘潵鐨勫璞�
+                // 姣斿鍒嗘敮锛歛->b->c涓巃->d->c锛岃蛋瀹宎->b->c鍚庤蛋鍙︿竴涓矾绾挎槸锛屽凡缁忕粡杩囩殑鑺傜偣搴旇涓嶅寘鍚玜->b->c璺嚎鐨勬暟鎹�
+                dirtyRoads = findChildProcessAllDirtyRoad(sequenceFlow.getTargetFlowElement(), new HashSet<>(hasSequenceFlow), dirtyRoads);
+            }
+        }
+        return dirtyRoads;
+    }
+
+    /**
+     * 鍒ゆ柇鑴忚矾绾跨粨鏉熻妭鐐规槸鍚﹀湪瀛愭祦绋嬩笂
+     * @param source 璧峰鑺傜偣
+     * @param hasSequenceFlow 宸茬粡缁忚繃鐨勮繛绾跨殑 ID锛岀敤浜庡垽鏂嚎璺槸鍚﹂噸澶�
+     * @param targets 鍒ゆ柇鑴忚矾绾胯妭鐐规槸鍚﹀瓨鍦ㄥ瓙娴佺▼涓婏紝鍙瀛樺湪涓�涓紝璇存槑鑴忚矾绾垮彧鍒板瓙娴佺▼涓烘
+     * @param inChildProcess 鏄惁瀛樺湪瀛愭祦绋嬩笂锛宼rue 鏄紝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;
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧嚭鍙h繛绾�
+        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);
+                }
+                // 缁х画杩唬
+                // 娉ㄦ剰锛氬凡缁忕粡杩囩殑鑺傜偣涓庤繛绾块兘搴旇鐢ㄦ祬鎷疯礉鍑烘潵鐨勫璞�
+                // 姣斿鍒嗘敮锛歛->b->c涓巃->d->c锛岃蛋瀹宎->b->c鍚庤蛋鍙︿竴涓矾绾挎槸锛屽凡缁忕粡杩囩殑鑺傜偣搴旇涓嶅寘鍚玜->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);
+        }
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧叆鍙h繛绾�
+        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;
+                }
+                // 鍚﹀垯灏辩户缁凯浠�
+                // 娉ㄦ剰锛氬凡缁忕粡杩囩殑鑺傜偣涓庤繛绾块兘搴旇鐢ㄦ祬鎷疯礉鍑烘潵鐨勫璞�
+                // 姣斿鍒嗘敮锛歛->b->c涓巃->d->c锛岃蛋瀹宎->b->c鍚庤蛋鍙︿竴涓矾绾挎槸锛屽凡缁忕粡杩囩殑鑺傜偣搴旇涓嶅寘鍚玜->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);
+        }
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧叆鍙h繛绾�
+        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());
+                }
+                // 缁х画杩唬
+                // 娉ㄦ剰锛氬凡缁忕粡杩囩殑鑺傜偣涓庤繛绾块兘搴旇鐢ㄦ祬鎷疯礉鍑烘潵鐨勫璞�
+                // 姣斿鍒嗘敮锛歛->b->c涓巃->d->c锛岃蛋瀹宎->b->c鍚庤蛋鍙︿竴涓矾绾挎槸锛屽凡缁忕粡杩囩殑鑺傜偣搴旇涓嶅寘鍚玜->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;
+    }
+}

--
Gitblit v1.9.3