Repository: tapdata/tapdata (language: python)

Issue:
chore: add datapp oem file



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/mr-ci.yaml b/.github/workflows/mr-ci.yaml
index 4ef49d19dd..3dc03639ef 100644
--- a/.github/workflows/mr-ci.yaml
+++ b/.github/workflows/mr-ci.yaml
@@ -126,6 +126,7 @@ jobs:
 
   Scan-Tapdata:
     runs-on: office-scan
+    if: ${{ !startsWith(github.base_ref, 'release-v') }}
     needs:
       - Get-Stable-Branch
       - Push-Code-To-GOGS
diff --git a/RELEASE b/RELEASE
index e72fe6ea4c..868849a5d6 100644
--- a/RELEASE
+++ b/RELEASE
@@ -1,2 +1,2 @@
-LST: v3.8.0
-DEV: v3.9.0-dev
\ No newline at end of file
+LST: v3.9.0
+DEV: v3.10.0-dev
\ No newline at end of file
diff --git a/iengine/api/src/main/java/io/tapdata/flow/engine/V2/sharecdc/ShareCdcContext.java b/iengine/api/src/main/java/io/tapdata/flow/engine/V2/sharecdc/ShareCdcContext.java
index 5889604da1..29a916f3e6 100644
--- a/iengine/api/src/main/java/io/tapdata/flow/engine/V2/sharecdc/ShareCdcContext.java
+++ b/iengine/api/src/main/java/io/tapdata/flow/engine/V2/sharecdc/ShareCdcContext.java
@@ -4,6 +4,7 @@
 import io.tapdata.loglistener.TapLogger;
 
 import java.io.Serializable;
+import java.util.List;
 
 /**
  * @author samuel
@@ -17,8 +18,11 @@ public class ShareCdcContext implements Serializable {
 	// Incremental start timestamp
 	private Long cdcStartTs;
 
+	private List<String> tableNames;
+
 	// Tapdata Settings
 	private ConfigurationCenter configurationCenter;
+
 	private TapLogger obsLogger;
 
 	public ShareCdcContext(Long cdcStartTs, ConfigurationCenter configurationCenter) {
@@ -45,4 +49,12 @@ public TapLogger getObsLogger() {
 	public void setObsLogger(TapLogger obsLogger) {
 		this.obsLogger = obsLogger;
 	}
+
+	public List<String> getTableNames() {
+		return tableNames;
+	}
+
+	public void setTableNames(List<String> tableNames) {
+		this.tableNames = tableNames;
+	}
 }
diff --git a/iengine/api/src/main/java/io/tapdata/threadgroup/ConnectorOnTaskThreadGroup.java b/iengine/api/src/main/java/io/tapdata/threadgroup/ConnectorOnTaskThreadGroup.java
index d5b6e78d11..0b4cac6ffd 100644
--- a/iengine/api/src/main/java/io/tapdata/threadgroup/ConnectorOnTaskThreadGroup.java
+++ b/iengine/api/src/main/java/io/tapdata/threadgroup/ConnectorOnTaskThreadGroup.java
@@ -5,7 +5,7 @@
 public class ConnectorOnTaskThreadGroup extends ThreadGroup {
     private DataProcessorContext dataProcessorContext;
     public ConnectorOnTaskThreadGroup(DataProcessorContext dataProcessorContext) {
-        super("ConnectorOnTaskThreadGroup_" + dataProcessorContext.getNode().getName());
+        super("ConnectorOnTaskThreadGroup_" + dataProcessorContext.getNode().getId() + "_" + dataProcessorContext.getNode().getName());
         this.dataProcessorContext = dataProcessorContext;
     }
 
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/common/DAGDataEngineServiceImpl.java b/iengine/iengine-app/src/main/java/io/tapdata/common/DAGDataEngineServiceImpl.java
index 185e9eacfe..3893d76db5 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/common/DAGDataEngineServiceImpl.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/common/DAGDataEngineServiceImpl.java
@@ -57,6 +57,7 @@ public class DAGDataEngineServiceImpl extends DAGDataServiceImpl {
     private final String uuid ;
 
     private final Map<String,TapTableMap<String, TapTable>> tapTableMapHashMap;
+    private Map<String, List<String>> tableNodeSameMetadataInstances = new HashMap<>();
 
     public DAGDataEngineServiceImpl(TransformerWsMessageDto transformerWsMessageDto, TaskService<TaskDto> taskService,
                                     Map<String,TapTableMap<String, TapTable>> tapTableMapHashMap,ClientMongoOperator clientMongoOperator) {
@@ -68,6 +69,7 @@ public DAGDataEngineServiceImpl(TransformerWsMessageDto transformerWsMessageDto,
         this.clientMongoOperator = clientMongoOperator;
         this.uuid = transformerWsMessageDto.getOptions().getUuid();
         this.tapTableMapHashMap = tapTableMapHashMap;
+        this.tableNodeSameMetadataInstances = transformerWsMessageDto.getTableNodeSameMetadataInstances();
 
     }
 
@@ -170,6 +172,7 @@ public void initializeModel(Boolean isSync) {
                     }else{
                         tapTableMap.putNew(metadataInstancesDto.getOriginalName(), tapTable, metadataInstancesDto.getQualifiedName());
                     }
+                    checkTableNodeSameMetadataInstancesDto(metadataInstancesDto,tapTable);
                 }
             });
         }
@@ -262,4 +265,16 @@ protected void mergeTableMap(TapTableMap<String, TapTable> tapTableMap , TapTabl
             tapTableMap.putNew(key,preTapTableMap.get(key),preTapTableMap.getQualifiedName(key));
         }
     }
+
+    protected void checkTableNodeSameMetadataInstancesDto(MetadataInstancesDto metadataInstancesDto,TapTable tapTable){
+         if(null != tableNodeSameMetadataInstances && tableNodeSameMetadataInstances.containsKey(metadataInstancesDto.getQualifiedName())){
+             tableNodeSameMetadataInstances.get(metadataInstancesDto.getQualifiedName()).forEach(n -> {
+                 if(!metadataInstancesDto.getNodeId().equals(n)){
+                     TapTableMap<String, TapTable> tapTableMap = tapTableMapHashMap.computeIfAbsent(n, key -> TapTableMap.create(null, n, new HashMap<>(),taskDto.getTmCurrentTime()));
+                     tapTableMap.putNew(metadataInstancesDto.getOriginalName(), tapTable, metadataInstancesDto.getQualifiedName());
+                 }
+         });
+         }
+    }
+
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNode.java
index 2cfad010a3..0cee99b9ac 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNode.java
@@ -1,5 +1,6 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.data.pdk;
 
+import cn.hutool.core.collection.ConcurrentHashSet;
 import com.hazelcast.core.HazelcastInstance;
 import com.tapdata.constant.ConnectorConstant;
 import com.tapdata.constant.Log4jUtil;
@@ -47,6 +48,8 @@
 import io.tapdata.pdk.core.utils.CommonUtils;
 import io.tapdata.schema.PdkTableMap;
 import io.tapdata.schema.TapTableMap;
+import io.tapdata.supervisor.TaskNodeInfo;
+import io.tapdata.threadgroup.ConnectorOnTaskThreadGroup;
 import org.apache.commons.collections4.MapUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
@@ -60,6 +63,7 @@
 import java.util.*;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * @author samuel
@@ -384,4 +388,28 @@ protected void removeNotSupportFields(String tableName, Map<String, Object> data
 			MapUtil.removeKey(data, notSupportField);
 		}
 	}
+
+	protected ThreadGroup getReuseOrNewThreadGroup(ConcurrentHashSet<TaskNodeInfo> taskNodeInfos) {
+		Optional<TaskNodeInfo> leakTaskNodeInfo = taskNodeInfos.stream().filter(
+						(taskNodeInfo) -> getNode().getId().equals(taskNodeInfo.getNode().getId())
+								&& taskNodeInfo.isHasLaked())
+				.findAny();
+		AtomicReference<ThreadGroup> connectorOnTaskThreadGroup = new AtomicReference<>();
+		leakTaskNodeInfo.ifPresent(taskNodeInfo -> {
+			synchronized (taskNodeInfo.getTaskNodeInfoLock()) {
+				if (!taskNodeInfo.isHasLaked()) return;
+				taskNodeInfo.setHasLeaked(false);
+				ThreadGroup nodeThreadGroup = taskNodeInfo.getNodeThreadGroup();
+				if (nodeThreadGroup instanceof ConnectorOnTaskThreadGroup) {
+					taskNodeInfo.setNode(getNode());
+					((ConnectorOnTaskThreadGroup) nodeThreadGroup).setDataProcessorContext(dataProcessorContext);
+					connectorOnTaskThreadGroup.set(nodeThreadGroup);
+				}
+			}
+		});
+		if (connectorOnTaskThreadGroup.get() == null) {
+			connectorOnTaskThreadGroup.set(new ConnectorOnTaskThreadGroup(dataProcessorContext));
+		}
+		return connectorOnTaskThreadGroup.get();
+	}
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSampleSourcePdkDataNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSampleSourcePdkDataNode.java
index 6b82437fe4..0f51b37cfa 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSampleSourcePdkDataNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSampleSourcePdkDataNode.java
@@ -1,6 +1,6 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.data.pdk;
 
-import com.tapdata.constant.CollectionUtil;
+import com.hazelcast.core.HazelcastInstance;
 import com.tapdata.entity.SyncStage;
 import com.tapdata.entity.TapdataEvent;
 import com.tapdata.entity.task.context.DataProcessorContext;
@@ -22,6 +22,7 @@
 import io.tapdata.pdk.apis.entity.TapAdvanceFilter;
 import io.tapdata.pdk.apis.functions.PDKMethod;
 import io.tapdata.pdk.apis.functions.connector.target.QueryByAdvanceFilterFunction;
+import io.tapdata.pdk.core.api.ConnectorNode;
 import io.tapdata.pdk.core.entity.params.PDKMethodInvoker;
 import io.tapdata.pdk.core.monitor.PDKInvocationMonitor;
 import io.tapdata.schema.SampleMockUtil;
@@ -60,6 +61,14 @@ protected void doInit(@NotNull Context context) throws TapCodeException {
 		startSourceRunner();
 	}
 
+	protected TapCodecsFilterManager initNode() {
+		HazelcastInstance hazelcastInstance = jetContext.hazelcastInstance();
+		createPdkConnectorNode(dataProcessorContext, hazelcastInstance);
+		connectorNodeInit(dataProcessorContext);
+		ConnectorNode connectorNode = getConnectorNode();
+		return connectorNode.getCodecsFilterManager();
+	}
+
 	public void startSourceRunner() {
 
 		try {
@@ -69,9 +78,11 @@ public void startSourceRunner() {
 			int rows = 1;
 			if (node instanceof DatabaseNode) {
 				rows = ((DatabaseNode) node).getRows() == null ? 1 : ((DatabaseNode) node).getRows();
+				tables = getSourceTables(node,tables);
 			} else if (node instanceof TableNode) {
 				rows = ((TableNode) node).getRows() == null ? 1 : ((TableNode) node).getRows();
 			}
+			TapCodecsFilterManager codecsFilterManager = initNode();
 
 			// 测试任务
 			long startTs = System.currentTimeMillis();
@@ -88,10 +99,6 @@ public void startSourceRunner() {
 				} else {
 					tapEventList = new ArrayList<>();
 				}
-				createPdkConnectorNode(dataProcessorContext, jetContext.hazelcastInstance());
-				connectorNodeInit(dataProcessorContext);
-				TapCodecsFilterManager codecsFilterManager = getConnectorNode().getCodecsFilterManager();
-
 				boolean isCache = true;
 				boolean needMock = false;
 				if (CollectionUtils.isEmpty(tapEventList) || tapEventList.size() < rows) {
@@ -228,4 +235,12 @@ private static List<TapEvent> wrapTapEvent(List<Map<String, Object>> results, St
 
 		return tapEvents;
 	}
+
+	protected List<String> getSourceTables(Node node,List<String> tables) {
+		if (StringUtils.equalsAnyIgnoreCase(dataProcessorContext.getTaskDto().getSyncType(),
+				TaskDto.SYNC_TYPE_TEST_RUN)){
+			return ((DatabaseNode) node).getTableNames();
+		}
+		return tables;
+	}
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePartitionReadDataNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePartitionReadDataNode.java
index 0b6ca002ea..9bb5993357 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePartitionReadDataNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePartitionReadDataNode.java
@@ -76,6 +76,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 import static io.tapdata.entity.simplify.TapSimplify.sleep;
 
@@ -565,7 +566,7 @@ private void handleStreamEventsReceivedDuringPartition(List<TapEvent> events, Ob
 //        return cast;
 //    }
 
-	public void handleStreamEventsReceived(List<TapEvent> events, Object offsetObj) {
+	public void handleStreamEventsReceived(List<TapEvent> tapEvents, Object offsetObj) {
 		try {
 //			while (isRunning()) {
 //				try {
@@ -576,22 +577,23 @@ public void handleStreamEventsReceived(List<TapEvent> events, Object offsetObj)
 //					break;
 //				}
 //			}
-			if (events != null && !events.isEmpty()) {
-				events.stream().map(event -> {
+			if (tapEvents != null && !tapEvents.isEmpty()) {
+				String taskSyncType = this.dataProcessorContext.getTaskDto().getSyncType();
+				tapEvents = tapEvents.stream().map(event -> {
 					if (null == event.getTime()) {
 						throw new NodeException("Invalid TapEvent, `TapEvent.time` should be NonNUll").context(getProcessorBaseContext()).event(event);
 					}
 					event.addInfo("eventId", UUID.randomUUID().toString());
-					return cdcDelayCalculation.filterAndCalcDelay(event, times -> AspectUtils.executeAspect(SourceCDCDelayAspect.class, () -> new SourceCDCDelayAspect().delay(times).dataProcessorContext(dataProcessorContext)));
-				});
+					return cdcDelayCalculation.filterAndCalcDelay(event, times -> AspectUtils.executeAspect(SourceCDCDelayAspect.class, () -> new SourceCDCDelayAspect().delay(times).dataProcessorContext(dataProcessorContext)), taskSyncType);
+				}).collect(Collectors.toList());
 
 				if (streamReadFuncAspect != null) {
-					AspectUtils.accept(streamReadFuncAspect.state(StreamReadFuncAspect.STATE_STREAMING_READ_COMPLETED).getStreamingReadCompleteConsumers(), events);
+					AspectUtils.accept(streamReadFuncAspect.state(StreamReadFuncAspect.STATE_STREAMING_READ_COMPLETED).getStreamingReadCompleteConsumers(), tapEvents);
 				}
 
-				List<TapdataEvent> tapdataEvents = wrapTapdataEvent(events, SyncStage.CDC, offsetObj);
+				List<TapdataEvent> tapdataEvents = wrapTapdataEvent(tapEvents, SyncStage.CDC, offsetObj);
 				if (logger.isDebugEnabled()) {
-					logger.debug("Stream read {} of events, {}", events.size(), LoggerUtils.sourceNodeMessage(getConnectorNode()));
+					logger.debug("Stream read {} of events, {}", tapEvents.size(), LoggerUtils.sourceNodeMessage(getConnectorNode()));
 				}
 
 				if (streamReadFuncAspect != null)
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNode.java
index 153221d179..3080c09231 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNode.java
@@ -1,5 +1,6 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.data.pdk;
 
+import cn.hutool.core.collection.ConcurrentHashSet;
 import cn.hutool.core.util.ReUtil;
 import com.alibaba.fastjson.JSON;
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -45,6 +46,7 @@
 import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.control.HeartbeatEvent;
 import io.tapdata.entity.event.ddl.TapDDLEvent;
+import io.tapdata.entity.event.ddl.TapDDLUnknownEvent;
 import io.tapdata.entity.event.ddl.table.TapCreateTableEvent;
 import io.tapdata.entity.event.ddl.table.TapDropTableEvent;
 import io.tapdata.entity.event.dml.TapRecordEvent;
@@ -80,6 +82,8 @@
 import io.tapdata.pdk.core.monitor.PDKInvocationMonitor;
 import io.tapdata.pdk.core.utils.CommonUtils;
 import io.tapdata.schema.TapTableMap;
+import io.tapdata.supervisor.TaskNodeInfo;
+import io.tapdata.supervisor.TaskResourceSupervisorManager;
 import io.tapdata.threadgroup.ConnectorOnTaskThreadGroup;
 import lombok.SneakyThrows;
 import org.apache.commons.collections.CollectionUtils;
@@ -156,6 +160,7 @@ public abstract class HazelcastSourcePdkBaseNode extends HazelcastPdkBaseNode {
 	private ConnectorOnTaskThreadGroup connectorOnTaskThreadGroup;
 	protected DynamicAdjustMemoryService dynamicAdjustMemoryService;
 	private ConcurrentHashMap<String, Connections> connectionMap = new ConcurrentHashMap<>();
+	TaskResourceSupervisorManager taskResourceSupervisorManager = InstanceFactory.bean(TaskResourceSupervisorManager.class);
 
 	public HazelcastSourcePdkBaseNode(DataProcessorContext dataProcessorContext) {
 		super(dataProcessorContext);
@@ -184,13 +189,13 @@ private boolean needCdcDelay() {
 	@Override
 	protected void doInit(@NotNull Context context) throws TapCodeException {
         AutoRecovery.setEnqueueConsumer(getNode().getTaskId(), this::enqueue);
+		ConcurrentHashSet<TaskNodeInfo> taskNodeInfos = taskResourceSupervisorManager.getTaskNodeInfos();
+		ThreadGroup connectorOnTaskThreadGroup = getReuseOrNewThreadGroup(taskNodeInfos);
 		if (needCdcDelay()) {
 			this.cdcDelayCalculation = new CdcDelay();
 		} else {
 			this.cdcDelayCalculation = new CdcDelayDisable();
 		}
-		if (connectorOnTaskThreadGroup == null)
-			connectorOnTaskThreadGroup = new ConnectorOnTaskThreadGroup(dataProcessorContext);
 		this.sourceRunner = AsyncUtils.createThreadPoolExecutor(String.format("Source-Runner-%s[%s]", getNode().getName(), getNode().getId()), 2, connectorOnTaskThreadGroup, TAG);
 		this.sourceRunner.submitSync(() -> {
 			super.doInit(context);
@@ -891,7 +896,7 @@ protected List<TapdataEvent> wrapTapdataEvent(List<TapEvent> events, SyncStage s
 			if (null == tapEvent.getTime()) {
 				throw new NodeException("Invalid TapEvent, `TapEvent.time` should be NonNUll").context(getProcessorBaseContext()).event(tapEvent);
 			}
-			TapEvent tapEventCache = cdcDelayCalculation.filterAndCalcDelay(tapEvent, times -> AspectUtils.executeAspect(SourceCDCDelayAspect.class, () -> new SourceCDCDelayAspect().delay(times).dataProcessorContext(dataProcessorContext)));
+			TapEvent tapEventCache = cdcDelayCalculation.filterAndCalcDelay(tapEvent, times -> AspectUtils.executeAspect(SourceCDCDelayAspect.class, () -> new SourceCDCDelayAspect().delay(times).dataProcessorContext(dataProcessorContext)),this.dataProcessorContext.getTaskDto().getSyncType());
 			eventCache.add(tapEventCache);
 			boolean isLast = i == (size - 1);
 			TapdataEvent tapdataEvent;
@@ -922,13 +927,17 @@ protected TapdataEvent wrapTapdataEvent(TapEvent tapEvent, SyncStage syncStage,
 		}
 	}
 
-	private TapdataEvent wrapSingleTapdataEvent(TapEvent tapEvent, SyncStage syncStage, Object offsetObj, boolean isLast) {
+	protected TapdataEvent wrapSingleTapdataEvent(TapEvent tapEvent, SyncStage syncStage, Object offsetObj, boolean isLast) {
 		TapdataEvent tapdataEvent = null;
 		switch (sourceMode) {
 			case NORMAL:
 				tapdataEvent = new TapdataEvent();
 				break;
 			case LOG_COLLECTOR:
+				if (tapEvent instanceof TapDDLUnknownEvent) {
+					obsLogger.warn("DDL unknown event: " + tapEvent);
+					return null;
+				}
 				tapdataEvent = new TapdataShareLogEvent();
 				Connections connections = dataProcessorContext.getConnections();
 				Node<?> node = getNode();
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java
index 40e30b845e..76548c9105 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java
@@ -361,7 +361,7 @@ protected void doSnapshot(List<String> tableList) {
 																	if (null == event.getTime()) {
 																		throw new NodeException("Invalid TapEvent, `TapEvent.time` should be NonNUll").context(getProcessorBaseContext()).event(event);
 																	}
-																	return cdcDelayCalculation.filterAndCalcDelay(event, times -> AspectUtils.executeAspect(SourceCDCDelayAspect.class, () -> new SourceCDCDelayAspect().delay(times).dataProcessorContext(dataProcessorContext)));
+																	return cdcDelayCalculation.filterAndCalcDelay(event, times -> AspectUtils.executeAspect(SourceCDCDelayAspect.class, () -> new SourceCDCDelayAspect().delay(times).dataProcessorContext(dataProcessorContext)),this.dataProcessorContext.getTaskDto().getSyncType());
 																}).collect(Collectors.toList());
 
 																if (batchReadFuncAspect != null)
@@ -781,7 +781,7 @@ protected StreamReadConsumer generateStreamReadConsumer(ConnectorNode connectorN
 						if (null == event.getTime()) {
 							throw new NodeException("Invalid TapEvent, `TapEvent.time` should be NonNUll").context(getProcessorBaseContext()).event(event);
 						}
-						return cdcDelayCalculation.filterAndCalcDelay(event, times -> AspectUtils.executeAspect(SourceCDCDelayAspect.class, () -> new SourceCDCDelayAspect().delay(times).dataProcessorContext(dataProcessorContext)));
+						return cdcDelayCalculation.filterAndCalcDelay(event, times -> AspectUtils.executeAspect(SourceCDCDelayAspect.class, () -> new SourceCDCDelayAspect().delay(times).dataProcessorContext(dataProcessorContext)),this.dataProcessorContext.getTaskDto().getSyncType());
 					}).collect(Collectors.toList());
 
 					if (streamReadFuncAspect != null) {
@@ -877,18 +877,19 @@ private void sendCdcStartedEvent() {
 		}
 	}
 
-	private void doShareCdc() throws Exception {
+	protected void doShareCdc() throws Exception {
 		if (!isRunning()) {
 			return;
 		}
-		Optional.of(cdcDelayCalculation.addHeartbeatTable(new ArrayList<>(dataProcessorContext.getTapTableMap().keySet())))
-				.map(joinHeartbeat -> executeAspect(SourceJoinHeartbeatAspect.class, () -> new SourceJoinHeartbeatAspect().dataProcessorContext(dataProcessorContext).joinHeartbeat(joinHeartbeat)));
-		ShareCdcTaskContext shareCdcTaskContext = createShareCDCTaskContext();
 		TapTableMap<String, TapTable> tapTableMap = dataProcessorContext.getTapTableMap();
 		List<String> tables = new ArrayList<>(tapTableMap.keySet());
 		excludeRemoveTable(tables);
+		Optional.of(cdcDelayCalculation.addHeartbeatTable(tables))
+				.map(joinHeartbeat -> executeAspect(SourceJoinHeartbeatAspect.class, () -> new SourceJoinHeartbeatAspect().dataProcessorContext(dataProcessorContext).joinHeartbeat(joinHeartbeat)));
+		ShareCdcTaskContext shareCdcTaskContext = createShareCDCTaskContext();
 		this.syncProgressType = SyncProgress.Type.SHARE_CDC;
 		PDKMethodInvoker pdkMethodInvoker = createPdkMethodInvoker();
+		shareCdcTaskContext.setTableNames(tables);
 		try {
 			executeDataFuncAspect(StreamReadFuncAspect.class,
 					() -> new StreamReadFuncAspect()
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNode.java
index 74b1be8476..585b0a5626 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNode.java
@@ -1,6 +1,7 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.data.pdk;
 
 import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.ConcurrentHashSet;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.collect.Queues;
 import com.hazelcast.jet.core.Inbox;
@@ -40,6 +41,7 @@
 import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
 import io.tapdata.entity.schema.TapTable;
 import io.tapdata.entity.schema.value.TapMapValue;
+import io.tapdata.entity.utils.InstanceFactory;
 import io.tapdata.error.TapEventException;
 import io.tapdata.error.TapdataEventException;
 import io.tapdata.error.TaskTargetProcessorExCode_15;
@@ -77,7 +79,8 @@
 import io.tapdata.pdk.core.async.ThreadPoolExecutorEx;
 import io.tapdata.pdk.core.monitor.PDKInvocationMonitor;
 import io.tapdata.pdk.core.utils.CommonUtils;
-import io.tapdata.threadgroup.ConnectorOnTaskThreadGroup;
+import io.tapdata.supervisor.TaskNodeInfo;
+import io.tapdata.supervisor.TaskResourceSupervisorManager;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -150,10 +153,13 @@ public abstract class HazelcastTargetPdkBaseNode extends HazelcastPdkBaseNode {
 	protected Boolean unwindProcess = false;
 	protected boolean illegalDateAcceptable = false;
 	protected ConcurrentHashMap<String, Boolean> everHandleTapTablePrimaryKeysMap;
+	TaskResourceSupervisorManager taskResourceSupervisorManager = InstanceFactory.bean(TaskResourceSupervisorManager.class);
 
 	public HazelcastTargetPdkBaseNode(DataProcessorContext dataProcessorContext) {
 		super(dataProcessorContext);
-		queueConsumerThreadPool = AsyncUtils.createThreadPoolExecutor(String.format("Target-Queue-Consumer-%s[%s]@task-%s", getNode().getName(), getNode().getId(), dataProcessorContext.getTaskDto().getName()), 1, new ConnectorOnTaskThreadGroup(dataProcessorContext), TAG);
+		ConcurrentHashSet<TaskNodeInfo> taskNodeInfos = taskResourceSupervisorManager.getTaskNodeInfos();
+		ThreadGroup connectorOnTaskThreadGroup = getReuseOrNewThreadGroup(taskNodeInfos);
+		queueConsumerThreadPool = AsyncUtils.createThreadPoolExecutor(String.format("Target-Queue-Consumer-%s[%s]@task-%s", getNode().getName(), getNode().getId(), dataProcessorContext.getTaskDto().getName()), 1, connectorOnTaskThreadGroup, TAG);
 		//threadPoolExecutorEx = AsyncUtils.createThreadPoolExecutor("Target-" + getNode().getName() + "@task-" + dataProcessorContext.getTaskDto().getName(), 1, new ConnectorOnTaskThreadGroup(dataProcessorContext), TAG);
 		flushOffsetExecutor = new ScheduledThreadPoolExecutor(1, r->{
 			Thread thread = new Thread(r);
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNode.java
index 1c6b02c79f..69917d1ded 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNode.java
@@ -64,6 +64,7 @@ public class HazelcastJavaScriptProcessorNode extends HazelcastProcessorBaseNode
 
 	private static final Logger logger = LogManager.getLogger(HazelcastJavaScriptProcessorNode.class);
 	public static final String TAG = HazelcastJavaScriptProcessorNode.class.getSimpleName();
+	public static final String BEFORE = "before";
 
 	private Invocable engine;
 
@@ -228,7 +229,7 @@ protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, Pr
 		Map<String, Object> eventMap = MapUtil.obj2Map(processContextEvent);
 		Map<String, Object> contextMap = MapUtil.obj2Map(processContext);
 		contextMap.put("event", eventMap);
-		contextMap.put("before", before);
+		contextMap.put(BEFORE, before);
 		contextMap.put("info", tapEvent.getInfo());
 		contextMap.put("global", this.globalTaskContent);
 		Map<String, Object> context = this.processContextThreadLocal.get();
@@ -237,6 +238,7 @@ protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, Pr
 
 
 		AtomicReference<Object> scriptInvokeResult = new AtomicReference<>();
+		AtomicReference<Object> scriptInvokeBeforeResult = new AtomicReference<>();
 		if (StringUtils.equalsAnyIgnoreCase(processorBaseContext.getTaskDto().getSyncType(),
 				TaskDto.SYNC_TYPE_TEST_RUN,
 				TaskDto.SYNC_TYPE_DEDUCE_SCHEMA)) {
@@ -264,6 +266,10 @@ protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, Pr
 
 		} else {
 			scriptInvokeResult.set(engine.invokeFunction(ScriptUtil.FUNCTION_NAME, afterMapInRecord));
+			// handle before
+			if (standard && TapUpdateRecordEvent.TYPE == tapEvent.getType() && MapUtils.isNotEmpty(before)) {
+				scriptInvokeBeforeResult.set(engine.invokeFunction(ScriptUtil.FUNCTION_NAME, before));
+			}
 		}
 
 		if (StringUtils.isNotEmpty((CharSequence) context.get("op"))) {
@@ -277,25 +283,42 @@ protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, Pr
 				logger.debug("The event does not need to continue to be processed {}", tapdataEvent);
 			}
 		} else if (scriptInvokeResult.get() instanceof List) {
+			Integer beforeIndex = 0;
 			for (Object o : (List) scriptInvokeResult.get()) {
 				Map<String, Object> recordMap = new HashMap<>();
 				MapUtil.copyToNewMap((Map<String, Object>) o, recordMap);
 				TapdataEvent cloneTapdataEvent = (TapdataEvent) tapdataEvent.clone();
 				TapEvent returnTapEvent = getTapEvent(cloneTapdataEvent.getTapEvent(), op);
 				setRecordMap(returnTapEvent, op, recordMap);
+				flushBeforeIfNeed(scriptInvokeBeforeResult.get(), returnTapEvent, beforeIndex);
 				cloneTapdataEvent.setTapEvent(returnTapEvent);
 				consumer.accept(cloneTapdataEvent, processResult);
+				beforeIndex++;
 			}
 		} else {
 			Map<String, Object> recordMap = new HashMap<>();
 			MapUtil.copyToNewMap((Map<String, Object>) scriptInvokeResult.get(), recordMap);
 			TapEvent returnTapEvent = getTapEvent(tapEvent, op);
 			setRecordMap(returnTapEvent, op, recordMap);
+			flushBeforeIfNeed(scriptInvokeBeforeResult.get(), returnTapEvent, null);
 			tapdataEvent.setTapEvent(returnTapEvent);
 			consumer.accept(tapdataEvent, processResult);
 		}
 	}
 
+	protected void flushBeforeIfNeed (Object processedBefore, TapEvent returnTapEvent, Integer index) {
+		if (null == processedBefore) return;
+		if (processedBefore instanceof List && null != index) {
+			if (index >= ((List<?>) processedBefore).size()) return;
+			Object beforeMap = ((List<?>) processedBefore).get(index);
+			if (beforeMap instanceof Map) {
+				TapEventUtil.setBefore(returnTapEvent, (Map<String, Object>) beforeMap);
+			}
+		} else if (processedBefore instanceof Map) {
+			TapEventUtil.setBefore(returnTapEvent, (Map<String, Object>) processedBefore);
+		}
+	}
+
 	private TapEvent getTapEvent(TapEvent tapEvent, String op) {
 		if (StringUtils.equals(TapEventUtil.getOp(tapEvent), op)) {
 			return tapEvent;
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateUnionProcessorNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateUnionProcessorNode.java
new file mode 100644
index 0000000000..38fc241e00
--- /dev/null
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateUnionProcessorNode.java
@@ -0,0 +1,31 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.processor;
+
+import com.tapdata.entity.TapdataEvent;
+import com.tapdata.entity.task.context.ProcessorBaseContext;
+import com.tapdata.tm.commons.dag.process.MigrateUnionProcessorNode;
+import io.tapdata.entity.event.TapBaseEvent;
+import io.tapdata.entity.event.TapEvent;
+import io.tapdata.flow.engine.V2.util.TapEventUtil;
+
+import java.util.function.BiConsumer;
+
+public class HazelcastMigrateUnionProcessorNode extends HazelcastProcessorBaseNode{
+    private String tableName;
+
+    public HazelcastMigrateUnionProcessorNode(ProcessorBaseContext processorBaseContext) {
+        super(processorBaseContext);
+        if((getNode() instanceof MigrateUnionProcessorNode)){
+            MigrateUnionProcessorNode unionProcessorNode = (MigrateUnionProcessorNode)getNode();
+            this.tableName = unionProcessorNode.getTableName();
+        }
+    }
+
+    @Override
+    protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, ProcessResult> consumer) {
+        TapEvent tapEvent = tapdataEvent.getTapEvent();
+        if (tapEvent instanceof TapBaseEvent) {
+            ((TapBaseEvent) tapEvent).setTableId(tableName);
+        }
+        consumer.accept(tapdataEvent, getProcessResult(TapEventUtil.getTableId(tapEvent)));
+    }
+}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskClient.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskClient.java
index cd322f3be1..8274ee8c1f 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskClient.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskClient.java
@@ -80,12 +80,14 @@ public HazelcastTaskClient(Job job, TaskDto taskDto, ClientMongoOperator clientM
 				logger.warn("The task ping time monitor failed to start, which may affect the ping time functionality; Error: "
 						+ e.getMessage() + "\n" + Log4jUtil.getStackString(e));
 			}
+			this.autoRecovery = AutoRecovery.init(taskDto.getId().toHexString());
+		}else{
+			this.autoRecovery = null;
 		}
 		Optional<Node> cacheNode = taskDto.getDag().getNodes().stream().filter(n -> n instanceof CacheNode).findFirst();
 		cacheNode.ifPresent(c -> cacheName = ((CacheNode) c).getCacheName());
 		this.retryCounter = new AtomicInteger(0);
 		this.retrying = new AtomicBoolean(false);
-        this.autoRecovery = AutoRecovery.init(taskDto.getId().toHexString());
 	}
 
 	@Override
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskService.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskService.java
index 96f9408275..a0b1311120 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskService.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskService.java
@@ -849,6 +849,19 @@ public static HazelcastBaseNode createNode(
 								.build()
 				);
 				break;
+			case MIGRATE_UNION_PROCESSOR:
+				hazelcastNode = new HazelcastMigrateUnionProcessorNode(
+						DataProcessorContext.newBuilder()
+								.withTaskDto(taskDto)
+								.withNode(node)
+								.withNodes(nodes)
+								.withEdges(edges)
+								.withConfigurationCenter(config)
+								.withTapTableMap(tapTableMap)
+								.withTaskConfig(taskConfig)
+								.build()
+				);
+				break;
 			default:
 				hazelcastNode = new HazelcastBlank(
 						DataProcessorContext.newBuilder()
diff --git a/iengine/iengine-common/src/main/java/io/tapdata/flow/engine/V2/node/NodeTypeEnum.java b/iengine/iengine-common/src/main/java/io/tapdata/flow/engine/V2/node/NodeTypeEnum.java
index fc6894b4e2..2edc3e2d80 100644
--- a/iengine/iengine-common/src/main/java/io/tapdata/flow/engine/V2/node/NodeTypeEnum.java
+++ b/iengine/iengine-common/src/main/java/io/tapdata/flow/engine/V2/node/NodeTypeEnum.java
@@ -22,6 +22,7 @@ public enum NodeTypeEnum {
 	JS_PROCESSOR("js_processor"),
 	STANDARD_JS_PROCESSOR("standard_js_processor"),
 	UNION_PROCESSOR("union_processor"),
+	MIGRATE_UNION_PROCESSOR("migrate_union_processor"),
 
 	MIGRATE_JS_PROCESSOR("migrate_js_processor"),
 	MIGRATE_PYTHON_PROCESS("migrate_python_process"),
diff --git a/iengine/modules/task-resource-supervisor-module/pom.xml b/iengine/modules/task-resource-supervisor-module/pom.xml
index f72d770112..d5a68bfe38 100644
--- a/iengine/modules/task-resource-supervisor-module/pom.xml
+++ b/iengine/modules/task-resource-supervisor-module/pom.xml
@@ -18,6 +18,37 @@
             <version>1.2.3-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
+        <!-- Test Dependencies -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.junit.vintage</groupId>
+                    <artifactId>junit-vintage-engine</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.logging.log4j</groupId>
+                    <artifactId>log4j-to-slf4j</artifactId>
+                </exclusion>
+            </exclusions>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.vintage</groupId>
+            <artifactId>junit-vintage-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/iengine/modules/task-resource-supervisor-module/src/main/java/io/tapdata/supervisor/SupervisorAspectTask.java b/iengine/modules/task-resource-supervisor-module/src/main/java/io/tapdata/supervisor/SupervisorAspectTask.java
index cf87478a81..c268c542d7 100644
--- a/iengine/modules/task-resource-supervisor-module/src/main/java/io/tapdata/supervisor/SupervisorAspectTask.java
+++ b/iengine/modules/task-resource-supervisor-module/src/main/java/io/tapdata/supervisor/SupervisorAspectTask.java
@@ -13,6 +13,7 @@
 import io.tapdata.entity.utils.DataMap;
 import io.tapdata.pdk.apis.consumer.StreamReadConsumer;
 import io.tapdata.supervisor.util.TapTaskThreadGroupUtil;
+import io.tapdata.threadgroup.ConnectorOnTaskThreadGroup;
 import io.tapdata.threadgroup.utils.ThreadGroupUtil;
 
 import java.util.*;
@@ -42,16 +43,21 @@ private Void handleDataNode(DataNodeThreadGroupAspect aspect) {
         return this.addAspect(aspect);
     }
 
-    private Void addAspect(ThreadGroupAspect aspect){
+    protected Void addAspect(ThreadGroupAspect aspect) {
+        connectorId = aspect.getNode().getId();
         Optional.ofNullable(aspect).flatMap(a -> Optional.ofNullable(aspect.getThreadGroup())).ifPresent(t -> {
-            TaskNodeInfo info = new TaskNodeInfo();
-            info.setNodeThreadGroup(t);
-            info.setSupervisorAspectTask(this);
-            info.setNode(aspect.getNode());
-            info.setAssociateId(aspect.getAssociateId());
-            connectorId = aspect.getNode().getId();
-            taskResourceSupervisorManager.addTaskSubscribeInfo(info);
-            threadGroupMap.put(t, info);
+            TaskNodeInfo taskNodeInfo = taskResourceSupervisorManager.getTaskNodeInfos()
+                    .stream().filter(nodeInfo -> nodeInfo.getNodeThreadGroup().equals(aspect.getThreadGroup()))
+                    .findAny().orElse(null);
+            if (null == taskNodeInfo) {
+                taskNodeInfo = new TaskNodeInfo();
+                taskNodeInfo.setNodeThreadGroup(t);
+                taskNodeInfo.setNode(aspect.getNode());
+                taskResourceSupervisorManager.addTaskSubscribeInfo(taskNodeInfo);
+            }
+            taskNodeInfo.setSupervisorAspectTask(this);
+            taskNodeInfo.setAssociateId(aspect.getAssociateId());
+            threadGroupMap.put(t, taskNodeInfo);
         });
         return null;
     }
@@ -75,25 +81,23 @@ public void onStart(TaskStartAspect startAspect) {
     @Override
     public void onStop(TaskStopAspect stopAspect) {
         super.onStop(stopAspect);
-        //ClassLifeCircleMonitor circleMonitor = InstanceFactory.instance(ClassLifeCircleMonitor.class);
-        //Map<Object, ClassOnThread> summary = circleMonitor.summary();
         isStarted = false;
         if (null != this.threadGroupMap && !this.threadGroupMap.isEmpty()) {
             for (Map.Entry<ThreadGroup, TaskNodeInfo> infoEntry : threadGroupMap.entrySet()) {
                 ThreadGroup group = infoEntry.getKey();
                 TaskNodeInfo info = infoEntry.getValue();
-                try {
-                    group.destroy();
-                    taskResourceSupervisorManager.removeTaskSubscribeInfo(info);
-                    info.setHasLeaked(Boolean.FALSE);
-                    info.setSupervisorAspectTask(null);
-                    info.setNodeThreadGroup(null);
-                    info.setNode(null);
-                    threadGroupMap.remove(group);
-                } catch (Exception e) {
-                    info.hasLaked = Boolean.TRUE;
-                    //@todo 延时30s再destroy后统计节点上泄露的线程
+                synchronized (info.getTaskNodeInfoLock()){
+                    try {
+                        group.destroy();
+                        info.setHasLeaked(Boolean.FALSE);
+                        taskResourceSupervisorManager.removeTaskSubscribeInfo(info);
+                        threadGroupMap.remove(group);
+                    } catch (Exception e) {
+                        info.hasLaked = Boolean.TRUE;
+                        //@todo 延时30s再destroy后统计节点上泄露的线程
+                    }
                 }
+
             }
         }
     }
diff --git a/iengine/modules/task-resource-supervisor-module/src/main/java/io/tapdata/supervisor/TaskNodeInfo.java b/iengine/modules/task-resource-supervisor-module/src/main/java/io/tapdata/supervisor/TaskNodeInfo.java
index 19aad1a005..13582d4a16 100644
--- a/iengine/modules/task-resource-supervisor-module/src/main/java/io/tapdata/supervisor/TaskNodeInfo.java
+++ b/iengine/modules/task-resource-supervisor-module/src/main/java/io/tapdata/supervisor/TaskNodeInfo.java
@@ -13,7 +13,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
-class TaskNodeInfo implements MemoryFetcher {
+public class TaskNodeInfo implements MemoryFetcher {
     public static final int STACK_LENGTH = 10;
     SupervisorAspectTask supervisorAspectTask;
     String associateId;
@@ -21,6 +21,7 @@ class TaskNodeInfo implements MemoryFetcher {
     com.tapdata.tm.commons.dag.Node<?> node;
     private Map<String, List<Node>> typeConnectionIdPDKNodeMap = new ConcurrentHashMap<>();
     private ThreadGroup nodeThreadGroup;
+    private final Object taskNodeInfoLock = new Object();
 
     @Override
     public DataMap memory(String keyRegex, String memoryLevel) {
@@ -154,6 +155,10 @@ public void setNodeThreadGroup(ThreadGroup nodeThreadGroup) {
         this.nodeThreadGroup = nodeThreadGroup;
     }
 
+    public Object getTaskNodeInfoLock() {
+        return taskNodeInfoLock;
+    }
+
     public String getAssociateId() {
         return associateId;
     }
diff --git a/iengine/modules/task-resource-supervisor-module/src/main/java/io/tapdata/supervisor/TaskResourceSupervisorManager.java b/iengine/modules/task-resource-supervisor-module/src/main/java/io/tapdata/supervisor/TaskResourceSupervisorManager.java
index fc8814dfa9..554b755389 100644
--- a/iengine/modules/task-resource-supervisor-module/src/main/java/io/tapdata/supervisor/TaskResourceSupervisorManager.java
+++ b/iengine/modules/task-resource-supervisor-module/src/main/java/io/tapdata/supervisor/TaskResourceSupervisorManager.java
@@ -11,6 +11,9 @@
 
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
@@ -21,10 +24,38 @@ public class TaskResourceSupervisorManager implements MemoryFetcher {
 
     private final ConcurrentHashSet<TaskNodeInfo> taskNodeInfos = new ConcurrentHashSet<>();
     private final Map<ThreadGroup, DisposableNodeInfo> disposableThreadGroupMap = new ConcurrentHashMap<>();
+    private static final ScheduledExecutorService cleanTaskNodeThreadGroupPool = new ScheduledThreadPoolExecutor(1, r -> new Thread(r, "clean-taskNode-threadGroup-thread"));
     private String userId;
     private String processId;
+    private Long lastCleanThreadGroupTime = null;
+    public final Long CHECK_THREAD_GROUP_POOL_PERIOD = 10 * 60L;
+
+    public final Long CHECK_THREAD_GROUP_POOL_INITIAL_DELAY = 30L;
+
+
+    public String CLEAN_LEAKED_THREAD_GROUP_MINUTES = System.getenv().getOrDefault("CLEAN_LEAKED_THREAD_GROUP_MINUTES", "30");
+
+    public String CLEAN_LEAKED_THREAD_GROUP_THRESHOLD = System.getenv().getOrDefault("CLEAN_LEAKED_THREAD_GROUP_THRESHOLD", "1000");
+
+
+    public void cleanThreadGroup() {
+        if (Objects.isNull(lastCleanThreadGroupTime)) {
+            lastCleanThreadGroupTime = System.currentTimeMillis();
+        }
+        List<TaskNodeInfo> leakedList = taskNodeInfos.stream().filter(TaskNodeInfo::hasLaked).collect(Collectors.toList());
+        int leakedSize = leakedList.size();
+        if (System.currentTimeMillis() - lastCleanThreadGroupTime > TimeUnit.MINUTES.toMillis(Long.parseLong(CLEAN_LEAKED_THREAD_GROUP_MINUTES))
+                || leakedSize >= Integer.parseInt(CLEAN_LEAKED_THREAD_GROUP_THRESHOLD)) {
+            for (TaskNodeInfo taskNodeInfo : leakedList) {
+                destroyTaskNodeInfo(taskNodeInfo);
+            }
+            lastCleanThreadGroupTime = System.currentTimeMillis();
+        }
+    }
+
 
     public TaskResourceSupervisorManager() {
+        cleanTaskNodeThreadGroupPool.scheduleAtFixedRate(this::cleanThreadGroup, CHECK_THREAD_GROUP_POOL_INITIAL_DELAY, CHECK_THREAD_GROUP_POOL_PERIOD, TimeUnit.SECONDS);
     }
 
     private void start() {
@@ -37,6 +68,10 @@ public void addTaskSubscribeInfo(TaskNodeInfo taskNodeInfo) {
         }
     }
 
+    public ConcurrentHashSet<TaskNodeInfo> getTaskNodeInfos() {
+        return taskNodeInfos;
+    }
+
     public void removeTaskSubscribeInfo(TaskNodeInfo taskNodeInfo) {
         if (Objects.nonNull(taskNodeInfo)){
             taskNodeInfo.setNodeThreadGroup(null);
@@ -125,20 +160,9 @@ public DataMap memory(String keyRegex, String memoryLevel) {
                 .kv("msg", total > 0 ? "Succeed" :" The thread occupancy of the corresponding resource has been released normally, and the corresponding information cannot be provided temporarily. Retrieve the summary data. ");
     }
 
-    private void summary(Set<TaskNodeInfo> summarySet, Set<SupervisorAspectTask> aliveSet, Set<SupervisorAspectTask> leakedSet) {
+    protected void summary(Set<TaskNodeInfo> summarySet, Set<SupervisorAspectTask> aliveSet, Set<SupervisorAspectTask> leakedSet) {
         for (TaskNodeInfo taskNodeInfo : summarySet) {
-            if (taskNodeInfo.isHasLaked()) {
-                try {
-                    taskNodeInfo.getNodeThreadGroup().destroy();
-                    taskNodeInfo.setHasLeaked(Boolean.FALSE);
-                    taskNodeInfo.getSupervisorAspectTask().getThreadGroupMap().remove(taskNodeInfo.getNodeThreadGroup());
-                    taskNodeInfo.setSupervisorAspectTask(null);
-                    taskNodeInfo.setNodeThreadGroup(null);
-                    continue;
-                } catch (Exception e1) {
-                    taskNodeInfo.setHasLeaked(Boolean.TRUE);
-                }
-            }
+            if (destroyTaskNodeInfo(taskNodeInfo)) continue;
             SupervisorAspectTask aspectTask = taskNodeInfo.getSupervisorAspectTask();
             if (Objects.isNull(aspectTask)) {
                 continue;
@@ -151,6 +175,23 @@ private void summary(Set<TaskNodeInfo> summarySet, Set<SupervisorAspectTask> ali
         }
     }
 
+    public boolean destroyTaskNodeInfo(TaskNodeInfo taskNodeInfo) {
+        synchronized (taskNodeInfo.getTaskNodeInfoLock()){
+            if (taskNodeInfo.isHasLaked()) {
+                try {
+                    taskNodeInfo.getNodeThreadGroup().destroy();
+                    taskNodeInfo.setHasLeaked(Boolean.FALSE);
+                    taskNodeInfo.getSupervisorAspectTask().getThreadGroupMap().remove(taskNodeInfo.getNodeThreadGroup());
+                    this.removeTaskSubscribeInfo(taskNodeInfo);
+                    return true;
+                } catch (Exception e1) {
+                    taskNodeInfo.setHasLeaked(Boolean.TRUE);
+                }
+            }
+        }
+        return false;
+    }
+
     private void summaryDisposable(Map<ThreadGroup, DisposableNodeInfo> disposableMap, Set<DisposableNodeInfo> aliveConnectorSet, Set<DisposableNodeInfo> leakedConnectorSet) {
         Iterator<Map.Entry<ThreadGroup, DisposableNodeInfo>> iterator = disposableMap.entrySet().iterator();
         while (iterator.hasNext()) {
@@ -179,6 +220,7 @@ private void summaryDisposable(Map<ThreadGroup, DisposableNodeInfo> disposableMa
         }
     }
 
+
     public String getUserId() {
         return userId;
     }
diff --git a/iengine/pom.xml b/iengine/pom.xml
index de383dc875..261168471e 100644
--- a/iengine/pom.xml
+++ b/iengine/pom.xml
@@ -36,7 +36,6 @@
 
         <!-- Databases -->
         <version.mysql.server>5.7</version.mysql.server>
-        <version.mysql.driver>8.0.23</version.mysql.driver>
         <version.hana.driver>2.3.58</version.hana.driver>
         <version.mysql.binlog>0.28.0</version.mysql.binlog>
         <version.mongo.server>3.2.12</version.mongo.server>
@@ -310,16 +309,6 @@
             </dependency>
 
             <!-- MySQL JDBC Driver, Binlog reader, Geometry support -->
-            <dependency>
-                <groupId>mysql</groupId>
-                <artifactId>mysql-connector-java</artifactId>
-                <version>${version.mysql.driver}</version>
-            </dependency>
-            <dependency>
-                <groupId>com.dm</groupId>
-                <artifactId>dmjdbc7</artifactId>
-                <version>1.7.0</version>
-            </dependency>
             <dependency>
                 <groupId>com.zendesk</groupId>
                 <artifactId>mysql-binlog-connector-java</artifactId>
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/base/aop/MeasureAOP.java b/manager/tm-api/src/main/java/com/tapdata/tm/base/aop/MeasureAOP.java
index d0025c7ada..42fb786294 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/base/aop/MeasureAOP.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/base/aop/MeasureAOP.java
@@ -40,6 +40,8 @@
 @Aspect
 @Component
 public class MeasureAOP {
+    public static final String GREATER = "GREATER";
+    public static final String LESS = "LESS";
 
     private final TaskService taskService;
     private final AlarmService alarmService;
@@ -76,7 +78,7 @@ public void addAgentMeasurement(JoinPoint joinPoint) {
                             Optional.ofNullable(ruleMap.get(taskId)).ifPresent(rules -> {
                                 Map<AlarmKeyEnum, AlarmRuleDto> collect = rules.stream().collect(Collectors.toMap(AlarmRuleDto::getKey, Function.identity(), (e1, e2) -> e1));
                                 if (!collect.isEmpty()) {
-                                    taskIncrementDelayAlarm(taskDto, taskId, vs.get("replicateLag"), collect.get(AlarmKeyEnum.TASK_INCREMENT_DELAY), userDetail);
+                                    taskIncrementDelayAlarm(taskDto, taskId, vs.get("replicateLag"), collect.get(AlarmKeyEnum.TASK_INCREMENT_DELAY));
                                 }
                             });
                         }
@@ -181,7 +183,7 @@ private void setTaskSnapshotDate(Map<String, Number> vs, String taskId, TaskDto
         }
     }
 
-    private void taskIncrementDelayAlarm(TaskDto task, String taskId, Number replicateLag, AlarmRuleDto alarmRuleDto, UserDetail userDetail) {
+    protected void taskIncrementDelayAlarm(TaskDto task, String taskId, Number replicateLag, AlarmRuleDto alarmRuleDto) {
         // check task start cdc
         if (Objects.isNull(task.getCurrentEventTimestamp()) || Objects.isNull(replicateLag)) {
             return;
@@ -198,7 +200,7 @@ private void taskIncrementDelayAlarm(TaskDto task, String taskId, Number replica
             infoMap = Maps.newHashMap();
         }
 
-        String flag = alarmRuleDto.getEqualsFlag() == -1 ? "小于" : "大于";
+        String flag = alarmRuleDto.getEqualsFlag() == -1 ? LESS : GREATER;
 
         boolean b;
         if (alarmRuleDto.getEqualsFlag() == -1) {
@@ -261,15 +263,6 @@ private void taskIncrementDelayAlarm(TaskDto task, String taskId, Number replica
             alarmInfo.setLevel(Level.WARNING);
             alarmInfo.setSummary(summary);
             alarmService.save(alarmInfo);
-
-            if (needInspect) {
-                // excute inspect task
-//                CommonUtils.ignoreAnyError(() -> {
-//                    InspectDto inspectDto = inspectService.createCheckByTask(task, userDetail);
-//
-//                    inspectService.executeInspect(Where.where("id", inspectDto.getId().toHexString()), inspectDto, userDetail);
-//                }, "excute inspect task");
-            }
         } else {
             Optional<AlarmInfo> first = alarmInfos.stream().filter(info -> AlarmStatusEnum.ING.equals(info.getStatus()) || AlarmStatusEnum.RECOVER.equals(info.getStatus())).findFirst();
             if (first.isPresent()) {
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/base/dto/Filter.java b/manager/tm-api/src/main/java/com/tapdata/tm/base/dto/Filter.java
index 72aa3ad30c..0af1217ab3 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/base/dto/Filter.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/base/dto/Filter.java
@@ -79,7 +79,7 @@ public int getLimit() {
 
 	@Override
 	public List<String> getSort() {
-		List<String> sort = super.getSort();
+		List<String> sort = new ArrayList<>(super.getSort());
 		if (order != null) {
 			if (order instanceof String) {
 				if (StringUtils.isNotBlank((String)order)) {
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/base/dto/PageParameter.java b/manager/tm-api/src/main/java/com/tapdata/tm/base/dto/PageParameter.java
new file mode 100644
index 0000000000..6d5037c3cd
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/base/dto/PageParameter.java
@@ -0,0 +1,11 @@
+package com.tapdata.tm.base.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class PageParameter {
+    private Integer page;
+    private Integer pageSize;
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/ds/entity/DataSourceEntity.java b/manager/tm-api/src/main/java/com/tapdata/tm/ds/entity/DataSourceEntity.java
index 07eec418a8..429172a47d 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/ds/entity/DataSourceEntity.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/ds/entity/DataSourceEntity.java
@@ -312,6 +312,8 @@ public class DataSourceEntity extends BaseEntity implements IDataPermissionEntit
 
     private String accessNodeProcessId;
 
+    private String priorityProcessId;
+
     private TimeZone timeZone;
 
     private String connectionString;
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectCronJob.java b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectCronJob.java
index bac4e90717..ac35265d4e 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectCronJob.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectCronJob.java
@@ -7,7 +7,6 @@
 import com.tapdata.tm.user.service.UserService;
 import com.tapdata.tm.utils.MongoUtils;
 import com.tapdata.tm.utils.SpringContextHelper;
-import com.tapdata.tm.worker.service.WorkerService;
 import lombok.extern.slf4j.Slf4j;
 import org.quartz.Job;
 import org.quartz.JobExecutionContext;
@@ -18,23 +17,11 @@
 @Component
 @Slf4j
 public class InspectCronJob implements Job {
-/*
-    @Resource
-    WorkerService workerService;
-
-    @Resource
-    InspectService inspectService;
-
-    @Resource
-    UserService userService;
-*/
-
-
     @Override
     public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
         InspectService inspectService = SpringContextHelper.getBean(InspectService.class);
+        InspectTaskService inspectTaskService = SpringContextHelper.getBean(InspectTaskService.class);
         UserService userService = SpringContextHelper.getBean(UserService.class);
-        WorkerService workerService = SpringContextHelper.getBean(WorkerService.class);
 
         JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
         String inspectId = jobKey.getName();
@@ -42,16 +29,14 @@ public void execute(JobExecutionContext jobExecutionContext) throws JobExecution
         InspectDto inspectDto = inspectService.findById(MongoUtils.toObjectId(inspectId));
 
         String status = inspectDto.getStatus();
-        if (InspectStatusEnum.SCHEDULING.getValue().equals(status)||InspectStatusEnum.RUNNING.getValue().equals(status)) {
-            log.info("inspect {},status:{}  不用在进行校验", inspectId,status);
-
+        if (InspectStatusEnum.SCHEDULING.getValue().equals(status) || InspectStatusEnum.RUNNING.getValue().equals(status)) {
+            log.info("inspect {},status:{}  不用在进行校验", inspectId, status);
         } else {
-            log.info("inspect {},status:{}  定时在进行校验", inspectId,status);
+            log.info("inspect {},status:{}  定时在进行校验", inspectId, status);
             UserDetail userDetail = userService.loadUserById(MongoUtils.toObjectId(inspectDto.getUserId()));
-
-            Where where=new Where();
-            where.put("id",inspectId);
-            inspectService.doExecuteInspect(where,inspectDto,userDetail);
+            Where where = new Where();
+            where.put("id", inspectId);
+            inspectTaskService.executeInspect(where, inspectDto, userDetail);
         }
     }
 }
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectDetailsService.java b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectDetailsService.java
index 6bc7fcec21..4a22f1e673 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectDetailsService.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectDetailsService.java
@@ -1,17 +1,29 @@
 package com.tapdata.tm.inspect.service;
 
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
 import com.tapdata.tm.base.service.BaseService;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.inspect.bean.Source;
+import com.tapdata.tm.inspect.bean.Stats;
 import com.tapdata.tm.inspect.dto.InspectDetailsDto;
+import com.tapdata.tm.inspect.dto.InspectResultDto;
 import com.tapdata.tm.inspect.entity.InspectDetailsEntity;
 import com.tapdata.tm.inspect.repository.InspectDetailsRepository;
 import com.tapdata.tm.inspect.vo.FailTableAndRowsVo;
 import lombok.NonNull;
 import org.bson.types.ObjectId;
+import org.springframework.data.domain.Sort;
 import org.springframework.data.mongodb.core.aggregation.Aggregation;
 import org.springframework.data.mongodb.core.aggregation.AggregationResults;
 import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
 
+import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipOutputStream;
 
 public abstract class InspectDetailsService extends BaseService<InspectDetailsDto, InspectDetailsEntity, ObjectId, InspectDetailsRepository> {
     public InspectDetailsService(@NonNull InspectDetailsRepository repository) {
@@ -31,4 +43,66 @@ public FailTableAndRowsVo totalFailTableAndRows(String inspectResultId) {
         return null;
     }
 
+
+    public void export(InspectDetailsDto inspectDetails, ZipOutputStream stream, UserDetail userDetail, InspectResultService inspectResultService)
+            throws IOException {
+        JSONArray jsonArray = new JSONArray();
+        Map<String, JSONObject> map = new HashMap<>();
+        InspectResultDto inspectResultDto = inspectResultService.findById(new ObjectId(inspectDetails.getInspectResultId()), userDetail);
+        List<Stats> stats = inspectResultDto.getStats();
+        Query query = Query.query(Criteria.where("inspectResultId").is(inspectDetails.getInspectResultId()));
+        Sort sort = Sort.by("createTime").descending();
+        query.with(sort);
+        List<InspectDetailsDto> inspectDetailsDto = findAllDto(query, userDetail);
+        inspectDetailsDto.forEach(inspectDetailsDtoTmp -> {
+            JSONObject jsonObject = new JSONObject();
+            Stats statsTmp = null;
+            String taskId = inspectDetailsDtoTmp.getTaskId();
+            for (int index = 0; index < stats.size(); index++) {
+                statsTmp = stats.get(index);
+                if (taskId.equals(statsTmp.getTaskId())) {
+                    break;
+                }
+            }
+            if (!"failed".equals(statsTmp.getResult())) {
+                return;
+            }
+            JSONObject jsonObjectTmp = new JSONObject();
+            jsonObjectTmp.put("source", inspectDetailsDtoTmp.getSource());
+            jsonObjectTmp.put("target", inspectDetailsDtoTmp.getTarget());
+            if (!map.containsKey(taskId)) {
+                map.put(taskId, jsonObject);
+
+                Source source = statsTmp.getSource();
+                jsonObject.put("sourceTableName", source.getTable() + "/" + source.getConnectionName()
+                        + "(Row:" + statsTmp.getSourceTotal() + ")");
+                Source target = statsTmp.getTarget();
+                jsonObject.put("targetTableName", target.getTable() + "/" + target.getConnectionName()
+                        + "(Row:" + statsTmp.getTargetTotal() + ")");
+                jsonObject.put("checkResult", "failed");
+                long count = statsTmp.getTargetTotal() - statsTmp.getSourceTotal();
+                if (count < 0) {
+                    jsonObject.put("targetCountLess", Math.abs(count));
+                } else {
+                    jsonObject.put("targetCountMore", count);
+                }
+
+                jsonObject.put("tableDiffCount", statsTmp.getSourceOnly() + statsTmp.getTargetOnly() +
+                        statsTmp.getRowFailed());
+                JSONArray jsonArrayTmp = new JSONArray();
+                jsonArrayTmp.add(jsonObjectTmp);
+                jsonObject.put("data", jsonArrayTmp);
+                jsonArray.add(jsonObject);
+            } else {
+                jsonObject = map.get(taskId);
+                JSONArray jsonArrayData = (JSONArray) jsonObject.get("data");
+                jsonArrayData.add(jsonObjectTmp);
+            }
+        });
+        stream.write(jsonArray.toString().getBytes());
+    }
+
+
+
+
 }
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/roleMapping/service/RoleMappingService.java b/manager/tm-api/src/main/java/com/tapdata/tm/roleMapping/service/RoleMappingService.java
index d7d1a64554..5f48219978 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/roleMapping/service/RoleMappingService.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/roleMapping/service/RoleMappingService.java
@@ -1,5 +1,6 @@
 package com.tapdata.tm.roleMapping.service;
 
+import cn.hutool.core.collection.CollUtil;
 import com.tapdata.tm.base.service.BaseService;
 import com.tapdata.tm.config.security.UserDetail;
 import com.tapdata.tm.roleMapping.dto.PrincipleType;
@@ -7,11 +8,16 @@
 import com.tapdata.tm.roleMapping.entity.RoleMappingEntity;
 import com.tapdata.tm.roleMapping.repository.RoleMappingRepository;
 import com.tapdata.tm.user.entity.User;
+import com.tapdata.tm.utils.Lists;
 import lombok.NonNull;
 import org.bson.types.ObjectId;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
 
 public abstract class RoleMappingService extends BaseService<RoleMappingDto, RoleMappingEntity, ObjectId, RoleMappingRepository> {
     public RoleMappingService(@NonNull RoleMappingRepository repository) {
@@ -27,4 +33,24 @@ public RoleMappingService(@NonNull RoleMappingRepository repository) {
 
     @Transactional
     public abstract List<RoleMappingDto> saveAll(List<RoleMappingDto> roleDtos, UserDetail userDetail);
+    
+    public abstract void removeRoleFromUser(String roleMappingId);
+
+    public List<RoleMappingDto> updateUserRoleMapping(List<RoleMappingDto> roleDto, UserDetail userDetail) {
+        if (CollUtil.isEmpty(roleDto)) return Lists.newArrayList();
+        Criteria c = new Criteria();
+        List<Criteria> or = Lists.newArrayList();
+        roleDto.stream()
+                .filter(Objects::nonNull)
+                .forEach(role -> {
+                    Criteria criteria = Criteria.where("roleId").is(role.getRoleId())
+                            .and("principalId").is(role.getPrincipalId())
+                            .and("principalType").is(role.getPrincipalType());
+                    upsert(Query.query(criteria), role, userDetail);
+                    or.add(criteria);
+
+                });
+        c.orOperator(or);
+        return Optional.ofNullable(findAll(Query.query(c))).orElse(Lists.newArrayList());
+    }
 }
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/task/entity/TaskEntity.java b/manager/tm-api/src/main/java/com/tapdata/tm/task/entity/TaskEntity.java
index dcc61cbc32..82f9f6dfb9 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/task/entity/TaskEntity.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/task/entity/TaskEntity.java
@@ -155,6 +155,8 @@ public class TaskEntity extends BaseEntity implements IDataPermissionEntity {
 
     private String accessNodeProcessId;
 
+    private String priorityProcessId;
+
     //是否开启数据校验 （true：开启校验；false：关闭校验）
     private Boolean isAutoInspect = false;
     private Boolean canOpenInspect = false;
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/user/service/UserService.java b/manager/tm-api/src/main/java/com/tapdata/tm/user/service/UserService.java
index fe06fa8330..2b4d1241f1 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/user/service/UserService.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/user/service/UserService.java
@@ -9,6 +9,7 @@
 import com.tapdata.tm.user.entity.User;
 import com.tapdata.tm.user.param.ResetPasswordParam;
 import com.tapdata.tm.user.repository.UserRepository;
+import com.tapdata.tm.utils.SendStatus;
 import lombok.NonNull;
 import org.bson.types.ObjectId;
 import org.springframework.security.core.userdetails.UserDetailsService;
@@ -49,7 +50,7 @@ public UserService(@NonNull UserRepository repository) {
 
     public abstract UserDto updateEmail(UserDetail loginUser, BindEmailReq bindEmailReq);
 
-    public abstract Boolean sendValidateCde(String email);
+    public abstract SendStatus sendValidateCde(String email);
 
     public abstract Long reset(ResetPasswordParam resetPasswordParam);
 
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/utils/MailUtils.java b/manager/tm-api/src/main/java/com/tapdata/tm/utils/MailUtils.java
index eb8df15ecf..c8fb2f995e 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/utils/MailUtils.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/utils/MailUtils.java
@@ -18,19 +18,29 @@
 import org.jsoup.nodes.Document;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.ApplicationContext;
 import org.springframework.stereotype.Component;
 
-import javax.mail.*;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Transport;
 import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeMessage;
-import java.io.*;
-import java.util.*;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
 import java.util.stream.Collectors;
 
 
 /**
- * todo Setting表的邮箱配置，是怎么样初始化进去的  邮箱已经变 了，应该怎么修改
+ * Setting表的邮箱配置，是怎么样初始化进去的  邮箱已经变 了，应该怎么修改
  * SMTP 地址
  * smtp.feishu.cn
  * IMAP/SMTP 密码
@@ -47,29 +57,35 @@
 @Slf4j
 @Component
 public class MailUtils {
-
     private String host;
     private Integer port;
-
     private String user;
     private String sendAddress;
-
     private String password;
 
-    public final static Integer CLOUD_MAIL_LIMIT = 10;
+    public static final Integer CLOUD_MAIL_LIMIT = 10;
+
     @Autowired
     SettingsService settingsService;
-
     @Autowired
     BlacklistService blacklistService;
 
     private static List<String> productList;
     @Value("#{'${spring.profiles.include:idaas}'.split(',')}")
     private void setProductList(List<String> versionList){
-        productList = versionList;
+        this.productList = versionList;
     }
 
-    public final static String SEND_STATUS_FALSE = "false";
+    public static final String SEND_STATUS_FALSE = "false";
+    public static final String MAIL_TEMPLATE = "mailTemplate.html";
+    public static final String USER_NAME = "username";
+    public static final String SYS_NAME = "sysName";
+    public static final String SYS_NAME_AGENT = "Your Agent：";
+    public static final String SYS_NAME_TASK = "Your Task：";
+    public static final String AGENT_NAME = "agentName";
+    public static final String MAIL_CONTENT = "mailContent";
+    public static final String CLICK_HREF = "clickHref";
+    public static final String EMAIL_SEND_EXCEPTION = "邮件发送异常";
     /**
      * 发送html形式的邮件
      */
@@ -80,66 +96,21 @@ public SendStatus sendHtmlMail(String subject, List<String> toList, String usern
             return sendStatus;
         }
         // 读取html模板
-        String html = readHtmlToString("mailTemplate.html");
+        String html = readHtmlToString(MAIL_TEMPLATE);
 
         // 写入模板内容
         Document doc = Jsoup.parse(html);
-        doc.getElementById("username").html(username);
+        doc.getElementById(USER_NAME).html(username);
 
         if (StringUtils.isEmpty(agentName)) {
             sendStatus.setErrorMessage("agentName 为空");
             return sendStatus;
         }
-        doc.getElementById("sysName").html("Your Agent：");
-        doc.getElementById("agentName").html(agentName);
-        doc.getElementById("mailContent").html(maiContent);
-        doc.getElementById("clickHref").attr("href", emailHref);
-
-        String result = doc.toString();
-        Properties props = new Properties();
-        props.put("mail.smtp.host", "");
-        props.put("mail.smtp.auth", "true");
-        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
-        props.put("mail.smtp.socketFactory.fallback", "true");
-        Session session = Session.getDefaultInstance(props);
-        session.setDebug(true);
-
-        Transport transport = null;
-        MimeMessage message = new MimeMessage(session);
-        try {
-            //初始化发送邮件配置
-            this.initMailConfig();
-            message.setFrom(new InternetAddress(this.sendAddress));// 设置发件人的地址
-            InternetAddress[] internetAddressList = getInternetAddress(notInBlacklistAddress);
-            message.setRecipients(Message.RecipientType.TO, internetAddressList);// 设置收件人,并设置其接收类型为TO
-            message.setSubject(subject);// 设置标题
-            message.setContent(result, "text/html;charset=UTF-8"); // 设置邮件内容类型为html
-            message.setSentDate(new Date());// 设置发信时间
-            message.saveChanges();// 存储邮件信息
-
-            // 发送邮件
-            transport = session.getTransport("smtp");
-            if (null != port) {
-                transport.connect(host, port, user, password);
-            } else {
-                transport.connect(host, user, password);
-            }
-            transport.sendMessage(message, message.getAllRecipients());
-
-            //发送邮件成功，status置为true
-            sendStatus.setStatus("true");
-        } catch (Exception e) {
-            log.error("邮件发送异常", e);
-            sendStatus.setErrorMessage(e.getMessage());
-        } finally {
-            if (null != transport) {
-                try {
-                    transport.close();//关闭连接
-                } catch (MessagingException e) {
-                    log.error("发送邮件 ，transport 关闭异常", e);
-                }
-            }
-        }
+        doc.getElementById(SYS_NAME).html(SYS_NAME_AGENT);
+        doc.getElementById(AGENT_NAME).html(agentName);
+        doc.getElementById(MAIL_CONTENT).html(maiContent);
+        doc.getElementById(CLICK_HREF).attr("href", emailHref);
+        sendEmail(doc, sendStatus, notInBlacklistAddress, subject, EMAIL_SEND_EXCEPTION);
         return sendStatus;
     }
 
@@ -154,70 +125,24 @@ public SendStatus sendHtmlMail(List<String> toList, String username, String agen
             return sendStatus;
         }
         // 读取html模板
-        String html = readHtmlToString("mailTemplate.html");
+        String html = readHtmlToString(MAIL_TEMPLATE);
 
         // 写入模板内容
         Document doc = Jsoup.parse(html);
-        doc.getElementById("username").html(username);
-        doc.getElementById("agentName").html(agentName);
+        doc.getElementById(USER_NAME).html(username);
+        doc.getElementById(AGENT_NAME).html(agentName);
 
         if (SystemEnum.AGENT.equals(systemEnum)) {
-            doc.getElementById("sysName").html("Your Agent：");
+            doc.getElementById(SYS_NAME).html(SYS_NAME_AGENT);
         } else if (SystemEnum.DATAFLOW.equals(systemEnum) || SystemEnum.SYNC.equals(systemEnum) || SystemEnum.MIGRATION.equals(systemEnum)) {
-            doc.getElementById("sysName").html("Your Task：");
+            doc.getElementById(SYS_NAME).html(SYS_NAME_TASK);
         }
 
         String mailContent = getMailContent(systemEnum, msgTypeEnum);
-        doc.getElementById("mailContent").html(mailContent);
-        doc.getElementById("clickHref").attr("href", emailHref);
+        doc.getElementById(MAIL_CONTENT).html(mailContent);
+        doc.getElementById(CLICK_HREF).attr("href", emailHref);
 
-        String result = doc.toString();
-        Properties props = new Properties();
-        props.put("mail.smtp.host", "");
-        props.put("mail.smtp.auth", "true");
-        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
-        props.put("mail.smtp.socketFactory.fallback", "true");
-        Session session = Session.getDefaultInstance(props);
-        session.setDebug(true);
-
-        Transport transport = null;
-        MimeMessage message = new MimeMessage(session);
-        try {
-            //初始化发送邮件配置
-            this.initMailConfig();
-            message.setFrom(new InternetAddress(this.sendAddress));// 设置发件人的地址
-            InternetAddress[] internetAddressList = getInternetAddress(notInBlacklistAddress);
-            message.setRecipients(Message.RecipientType.TO, internetAddressList);// 设置收件人,并设置其接收类型为TO
-
-            String title = getMailTitle(systemEnum, msgTypeEnum);
-            message.setSubject(title);// 设置标题
-            message.setContent(result, "text/html;charset=UTF-8"); // 设置邮件内容类型为html
-            message.setSentDate(new Date());// 设置发信时间
-            message.saveChanges();// 存储邮件信息
-
-            // 发送邮件
-            transport = session.getTransport("smtp");
-            if (null != port) {
-                transport.connect(host, port, user, password);
-            } else {
-                transport.connect(host, user, password);
-            }
-            transport.sendMessage(message, message.getAllRecipients());
-
-            //发送邮件成功，status置为true
-            sendStatus.setStatus("true");
-        } catch (Exception e) {
-            log.error("邮件发送异常", e);
-            sendStatus.setErrorMessage(e.getMessage());
-        } finally {
-            if (null != transport) {
-                try {
-                    transport.close();//关闭连接
-                } catch (MessagingException e) {
-                    log.error("发送邮件 ，transport 关闭异常", e);
-                }
-            }
-        }
+        sendEmail(doc, sendStatus, notInBlacklistAddress, getMailTitle(systemEnum, msgTypeEnum), EMAIL_SEND_EXCEPTION);
         return sendStatus;
     }
 
@@ -232,73 +157,26 @@ public SendStatus sendHtmlMail(List<String> toList, String username, String agen
             return sendStatus;
         }
         // 读取html模板
-        String html = readHtmlToString("mailTemplate.html");
+        String html = readHtmlToString(MAIL_TEMPLATE);
 
         // 写入模板内容
         Document doc = Jsoup.parse(html);
-        doc.getElementById("username").html(username);
-        doc.getElementById("agentName").html(agentName);
+        doc.getElementById(USER_NAME).html(username);
+        doc.getElementById(AGENT_NAME).html(agentName);
 
         if (SystemEnum.AGENT.equals(systemEnum)) {
-            doc.getElementById("sysName").html("Your Agent：");
+            doc.getElementById(SYS_NAME).html(SYS_NAME_AGENT);
         } else if (SystemEnum.DATAFLOW.equals(systemEnum) || SystemEnum.SYNC.equals(systemEnum) || SystemEnum.MIGRATION.equals(systemEnum)) {
-            doc.getElementById("sysName").html("Your Task：");
+            doc.getElementById(SYS_NAME).html(SYS_NAME_TASK);
         }
 
         String mailContent = getMailContent(systemEnum, msgTypeEnum);
-        doc.getElementById("mailContent").html(mailContent);
+        doc.getElementById(MAIL_CONTENT).html(mailContent);
 
         String emailHref = getHrefClick(sourceId, systemEnum, msgTypeEnum);
 
-        doc.getElementById("clickHref").attr("href", emailHref);
-
-        String result = doc.toString();
-        Properties props = new Properties();
-        props.put("mail.smtp.host", "");
-        props.put("mail.smtp.auth", "true");
-        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
-        props.put("mail.smtp.socketFactory.fallback", "true");
-        Session session = Session.getDefaultInstance(props);
-        session.setDebug(true);
-
-        Transport transport = null;
-        MimeMessage message = new MimeMessage(session);
-        try {
-            //初始化发送邮件配置
-            this.initMailConfig();
-            message.setFrom(new InternetAddress(this.sendAddress));// 设置发件人的地址
-            InternetAddress[] internetAddressList = getInternetAddress(notInBlacklistAddress);
-            message.setRecipients(Message.RecipientType.TO, internetAddressList);// 设置收件人,并设置其接收类型为TO
-
-            String title = getMailTitle(systemEnum, msgTypeEnum);
-            message.setSubject(title);// 设置标题
-            message.setContent(result, "text/html;charset=UTF-8"); // 设置邮件内容类型为html
-            message.setSentDate(new Date());// 设置发信时间
-            message.saveChanges();// 存储邮件信息
-
-            // 发送邮件
-            transport = session.getTransport("smtp");
-            if (null != port) {
-                transport.connect(host, port, user, password);
-            } else {
-                transport.connect(host, user, password);
-            }
-            transport.sendMessage(message, message.getAllRecipients());
-
-            //发送邮件成功，status置为true
-            sendStatus.setStatus("true");
-        } catch (Exception e) {
-            log.error("邮件发送异常", e);
-            sendStatus.setErrorMessage(e.getMessage());
-        } finally {
-            if (null != transport) {
-                try {
-                    transport.close();//关闭连接
-                } catch (MessagingException e) {
-                    log.error("发送邮件 ，transport 关闭异常", e);
-                }
-            }
-        }
+        doc.getElementById(CLICK_HREF).attr("href", emailHref);
+        sendEmail(doc, sendStatus, notInBlacklistAddress, getMailTitle(systemEnum, msgTypeEnum), EMAIL_SEND_EXCEPTION);
         return sendStatus;
     }
 
@@ -437,20 +315,20 @@ public static String readHtmlToString(String htmlFileName) {
      * IMAP 端口号
      * 993
      */
-    private void initMailConfig() {
-        String host = String.valueOf(settingsService.getByCategoryAndKey("SMTP", "smtp.server.host"));
-        String port = String.valueOf(settingsService.getByCategoryAndKey("SMTP", "smtp.server.port"));
-        String username = String.valueOf(settingsService.getByCategoryAndKey("SMTP", "smtp.server.user"));
-        String sendAddress = String.valueOf(settingsService.getByCategoryAndKey("SMTP", "email.send.address"));
-        String password = String.valueOf(settingsService.getByCategoryAndKey("SMTP", "smtp.server.password"));
-
-        this.host = host;
-        if (StringUtils.isNotEmpty(port)) {
-            this.port = Integer.valueOf(port);
+    protected void initMailConfig() {
+        String hostFromDB = String.valueOf(settingsService.getByCategoryAndKey("SMTP", "smtp.server.host"));
+        String portFromDB = String.valueOf(settingsService.getByCategoryAndKey("SMTP", "smtp.server.port"));
+        String usernameFromDB = String.valueOf(settingsService.getByCategoryAndKey("SMTP", "smtp.server.user"));
+        String sendAddressFromDB = String.valueOf(settingsService.getByCategoryAndKey("SMTP", "email.send.address"));
+        String passwordFromDB = String.valueOf(settingsService.getByCategoryAndKey("SMTP", "smtp.server.password"));
+
+        this.host = hostFromDB;
+        if (StringUtils.isNotEmpty(portFromDB)) {
+            this.port = Integer.valueOf(portFromDB);
         }
-        this.sendAddress = sendAddress;
-        this.user = username;
-        this.password = password;
+        this.sendAddress = sendAddressFromDB;
+        this.user = usernameFromDB;
+        this.password = passwordFromDB;
 
     }
 
@@ -671,4 +549,81 @@ protected InternetAddress[] getInternetAddress(List<String> notInBlacklistAddres
         return addressList.toArray(new InternetAddress[notInBlacklistAddress.size()]);
     }
 
+    /**
+     * 发送html形式的邮件, 重置密码
+     */
+    public SendStatus sendValidateCodeForResetPWD(String to, String username, String validateCode) {
+        SendStatus sendStatus = new SendStatus(SEND_STATUS_FALSE, "");
+        String html = readHtmlToString("resetPasswordTemplate.html");
+        Document doc = Jsoup.parse(html);
+        doc.getElementById(USER_NAME).html(username);
+        doc.getElementById("code").html(validateCode);
+        doc.getElementById("account").html(to);
+        doc.getElementById("validateTimes").html("5");
+        sendEmail(doc, sendStatus, Lists.newArrayList(to), "修改密码-验证码", "Send validate code email failed before reset password");
+        return sendStatus;
+    }
+
+    protected Transport connectSMTP(Session session) throws MessagingException {
+        Transport transport = session.getTransport("smtp");
+        if (null != port) {
+            transport.connect(host, port, user, password);
+        } else {
+            transport.connect(host, user, password);
+        }
+        return transport;
+    }
+
+    protected MimeMessage message(Session session, List<String> internetAddress, String emailContent, String emailSubject) throws MessagingException, UnsupportedEncodingException {
+        MimeMessage message = new MimeMessage(session);
+        this.initMailConfig();
+        message.setFrom(new InternetAddress(this.sendAddress));
+        InternetAddress[] internetAddressList = getInternetAddress(internetAddress);
+        message.setRecipients(Message.RecipientType.TO, internetAddressList);
+
+        message.setContent(emailContent, "text/html;charset=UTF-8");
+        message.setSentDate(new Date());
+        message.saveChanges();
+        message.setSubject(emailSubject);
+        return message;
+    }
+
+    protected void closeTransport(Transport transport) {
+        if (null != transport) {
+            try {
+                transport.close();//关闭连接
+            } catch (MessagingException e) {
+                log.error("发送邮件 ，transport 关闭异常", e);
+            }
+        }
+    }
+
+    protected Session emailSession() {
+        Properties props = new Properties();
+        props.put("mail.smtp.host", "");
+        props.put("mail.smtp.auth", "true");
+        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+        props.put("mail.smtp.ssl.checkserveridentity", "true");
+        props.put("mail.smtp.socketFactory.fallback", "true");
+        Session session = Session.getDefaultInstance(props);
+        session.setDebug(true);
+        return session;
+    }
+
+    protected void sendEmail(Document doc, SendStatus sendStatus, List<String> list, String subject, String errorMessage) {
+        String result = doc.toString();
+        Session session = emailSession();
+        Transport transport = null;
+        try {
+            MimeMessage message = message(session, list, result, subject);
+            transport = connectSMTP(session);
+            transport.sendMessage(message, message.getAllRecipients());
+            sendStatus.setStatus("true");
+        } catch (Exception e) {
+            log.error(errorMessage, e);
+            sendStatus.setErrorMessage(e.getMessage());
+        } finally {
+            closeTransport(transport);
+        }
+    }
 }
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/utils/MetadataInstancesFilterUtil.java b/manager/tm-api/src/main/java/com/tapdata/tm/utils/MetadataInstancesFilterUtil.java
new file mode 100644
index 0000000000..34c92608b5
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/utils/MetadataInstancesFilterUtil.java
@@ -0,0 +1,75 @@
+package com.tapdata.tm.utils;
+
+import com.tapdata.manager.common.utils.StringUtils;
+import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
+import com.tapdata.tm.commons.schema.Field;
+import com.tapdata.tm.commons.schema.MetadataInstancesDto;
+import com.tapdata.tm.commons.util.NoPrimaryKeyTableSelectType;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class MetadataInstancesFilterUtil {
+
+    private MetadataInstancesFilterUtil() {
+        throw new IllegalStateException("Utility class");
+    }
+    public static List<String> getFilteredOriginalNames(List<MetadataInstancesDto> metaList, DatabaseNode sourceNode){
+        return metaList.stream()
+                .map(metadataInstancesDto -> getMetadataInstancesDtoOriginalName(sourceNode, metadataInstancesDto))
+                .filter(originalName -> isValidOriginalName(originalName, sourceNode))
+                .collect(Collectors.toList());
+
+    }
+    public static Long countFilteredOriginalNames(List<MetadataInstancesDto> metaInstances, DatabaseNode sourceNode) {
+        return metaInstances.stream()
+                .map(metadataInstancesDto -> getMetadataInstancesDtoOriginalName(sourceNode, metadataInstancesDto))
+                .filter(originalName -> isValidOriginalName(originalName, sourceNode))
+                .count();
+    }
+    private static String getMetadataInstancesDtoOriginalName(DatabaseNode sourceNode,MetadataInstancesDto metadataInstances){
+        Function<MetadataInstancesDto, Boolean> filterTableByNoPrimaryKey = Optional
+                .of(NoPrimaryKeyTableSelectType.parse(sourceNode.getNoPrimaryKeyTableSelectType()))
+                .map(type -> {
+                    switch (type) {
+                        case HasKeys:
+                            return (Function<MetadataInstancesDto, Boolean>) metadataInstancesDto -> {
+                                if (null != metadataInstancesDto.getFields()) {
+                                    for (Field field : metadataInstancesDto.getFields()) {
+                                        if (Boolean.TRUE.equals(field.getPrimaryKey())) return false;
+                                    }
+                                }
+                                return true;
+                            };
+                        case NoKeys:
+                            return (Function<MetadataInstancesDto, Boolean>) metadataInstancesDto -> {
+                                if (null != metadataInstancesDto.getFields()) {
+                                    for (Field field : metadataInstancesDto.getFields()) {
+                                        if (Boolean.TRUE.equals(field.getPrimaryKey())) return true;
+                                    }
+                                }
+                                return false;
+                            };
+                        default:
+                    }
+                    return null;
+                }).orElse(metadataInstancesDto -> false);
+        if (filterTableByNoPrimaryKey.apply(metadataInstances)) {
+            return null;
+        }
+        return metadataInstances.getOriginalName();
+    }
+
+    private static boolean isValidOriginalName(String originalName, DatabaseNode sourceNode) {
+        if (null == originalName) {
+            return false;
+        } else if (StringUtils.isEmpty(sourceNode.getTableExpression())) {
+            return false;
+        } else {
+            return Pattern.matches(sourceNode.getTableExpression(), originalName);
+        }
+    }
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/utils/QueryUtil.java b/manager/tm-api/src/main/java/com/tapdata/tm/utils/QueryUtil.java
index 019a9c69b3..1bb30d59b3 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/utils/QueryUtil.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/utils/QueryUtil.java
@@ -45,4 +45,20 @@ public static List<Sort> parseOrder(Filter filter) {
         }
         return sortList;
     }
+
+    public static Sort parseOrder(String order) {
+        if (null != order && !order.trim().isEmpty()) {
+            String[] splits = order.trim().split(" +");
+            Sort sort = Sort.by(splits[0].trim());
+            if (splits.length == 1) {
+                return sort.descending();
+            }
+
+            if (Sort.Direction.ASC.name().equalsIgnoreCase(splits[1].trim())) {
+                return sort.ascending();
+            }
+            return sort.descending();
+        }
+        return null;
+    }
 }
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/CdcDelay.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/CdcDelay.java
index 188ad084bf..fe408e8005 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/CdcDelay.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/CdcDelay.java
@@ -1,5 +1,6 @@
 package com.tapdata.tm.commons.cdcdelay;
 
+import com.tapdata.tm.commons.task.dto.TaskDto;
 import com.tapdata.tm.commons.util.ConnHeartbeatUtils;
 import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.control.HeartbeatEvent;
@@ -18,6 +19,7 @@
 import java.util.Map;
 import java.util.TimeZone;
 import java.util.function.Consumer;
+import java.util.function.LongConsumer;
 
 /**
  * @author <a href="mailto:harsen_lin@163.com">Harsen</a>
@@ -48,13 +50,13 @@ public boolean addHeartbeatTable(@NonNull List<String> tables) {
     }
 
     @Override
-    public TapEvent filterAndCalcDelay(TapEvent tapEvent, @NonNull Consumer<Long> delayConsumer) {
+    public TapEvent filterAndCalcDelay(TapEvent tapEvent, @NonNull LongConsumer delayConsumer, String syncType) {
         if (tapEvent instanceof TapRecordEvent) {
             Long sourceTimes;
             TapRecordEvent tapRecordEvent = ((TapRecordEvent) tapEvent);
             if (ConnHeartbeatUtils.TABLE_NAME.equals(tapRecordEvent.getTableId())) {
                 sourceTimes = parseTs(tapRecordEvent);
-                if (isFilter) {
+                if (isFilter && !TaskDto.SYNC_TYPE_LOG_COLLECTOR.equals(syncType)) {
                     HeartbeatEvent heartbeatEvent = new HeartbeatEvent();
                     tapEvent.clone(heartbeatEvent);
                     heartbeatEvent.setReferenceTime(tapRecordEvent.getReferenceTime());
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/CdcDelayDisable.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/CdcDelayDisable.java
index 1bd6f9cc28..12c5cb2a29 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/CdcDelayDisable.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/CdcDelayDisable.java
@@ -4,7 +4,7 @@
 import org.springframework.lang.NonNull;
 
 import java.util.List;
-import java.util.function.Consumer;
+import java.util.function.LongConsumer;
 
 /**
  * @author <a href="mailto:harsen_lin@163.com">Harsen</a>
@@ -17,7 +17,7 @@ public boolean addHeartbeatTable(@NonNull List<String> tables) {
     }
 
     @Override
-    public TapEvent filterAndCalcDelay(TapEvent tapEvent, @NonNull Consumer<Long> delayConsumer) {
+    public TapEvent filterAndCalcDelay(TapEvent tapEvent, LongConsumer delayConsumer, String syncType) {
         return tapEvent;
     }
 }
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/ICdcDelay.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/ICdcDelay.java
index 3f0c6b4262..e6f92bc02c 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/ICdcDelay.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/ICdcDelay.java
@@ -5,6 +5,7 @@
 
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.function.LongConsumer;
 
 /**
  * @author <a href="mailto:harsen_lin@163.com">Harsen</a>
@@ -24,5 +25,5 @@ public interface ICdcDelay {
      * @param tapEvent TapEvent
      * @return TapEvent
      */
-    TapEvent filterAndCalcDelay(TapEvent tapEvent, @NonNull Consumer<Long> delayConsumer);
+    TapEvent filterAndCalcDelay(TapEvent tapEvent, @NonNull LongConsumer delayConsumer, String taskSyncType);
 }
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/DAGDataServiceImpl.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/DAGDataServiceImpl.java
index 56a77670f9..c4e80abc60 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/DAGDataServiceImpl.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/DAGDataServiceImpl.java
@@ -1199,48 +1199,10 @@ public MetadataInstancesDto getSchemaByNodeAndTableName(String nodeId, String ta
         Node<?> node = dag.getNode(nodeId);
         String qualifiedName = null;
         if (node instanceof DatabaseNode) {
-//            List<Node> sources = node.getDag().getSources();
-//            Node source = sources.get(0);
-//            LinkedList<TableRenameProcessNode> linkedList = new LinkedList<>();
-//            while (!source.getId().equals(node.getId())) {
-//                if (source instanceof TableRenameProcessNode) {
-//                    linkedList.add((TableRenameProcessNode) source);
-//                }
-//                List successors = source.successors();
-//                if (CollectionUtils.isEmpty(successors)) {
-//                    break;
-//                }
-//                source = (Node) successors.get(0);
-//            }
-//            TableRenameTableInfo tableInfo = null;
-//            if (CollectionUtils.isNotEmpty(linkedList)) {
-//                for (TableRenameProcessNode node1 : linkedList) {
-//                    Map<String, TableRenameTableInfo> tableRenameTableInfoMap = node1.originalMap();
-//                    TableRenameTableInfo tableInfo1 = tableRenameTableInfoMap.get(tableName);
-//                    if (tableInfo1 != null) {
-//                        tableInfo = tableInfo1;
-//                    }
-//                }
-//            }
-//
-//            if (tableInfo != null) {
-//                tableName = tableInfo.getCurrentTableName();
-//            }
             String connectionId = ((DatabaseNode) node).getConnectionId();
             DataSourceConnectionDto connectionDto = dataSourceMap.get(connectionId);
             qualifiedName = MetaDataBuilderUtils.generateQualifiedName(MetaType.table.name(), connectionDto, tableName, taskId);
         } else if (node instanceof ProcessorNode || node instanceof MigrateProcessorNode) {
-//            if (node instanceof TableRenameProcessNode) {
-//                LinkedHashSet<TableRenameTableInfo> tableNames = ((TableRenameProcessNode) node).getTableNames();
-//                if (CollectionUtils.isNotEmpty(tableNames)) {
-//                    for (TableRenameTableInfo name : tableNames) {
-//                        if (name.getOriginTableName().equals(tableName)) {
-//                            tableName = name.getCurrentTableName();
-//                            break;
-//                        }
-//                    }
-//                }
-//            }
             qualifiedName = MetaDataBuilderUtils.generateQualifiedName(MetaType.processor_node.name(), nodeId, tableName);
         }
 
@@ -1253,7 +1215,7 @@ public MetadataInstancesDto getSchemaByNodeAndTableName(String nodeId, String ta
                 return metadataInstancesDto;
             }
         }
-        return null;
+        return getMetadata(qualifiedName);
     }
 
 
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/NodeEnum.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/NodeEnum.java
index e1459bf581..02e7751ecb 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/NodeEnum.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/NodeEnum.java
@@ -35,6 +35,7 @@ public enum NodeEnum {
     migrate_python_processor("Python节点"),
     standard_migrate_js_processor("标准JS节点"),
     union_processor("Union节点"),
+    migrate_union_processor("数据复制Union节点"),
     migrate_field_mod_type_filter_processor("类型过滤节点"),
     unwind_processor("Unwind节点"),
     ADD_DATE_FIELD_PROCESSOR("增加时间字段节点"),
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/DatabaseNode.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/DatabaseNode.java
index 6c16cd94f4..6899f2289d 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/DatabaseNode.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/DatabaseNode.java
@@ -5,9 +5,7 @@
 import com.tapdata.tm.commons.dag.Node;
 import com.tapdata.tm.commons.dag.NodeType;
 import com.tapdata.tm.commons.dag.SchemaTransformerResult;
-import com.tapdata.tm.commons.dag.process.FieldProcessorNode;
-import com.tapdata.tm.commons.dag.process.MigrateFieldRenameProcessorNode;
-import com.tapdata.tm.commons.dag.process.TableRenameProcessNode;
+import com.tapdata.tm.commons.dag.process.*;
 import com.tapdata.tm.commons.dag.vo.BatchTypeOperation;
 import com.tapdata.tm.commons.dag.vo.FieldProcess;
 import com.tapdata.tm.commons.dag.vo.SyncObjects;
@@ -277,6 +275,13 @@ protected List<Schema> loadSchema(List<String> includes) {
                 LinkedList<TableRenameProcessNode> collect = preNodes.stream().filter(node -> node instanceof TableRenameProcessNode)
                         .map(node -> (TableRenameProcessNode) node)
                         .collect(Collectors.toCollection(LinkedList::new));
+
+                LinkedList<MigrateUnionProcessorNode> migrateUnionProcessorNodes = preNodes.stream().filter(node -> node instanceof MigrateUnionProcessorNode)
+                        .map(node -> (MigrateUnionProcessorNode) node)
+                        .collect(Collectors.toCollection(LinkedList::new));
+                if(CollectionUtils.isNotEmpty(migrateUnionProcessorNodes)){
+                    return Collections.emptyList();
+                }
                 if (CollectionUtils.isNotEmpty(collect)) {
                     Map<String, TableRenameTableInfo> namesMapping = collect.getLast().originalMap();
                     if (!namesMapping.isEmpty()) {
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/MigrateUnionProcessorNode.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/MigrateUnionProcessorNode.java
new file mode 100644
index 0000000000..74dc463dd1
--- /dev/null
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/MigrateUnionProcessorNode.java
@@ -0,0 +1,38 @@
+package com.tapdata.tm.commons.dag.process;
+
+import com.google.common.collect.Lists;
+import com.tapdata.tm.commons.dag.DAG;
+import com.tapdata.tm.commons.dag.NodeEnum;
+import com.tapdata.tm.commons.dag.NodeType;
+import com.tapdata.tm.commons.schema.Schema;
+import com.tapdata.tm.commons.schema.SchemaUtils;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.collections4.CollectionUtils;
+
+import java.util.*;
+@NodeType("migrate_union_processor")
+@Getter
+@Setter
+public class MigrateUnionProcessorNode extends MigrateProcessorNode{
+    private String tableName;
+
+    public MigrateUnionProcessorNode() {
+        super(NodeEnum.migrate_union_processor.name(), NodeCatalog.processor);
+    }
+
+    @Override
+    public List<Schema> mergeSchema(List<List<Schema>> inputSchemas, List<Schema> schemas, DAG.Options options) {
+        if (CollectionUtils.isEmpty(inputSchemas)) {
+            return Lists.newArrayList();
+        }
+        List<Schema> outputSchemas = inputSchemas.get(0);
+        Schema schema = SchemaUtils.mergeSchema(outputSchemas, null);
+        schema.setName(tableName);
+        schema.setOriginalName(tableName);
+        schema.setAncestorsName(tableName);
+        outputSchemas.clear();
+        outputSchemas.add(schema);
+        return outputSchemas;
+    }
+}
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/DataSourceConnectionDto.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/DataSourceConnectionDto.java
index a476482ce6..195fa09e12 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/DataSourceConnectionDto.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/DataSourceConnectionDto.java
@@ -271,6 +271,8 @@ public class DataSourceConnectionDto extends BaseDto implements IDataPermissionD
 
         private String accessNodeProcessId;
 
+        private String priorityProcessId;
+
         private List<String> accessNodeProcessIdList;
 
         private boolean accessNodeTypeEmpty;
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/TransformerWsMessageDto.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/TransformerWsMessageDto.java
index a9ecfecca2..193662bb15 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/TransformerWsMessageDto.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/TransformerWsMessageDto.java
@@ -23,4 +23,5 @@ public class TransformerWsMessageDto {
     private String userName;
     private Map<String, MetadataTransformerDto> transformerDtoMap;
     private String type;
+    private Map<String, List<String>> tableNodeSameMetadataInstances;
 }
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/ParentTaskDto.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/ParentTaskDto.java
index e8e6e42a25..4f6a8db8d6 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/ParentTaskDto.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/ParentTaskDto.java
@@ -215,6 +215,8 @@ public class ParentTaskDto extends SchedulableDto {
      * */
     private String accessNodeProcessId;
 
+    private String priorityProcessId;
+
     private HashSet<String> heartbeatTasks;
 
 
diff --git a/manager/tm-oss/src/main/java/com/tapdata/tm/roleMapping/service/RoleMappingServiceImpl.java b/manager/tm-oss/src/main/java/com/tapdata/tm/roleMapping/service/RoleMappingServiceImpl.java
index 7e9605ae9b..39efa9c57f 100644
--- a/manager/tm-oss/src/main/java/com/tapdata/tm/roleMapping/service/RoleMappingServiceImpl.java
+++ b/manager/tm-oss/src/main/java/com/tapdata/tm/roleMapping/service/RoleMappingServiceImpl.java
@@ -57,4 +57,9 @@ public List<RoleMappingDto> getByRoleIdsAndPrincipleType(PrincipleType principle
     public List<RoleMappingDto> saveAll(List<RoleMappingDto> roleDtos, UserDetail userDetail) {
         throw new BizException("TapOssNonSupportFunctionException");
     }
+
+    @Override
+    public void removeRoleFromUser(String roleMappingId) {
+        //do Nothing
+    }
 }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImpl.java
index e9fde0723f..c488b50b77 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImpl.java
@@ -1269,9 +1269,13 @@ public void sendTestConnection(DataSourceConnectionDto connectionDto, boolean up
         }
 
         List<Worker> availableAgent;
-        if (StringUtils.isBlank(connectionDto.getAccessNodeType())
+        if (StringUtils.isNotEmpty(connectionDto.getAccessNodeType())
                 && AccessNodeTypeEnum.isManually(connectionDto.getAccessNodeType())) {
             availableAgent = workerService.findAvailableAgentByAccessNode(user, agentGroupService.getProcessNodeListWithGroup(connectionDto, user));
+            List<String> processIds = availableAgent.stream().map(Worker::getProcessId).collect(Collectors.toList());
+            if(StringUtils.isNotEmpty(connectionDto.getPriorityProcessId()) && processIds.contains(connectionDto.getPriorityProcessId())){
+                availableAgent = availableAgent.stream().filter(worker -> worker.getProcessId().equals(connectionDto.getPriorityProcessId())).collect(Collectors.toList());
+            }
         } else {
             availableAgent = workerService.findAvailableAgent(user);
         }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectDetailsController.java b/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectDetailsController.java
index bc988e5cd7..0dd42ca93a 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectDetailsController.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectDetailsController.java
@@ -2,8 +2,10 @@
 
 import com.tapdata.tm.base.controller.BaseController;
 import com.tapdata.tm.base.dto.*;
+import com.tapdata.tm.base.exception.BizException;
 import com.tapdata.tm.inspect.dto.InspectDetailsDto;
 import com.tapdata.tm.inspect.service.InspectDetailsService;
+import com.tapdata.tm.inspect.service.InspectResultService;
 import com.tapdata.tm.utils.MongoUtils;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -13,8 +15,14 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 
 /**
@@ -30,6 +38,9 @@ public class InspectDetailsController extends BaseController {
     @Autowired
     private InspectDetailsService inspectDetailsService;
 
+    @Autowired
+    private InspectResultService inspectResultService;
+
     /**
      * Create a new instance of the model and persist it into the data source
      * @param inspectDetails
@@ -207,4 +218,33 @@ public ResponseMessage<InspectDetailsDto> upsertByWhere(@RequestParam("where") S
         return success(inspectDetailsService.upsertByWhere(where, inspectDetails, getLoginUser()));
     }
 
+
+    /**
+     * Export different data from the inspectDetail.
+     *
+     * @return
+     */
+    @Operation(summary = "Export different data from the inspectDetail.")
+    @PostMapping("export")
+    public void export(@RequestBody InspectDetailsDto inspectDetails,
+                       HttpServletResponse response) throws IOException {
+        String date = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
+        String filename = inspectDetails.getInspectResultId() + "-" + date;
+
+        response.setContentType("application/zip");
+        response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + ".zip\"");
+        ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream());
+        zipOutputStream.putNextEntry(new ZipEntry(filename + ".json"));
+        try {
+            inspectDetailsService.export(inspectDetails, zipOutputStream, getLoginUser(), inspectResultService);
+        } catch (Exception e) {
+            log.error("Export inspectDetails failed", e);
+            throw new BizException("export.inspectDetails.failed",e.getMessage());
+        } finally {
+            zipOutputStream.closeEntry();
+            zipOutputStream.flush();
+            zipOutputStream.close();
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectResultController.java b/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectResultController.java
index 79988d043e..1c3ee76918 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectResultController.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectResultController.java
@@ -3,6 +3,7 @@
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import com.alibaba.fastjson.JSON;
+import com.google.common.collect.Lists;
 import com.tapdata.tm.base.controller.BaseController;
 import com.tapdata.tm.base.dto.Filter;
 import com.tapdata.tm.base.dto.Page;
@@ -25,7 +26,6 @@
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
-import org.bson.types.ObjectId;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.data.mongodb.core.query.Query;
@@ -40,8 +40,12 @@
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.StringJoiner;
 
@@ -129,18 +133,47 @@ public ResponseMessage<Page<InspectResultDto>> find(
                 inspectGroupByFirstCheckId = filter.getInspectGroupByFirstCheckId();
             }
         }
-        String inspectId = String.valueOf(filter.getWhere().get("inspect_id"));
-        if (null == MongoUtils.toObjectId(inspectId)) {
-            InspectResultDto one = inspectResultService.findOne(Query.query(Criteria.where("_id").is(MongoUtils.toObjectId(String.valueOf(filter.getWhere().get("id"))))));
-            if (null == one || null == one.getInspect_id()) {
-                throw new BizException("inspect.result.not.exists", filter.getWhere().get("id"));
+        Object inspectIdObject = filter.getWhere().get("inspect_id");
+        if (inspectIdObject instanceof Map) {
+            List<String> idList = filterViewPermissionNotInspectId(filter.getWhere());
+            if (idList.isEmpty()) return success(new Page<>(0, Lists.newArrayList()));
+            Where w = filter.getWhere();
+            Map<String, Object> in = new HashMap<>();
+            w.put("inspect_id", in);
+            in.put("$in", idList);
+            filter.setWhere(w);
+        } else {
+            String inspectId = String.valueOf(inspectIdObject);
+            if (null == MongoUtils.toObjectId(inspectId)) {
+                InspectResultDto one = inspectResultService.findOne(Query.query(Criteria.where("_id").is(MongoUtils.toObjectId(String.valueOf(filter.getWhere().get("id"))))));
+                if (null == one || null == one.getInspect_id()) {
+                    throw new BizException("inspect.result.not.exists", filter.getWhere().get("id"));
+                }
+                inspectId = one.getInspect_id();
             }
-            inspectId = one.getInspect_id();
+            checkInspect(inspectId, DataPermissionActionEnums.View);
         }
-        checkInspect(inspectId, DataPermissionActionEnums.View);
         return success(inspectResultService.find(filter, getLoginUser(), inspectGroupByFirstCheckId));
     }
 
+    protected List<String> filterViewPermissionNotInspectId(Where where) {
+        List<InspectResultDto> all = inspectResultService.findAll(Where.where("inspect_id", where.get("inspect_id")));
+        if (null == all) return new ArrayList<>();
+        Set<String> ids = new HashSet<>();
+        all.stream().filter(Objects::nonNull).forEach(dto -> {
+            try {
+                String inspectId = dto.getInspect_id();
+                //过滤掉没有权限校验任务
+                checkInspect(inspectId, DataPermissionActionEnums.View);
+                ids.add(inspectId);
+            } catch (Exception e) {
+                log.warn(e.getMessage());
+            }
+        });
+        return new ArrayList<>(ids);
+    }
+
+
     /**
      *  Replace an existing model instance or insert a new one into the data source
      * @param inspectResult
diff --git a/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/controller/ApiAppController.java b/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/controller/ApiAppController.java
index c452758e9a..f0732b362e 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/controller/ApiAppController.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/controller/ApiAppController.java
@@ -14,6 +14,9 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.ArrayList;
+import java.util.List;
+
 
 /**
  * @Date: 2021/10/15
@@ -40,9 +43,10 @@ public class ApiAppController extends BaseController {
     @PostMapping
     public ResponseMessage<MetadataDefinitionDto> save(@RequestBody MetadataDefinitionDto metadataDefinition) {
         metadataDefinition.setId(null);
+        List<String> itemType = new ArrayList<>();
+        itemType.add(MetadataDefinitionDto.ITEM_TYPE_APP);
+        metadataDefinition.setItemType(itemType);
         metadataDefinitionService.findByItemtypeAndValue(metadataDefinition,getLoginUser());
-
-
         return success(apiAppService.save(metadataDefinition, getLoginUser()));
     }
 
diff --git a/manager/tm/src/main/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImpl.java
index c68bd1e875..ef89bd7496 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImpl.java
@@ -1,7 +1,6 @@
 package com.tapdata.tm.metadatainstance.service;
 
 import cn.hutool.core.bean.BeanUtil;
-import com.alibaba.fastjson.JSON;
 import com.google.common.collect.ImmutableMap;
 import com.mongodb.BasicDBObject;
 import com.mongodb.bulk.BulkWriteResult;
@@ -11,7 +10,6 @@
 import com.tapdata.tm.base.dto.Page;
 import com.tapdata.tm.base.dto.Where;
 import com.tapdata.tm.base.exception.BizException;
-import com.tapdata.tm.base.service.BaseService;
 import com.tapdata.tm.commons.dag.DAG;
 import com.tapdata.tm.commons.dag.Node;
 import com.tapdata.tm.commons.dag.logCollector.LogCollecotrConnConfig;
@@ -54,7 +52,6 @@
 import com.tapdata.tm.utils.MetadataUtil;
 import com.tapdata.tm.utils.MongoUtils;
 import com.tapdata.tm.utils.SchemaTransformUtils;
-import io.tapdata.entity.conversion.PossibleDataTypes;
 import io.tapdata.entity.mapping.DefaultExpressionMatchingMap;
 import io.tapdata.entity.mapping.TypeExprResult;
 import io.tapdata.entity.mapping.type.TapStringMapping;
@@ -108,6 +105,30 @@ public class MetadataInstancesServiceImpl extends MetadataInstancesService{
     private DAGService dagService;
     private MetaDataHistoryService metaDataHistoryService;
     private MongoTemplate mongoTemplate;
+    public static final String IS_DELETED = "is_deleted";
+    public static final String IS_DELETE = "is_delete";
+    public static final String SOURCE_ID = "source._id";
+    public static final String SOURCE_ID_WITHOUT_UNDERLINE = "source.id";
+    public static final String TASK_ID = "taskId";
+    public static final String META_TYPE = "meta_type";
+    public static final String QUALIFIED_NAME = "qualified_name";
+    public static final String ORIGINAL_NAME = "original_name";
+    public static final String LOWER_CAME_ORIGINAL_NAME = "originalName";
+    public static final String SOURCE_TYPE = "sourceType";
+    public static final String NODE_ID = "nodeId";
+    public static final String COMMENT = "comment";
+    public static final String MONGODB = "mongodb";
+    public static final String METADATA_INSTANCES = "MetadataInstances";
+    public static final String FIELDS = "fields";
+    public static final String TABLE = "table";
+    public static final String COLLECTION = "collection";
+    public static final String HISTORIES = "histories";
+    public static final String TRANSFORM_UUID = "transformUuid";
+    public static final String TABLE_NAME = "tableName";
+    public static final String DAG_NODES_ID = "dag.nodes.id";
+    public static final String TABLE_ID = "tableId";
+    public static final String ILLEGAL_ARGUMENT = "IllegalArgument";
+    public static final String TABLE_COMMENT = "tableComment";
 
     public MetadataInstancesDto add(MetadataInstancesDto record, UserDetail user) {
         return save(record, user);
@@ -146,13 +167,13 @@ protected void beforeSave(MetadataInstancesDto record, UserDetail user) {
     public Page<MetadataInstancesDto> list(Filter filter, UserDetail user) {
         Where where = filter.getWhere();
         //TODO 这个id 后续需要处理一下
-        Object sourceId = where.get("source.id");
-        where.remove("source.id");
+        Object sourceId = where.get(SOURCE_ID_WITHOUT_UNDERLINE);
+        where.remove(SOURCE_ID_WITHOUT_UNDERLINE);
 
         if (sourceId != null) {
-            where.put("source._id", sourceId);
+            where.put(SOURCE_ID, sourceId);
         }
-        where.put("is_deleted", ImmutableMap.of("$ne", true));
+        where.put(IS_DELETED, ImmutableMap.of("$ne", true));
 
         if (null != where.get("classifications.id")) {
             Map<String, List> classficitionIn = (Map) where.get("classifications.id");
@@ -165,10 +186,10 @@ public Page<MetadataInstancesDto> list(Filter filter, UserDetail user) {
         }
 
         Page<MetadataInstancesDto> page = null;
-        if (filter.getWhere().containsKey("taskId")) {
-            Object taskId = filter.getWhere().remove("taskId");
+        if (filter.getWhere().containsKey(TASK_ID)) {
+            Object taskId = filter.getWhere().remove(TASK_ID);
             Criteria criteria = repository.whereToCriteria(filter.getWhere());
-            criteria.orOperator(Criteria.where("taskId").is(taskId), Criteria.where("taskId").exists(false));
+            criteria.orOperator(Criteria.where(TASK_ID).is(taskId), Criteria.where(TASK_ID).exists(false));
             // maybe model deduction slow then task model not save, could query physics table meta
 
             Query query = new Query(criteria);
@@ -180,7 +201,7 @@ public Page<MetadataInstancesDto> list(Filter filter, UserDetail user) {
                 query.limit(20);
             }
             query.skip(Math.max(filter.getSkip(), 0));
-            query.fields().include("comment");
+            query.fields().include(COMMENT);
             applyField(query, filter.getFields());
             applySort(query, filter.getSort());
             List<MetadataInstancesDto> allDto = findAll(query);
@@ -205,11 +226,11 @@ public Page<MetadataInstancesDto> list(Filter filter, UserDetail user) {
      */
     public List<MetadataInstancesVo> findInspect(Filter filter, UserDetail userDetail) {
         Where where = filter.getWhere();
-        Object sourceId = where.get("source.id");
+        Object sourceId = where.get(SOURCE_ID_WITHOUT_UNDERLINE);
         if (sourceId != null) {
-            where.remove("source.id");
+            where.remove(SOURCE_ID_WITHOUT_UNDERLINE);
 
-            where.put("source._id", sourceId);
+            where.put(SOURCE_ID, sourceId);
         }
         List<MetadataInstancesDto> metadataInstancesDtoList = super.findAll(filter);
         List<MetadataInstancesVo> metadataInstancesVoList = new ArrayList<>();
@@ -235,14 +256,14 @@ public MetadataInstancesDto queryById(ObjectId id, com.tapdata.tm.base.dto.Field
 
     public MetadataInstancesDto queryByOne(Filter filter, UserDetail user) {
         Where where = filter.getWhere();
-        Object sourceId = where.get("source.id");
+        Object sourceId = where.get(SOURCE_ID_WITHOUT_UNDERLINE);
 
         if (sourceId != null) {
-            where.remove("source.id");
+            where.remove(SOURCE_ID_WITHOUT_UNDERLINE);
 
-            where.put("source._id", sourceId);
+            where.put(SOURCE_ID, sourceId);
         }
-        where.put("is_deleted", false);
+        where.put(IS_DELETED, false);
         MetadataInstancesDto metadata = findOne(filter, user);
         afterFindOne(metadata, user);
         afterFind(metadata);
@@ -263,15 +284,15 @@ public List<MetadataInstancesDto> jobStats(long skip, int limit) {
                 foreignField("_id").
                 as("targetConnection");
 
-        Criteria jobCriteria = Criteria.where("meta_type").is("job");
-        Criteria targetCriteria = Criteria.where("targetConnection.database_type").is("mongodb");
+        Criteria jobCriteria = Criteria.where(META_TYPE).is("job");
+        Criteria targetCriteria = Criteria.where("targetConnection.database_type").is(MONGODB);
         AggregationOperation match = Aggregation.match(jobCriteria);
         AggregationOperation match1 = Aggregation.match(targetCriteria);
         ProjectionOperation project = Aggregation.project("listtags", "source.name", "source.stats");
         LimitOperation limitOperation = Aggregation.limit(limit);
         SkipOperation skipOperation = Aggregation.skip(skip);
         Aggregation aggregation = Aggregation.newAggregation(match, lookUp, match1, project, skipOperation, limitOperation);
-        List<MetadataInstancesEntity> metadataInstances = repository.getMongoOperations().aggregate(aggregation, "MetadataInstances", MetadataInstancesEntity.class).getMappedResults();
+        List<MetadataInstancesEntity> metadataInstances = repository.getMongoOperations().aggregate(aggregation, METADATA_INSTANCES, MetadataInstancesEntity.class).getMappedResults();
 
         List<MetadataInstancesDto> metadataInstancesDtos = convertToDto(metadataInstances, MetadataInstancesDto.class);
         return metadataInstancesDtos;
@@ -297,23 +318,23 @@ public List<MetadataInstancesDto> schema(Filter filter, UserDetail user) {
 
     public List<MetadataInstancesDto> lienage(String id) {
 
-        Criteria criteria = Criteria.where("is_deleted").exists(false);
-        Criteria criteria1 = Criteria.where("is_deleted").is(false);
+        Criteria criteria = Criteria.where(IS_DELETED).exists(false);
+        Criteria criteria1 = Criteria.where(IS_DELETED).is(false);
         Criteria deleteCriteria = new Criteria().orOperator(criteria, criteria1);
 
         GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder().
-                from("MetadataInstances").
+                from(METADATA_INSTANCES).
                 startWith("$lienage.qualified_name").
                 connectFrom("lienage.qualified_name").
-                connectTo("qualified_name").
+                connectTo(QUALIFIED_NAME).
                 restrict(deleteCriteria).
                 as("tree");
 
 
-        ProjectionOperation project = Aggregation.project("_id", "qualified_name", "meta_type",
-                "lienage", "name", "tree", "fields", "fields_lienage", "source", "original_name", "table_lienage");
+        ProjectionOperation project = Aggregation.project("_id", QUALIFIED_NAME, META_TYPE,
+                "lienage", "name", "tree", FIELDS, "fields_lienage", "source", ORIGINAL_NAME, "table_lienage");
         Aggregation aggregation = Aggregation.newAggregation(graphLookupOperation, project);
-        List<MetadataInstancesEntity> metadataInstances = repository.getMongoOperations().aggregate(aggregation, "MetadataInstances", MetadataInstancesEntity.class).getMappedResults();
+        List<MetadataInstancesEntity> metadataInstances = repository.getMongoOperations().aggregate(aggregation, METADATA_INSTANCES, MetadataInstancesEntity.class).getMappedResults();
 
         List<MetadataInstancesDto> metadataInstancesDtos = convertToDto(metadataInstances, MetadataInstancesDto.class);
         return metadataInstancesDtos;
@@ -328,7 +349,7 @@ public void beforeCreateOrUpdate(MetadataInstancesDto data, UserDetail user) {
             connectionId = connectionId.replace("CONN_", "");
         } else {
             com.tapdata.tm.base.dto.Field map = new com.tapdata.tm.base.dto.Field();
-            map.put("source._id", true);
+            map.put(SOURCE_ID, true);
             if (data.getId() != null) {
                 MetadataInstancesDto old = findById(data.getId(), map);
                 if (old != null) {
@@ -345,9 +366,9 @@ public void beforeCreateOrUpdate(MetadataInstancesDto data, UserDetail user) {
             if (StringUtils.isBlank(data.getConnectionId())) {
                 return;
             }
-            Criteria criteria = Criteria.where("source._id").is(connectionId);
-            Criteria criteria1 = Criteria.where("source.id").is(toObjectId(connectionId));
-            Criteria or = Criteria.where("meta_type").in(MetaType.database.name(), MetaType.directory.name(), MetaType.ftp.name())
+            Criteria criteria = Criteria.where(SOURCE_ID).is(connectionId);
+            Criteria criteria1 = Criteria.where(SOURCE_ID_WITHOUT_UNDERLINE).is(toObjectId(connectionId));
+            Criteria or = Criteria.where(META_TYPE).in(MetaType.database.name(), MetaType.directory.name(), MetaType.ftp.name())
                     .orOperator(criteria, criteria1);
             MetadataInstancesDto mObj = findOne(new Query(or));
             if (connectionDto != null) {
@@ -437,9 +458,9 @@ public void afterFindOne(MetadataInstancesDto result, UserDetail user) {
             inMetaTypes.add(MetaType.view.name());
             inMetaTypes.add(MetaType.table.name());
             inMetaTypes.add(MetaType.mongo_view.name());
-            Criteria criteria = Criteria.where("databaseId").is(result.getId().toHexString()).and("meta_type").in(inMetaTypes).and("is_delete").is(false);
+            Criteria criteria = Criteria.where("databaseId").is(result.getId().toHexString()).and(META_TYPE).in(inMetaTypes).and(IS_DELETE).is(false);
             Query query = new Query(criteria);
-            query.fields().include("id", "original_name");
+            query.fields().include("id", ORIGINAL_NAME);
             List<MetadataInstancesDto> collections = findAllDto(query, user);
             result.setCollections(collections);
         } else if (MetaType.collection.name().equals(result.getMetaType()) || MetaType.table.name().equals(result.getMetaType())
@@ -449,9 +470,9 @@ public void afterFindOne(MetadataInstancesDto result, UserDetail user) {
             inMetaTypes.add(MetaType.database.name());
             inMetaTypes.add(MetaType.directory.name());
             inMetaTypes.add(MetaType.ftp.name());
-            Criteria criteria = Criteria.where("_id").is(result.getDatabaseId()).and("meta_type").in(inMetaTypes).and("is_delete").is(false);
+            Criteria criteria = Criteria.where("_id").is(result.getDatabaseId()).and(META_TYPE).in(inMetaTypes).and(IS_DELETE).is(false);
             Query query = new Query(criteria);
-            query.fields().include("original_name");
+            query.fields().include(ORIGINAL_NAME);
             List<MetadataInstancesDto> collections = findAllDto(query, user);
             if (CollectionUtils.isNotEmpty(collections)) {
                 result.setDatabase(collections.get(0).getOriginalName());
@@ -479,9 +500,9 @@ public void afterFindAll(List<MetadataInstancesDto> results) {
         inMetaTypes.add(MetaType.database.name());
         inMetaTypes.add(MetaType.directory.name());
         inMetaTypes.add(MetaType.ftp.name());
-        Criteria criteria = Criteria.where("_id").in(databaseIds).and("meta_type").in(inMetaTypes).and("is_delete").is(false);
+        Criteria criteria = Criteria.where("_id").in(databaseIds).and(META_TYPE).in(inMetaTypes).and(IS_DELETE).is(false);
         Query query = new Query(criteria);
-        query.fields().include("id", "original_name");
+        query.fields().include("id", ORIGINAL_NAME);
         List<MetadataInstancesDto> collections = findAll(query);
         Map<String, String> databaseNameMap = collections.stream().collect(Collectors.toMap(d -> d.getId().toHexString(), MetadataInstancesDto::getOriginalName));
 
@@ -572,7 +593,7 @@ public Map<String, Object> classifications(List<ClassificationParam> classificat
         for (ClassificationParam classificationParam : classificationParamList) {
             Query query = new Query(Criteria.where("_id").is(classificationParam.getId()));
             Update update = Update.update("classifications", classificationParam.getClassifications());
-            UpdateResult updateResult = repository.getMongoOperations().upsert(query, update, "MetadataInstances");
+            UpdateResult updateResult = repository.getMongoOperations().upsert(query, update, METADATA_INSTANCES);
             if (updateResult != null && updateResult.getModifiedCount() > 0) {
                 rows += updateResult.getModifiedCount();
 
@@ -649,9 +670,9 @@ public MetadataUtil.CompareResult compareHistory(ObjectId id, int historyVersion
 
 
     public List<MetadataInstancesDto> tableConnection(String name, UserDetail user) {
-        Criteria criteria = Criteria.where("original_name").regex(name, "i")
-                .and("meta_type").in(MetaType.table.name(), MetaType.collection.name(), MetaType.view.name())
-                .and("is_deleted").is(false);
+        Criteria criteria = Criteria.where(ORIGINAL_NAME).regex(name, "i")
+                .and(META_TYPE).in(MetaType.table.name(), MetaType.collection.name(), MetaType.view.name())
+                .and(IS_DELETED).is(false);
 
 
         List<MetadataInstancesDto> metaArr = findAllDto(new Query(criteria), user);
@@ -665,11 +686,11 @@ public List<MetadataInstancesDto> tableConnection(String name, UserDetail user)
                 }
             }
 
-            Criteria criteria1 = Criteria.where("source.id").in(connId)
-                    .and("meta_type").is(MetaType.database.name()).
-                    and("is_deleted").ne(true);
+            Criteria criteria1 = Criteria.where(SOURCE_ID_WITHOUT_UNDERLINE).in(connId)
+                    .and(META_TYPE).is(MetaType.database.name()).
+                    and(IS_DELETED).ne(true);
             Query query = new Query(criteria1);
-            query.fields().include("id", "name", "meta_type", "original_name", "source");
+            query.fields().include("id", "name", META_TYPE, ORIGINAL_NAME, "source");
             List<MetadataInstancesDto> connObj = findAllDto(query, user);
             return connObj;
 
@@ -716,24 +737,24 @@ public List<MetadataInstancesDto> originalData(String isTarget, String qualified
 
     public MetadataInstancesDto findBySourceIdAndTableName(String sourceId, String tableName, String taskId, UserDetail userDetail) {
         Criteria criteria = Criteria
-                .where("meta_type").in("table", "collection", "view")
-                .and("original_name").is(tableName)
-                .and("is_deleted").ne(true)
-                .and("source._id").is(sourceId)
-                .and("taskId").is(taskId);
+                .where(META_TYPE).in(TABLE, COLLECTION, "view")
+                .and(ORIGINAL_NAME).is(tableName)
+                .and(IS_DELETED).ne(true)
+                .and(SOURCE_ID).is(sourceId)
+                .and(TASK_ID).is(taskId);
 
         return findOne(Query.query(criteria), userDetail);
     }
 
     public List<MetadataInstancesDto> findSourceSchemaBySourceId(String sourceId, List<String> tableNames, UserDetail userDetail, String... fields) {
         Criteria criteria = Criteria
-                .where("meta_type").in(Lists.of("table", "collection", "view"))
-                .and("is_deleted").ne(true)
-                .and("source._id").is(sourceId)
-                .and("sourceType").is(SourceTypeEnum.SOURCE.name())
-                .and("taskId").exists(false);
+                .where(META_TYPE).in(Lists.of(TABLE, COLLECTION, "view"))
+                .and(IS_DELETED).ne(true)
+                .and(SOURCE_ID).is(sourceId)
+                .and(SOURCE_TYPE).is(SourceTypeEnum.SOURCE.name())
+                .and(TASK_ID).exists(false);
         if (CollectionUtils.isNotEmpty(tableNames)) {
-            criteria.and("original_name").in(tableNames);
+            criteria.and(ORIGINAL_NAME).in(tableNames);
         }
 
         Query query = new Query(criteria);
@@ -745,13 +766,13 @@ public List<MetadataInstancesDto> findSourceSchemaBySourceId(String sourceId, Li
 
     public List<MetadataInstancesDto> findBySourceIdAndTableNameList(String sourceId, List<String> tableNames, UserDetail userDetail, String taskId) {
         Criteria criteria = Criteria
-                .where("meta_type").in(Lists.of("table", "collection", "view"))
-                .and("is_deleted").ne(true)
-                .and("source._id").is(sourceId)
-                .and("taskId").is(taskId);
+                .where(META_TYPE).in(Lists.of(TABLE, COLLECTION, "view"))
+                .and(IS_DELETED).ne(true)
+                .and(SOURCE_ID).is(sourceId)
+                .and(TASK_ID).is(taskId);
 
         if (CollectionUtils.isNotEmpty(tableNames)) {
-            criteria.and("original_name").in(tableNames);
+            criteria.and(ORIGINAL_NAME).in(tableNames);
         }
 
         return findAllDto(Query.query(criteria), userDetail);
@@ -759,13 +780,13 @@ public List<MetadataInstancesDto> findBySourceIdAndTableNameList(String sourceId
 
     public List<MetadataInstancesDto> findBySourceIdAndTableNameListNeTaskId(String sourceId, List<String> tableNames, UserDetail userDetail) {
         Criteria criteria = Criteria
-                .where("meta_type").in(Lists.of("table", "collection", "view"))
-                .and("is_deleted").ne(true)
-                .and("source._id").is(sourceId)
-                .and("taskId").exists(false);
+                .where(META_TYPE).in(Lists.of(TABLE, COLLECTION, "view"))
+                .and(IS_DELETED).ne(true)
+                .and(SOURCE_ID).is(sourceId)
+                .and(TASK_ID).exists(false);
 
         if (CollectionUtils.isNotEmpty(tableNames)) {
-            criteria.and("original_name").in(tableNames);
+            criteria.and(ORIGINAL_NAME).in(tableNames);
         }
 
         return findAllDto(Query.query(criteria), userDetail);
@@ -773,13 +794,13 @@ public List<MetadataInstancesDto> findBySourceIdAndTableNameListNeTaskId(String
 
     public List<MetadataInstancesEntity> findEntityBySourceIdAndTableNameList(String sourceId, List<String> tableNames, UserDetail userDetail, String taskId) {
         Criteria criteria = Criteria
-                .where("meta_type").in(Lists.of("table", "collection", "view"))
-                .and("is_deleted").ne(true)
-                .and("source._id").is(sourceId)
-                .and("taskId").is(taskId);
+                .where(META_TYPE).in(Lists.of(TABLE, COLLECTION, "view"))
+                .and(IS_DELETED).ne(true)
+                .and(SOURCE_ID).is(sourceId)
+                .and(TASK_ID).is(taskId);
 
         if (CollectionUtils.isNotEmpty(tableNames)) {
-            criteria.and("original_name").in(tableNames);
+            criteria.and(ORIGINAL_NAME).in(tableNames);
         }
 
         return findAll(Query.query(criteria), userDetail);
@@ -790,21 +811,21 @@ public Update buildUpdateSet(MetadataInstancesEntity entity) {
     }
 
     public List<MetadataInstancesDto> findByQualifiedName(String qualifiedName, UserDetail user) {
-        Where where = new Where().and("qualified_name", qualifiedName);
+        Where where = new Where().and(QUALIFIED_NAME, qualifiedName);
         return findAll(where, user);
     }
 
     public MetadataInstancesDto findByQualifiedNameNotDelete(String qualifiedName, UserDetail user, String... fieldName) {
-        Criteria criteria = Criteria.where("qualified_name").is(qualifiedName).and("is_deleted").ne(true);
+        Criteria criteria = Criteria.where(QUALIFIED_NAME).is(qualifiedName).and(IS_DELETED).ne(true);
 
         Query query = new Query(criteria);
         return findOne(query);
     }
 
     public List<MetadataInstancesDto> findByQualifiedNameList(List<String> qualifiedNames, String taskId) {
-        Criteria criteria = Criteria.where("qualified_name").in(qualifiedNames)
-                .and("is_deleted").ne(true)
-                .and("taskId").is(taskId);
+        Criteria criteria = Criteria.where(QUALIFIED_NAME).in(qualifiedNames)
+                .and(IS_DELETED).ne(true)
+                .and(TASK_ID).is(taskId);
 
         Query query = new Query(criteria);
         return findAll(query);
@@ -813,7 +834,7 @@ public List<MetadataInstancesDto> findByQualifiedNameList(List<String> qualified
 
 
     public List<MetadataInstancesDto> findByQualifiedNameNotDelete(List<String> qualifiedNames, UserDetail user, String... excludeFiled) {
-        Criteria criteria = Criteria.where("qualified_name").in(qualifiedNames).and("is_deleted").ne(true);
+        Criteria criteria = Criteria.where(QUALIFIED_NAME).in(qualifiedNames).and(IS_DELETED).ne(true);
 
         Query query = new Query(criteria);
         query.fields().exclude(excludeFiled);
@@ -821,10 +842,10 @@ public List<MetadataInstancesDto> findByQualifiedNameNotDelete(List<String> qual
     }
 
     public List<MetadataInstancesDto> findDatabaseSchemeNoHistory(List<String> databaseIds, UserDetail user) {
-        Criteria criteria = Criteria.where("source._id").in(databaseIds).and("meta_type").is("database").and("is_deleted").ne(true);
+        Criteria criteria = Criteria.where(SOURCE_ID).in(databaseIds).and(META_TYPE).is("database").and(IS_DELETED).ne(true);
 
         Query query = new Query(criteria);
-        query.fields().exclude("histories");
+        query.fields().exclude(HISTORIES);
         return findAllDto(query, user);
     }
 
@@ -889,7 +910,7 @@ public int bulkSave(List<MetadataInstancesDto> metadataInstancesDtos,
                 String rollbackTable = options != null ? options.getRollbackTable() : null;
                 String fieldsNameTransform = options != null ? options.getFieldsNameTransform() : null;
                 if ("all".equalsIgnoreCase(rollback) ||
-                        ("table".equalsIgnoreCase(rollback) &&
+                        (TABLE.equalsIgnoreCase(rollback) &&
                                 metadataInstancesDto.getOriginalName().equalsIgnoreCase(rollbackTable))) {
 
                     metadataInstancesDto.getFields().forEach(field -> {
@@ -899,7 +920,7 @@ public int bulkSave(List<MetadataInstancesDto> metadataInstancesDtos,
                                     && existsField.getIsAutoAllowed() != null && !existsField.getIsAutoAllowed()) {*/
                             String transformDataType = field.getDataType();
                                 BeanUtils.copyProperties(existsField, field);
-                                if ("table".equalsIgnoreCase(rollback) && StringUtils.isNotBlank(fieldsNameTransform)) {
+                                if (TABLE.equalsIgnoreCase(rollback) && StringUtils.isNotBlank(fieldsNameTransform)) {
                                     if ("toUpperCase".equalsIgnoreCase(fieldsNameTransform)) {
                                         field.setFieldName(field.getOriginalFieldName().toUpperCase());
                                     } else if("toLowerCase".equalsIgnoreCase(fieldsNameTransform)) {
@@ -926,12 +947,12 @@ public int bulkSave(List<MetadataInstancesDto> metadataInstancesDtos,
                 hisModels.add(historyModel);
                 BasicDBObject basicDBObject = new BasicDBObject("$each", hisModels);
                 basicDBObject.append("$slice", -5);
-                update.push("histories", basicDBObject);
-                update.set("fields", metadataInstancesDto.getFields());
+                update.push(HISTORIES, basicDBObject);
+                update.set(FIELDS, metadataInstancesDto.getFields());
                 update.set("indices", metadataInstancesDto.getIndices());
-                update.set("is_deleted", false);
+                update.set(IS_DELETED, false);
                 update.set("createSource", metadataInstancesDto.getCreateSource());
-                update.set("original_name", metadataInstancesDto.getOriginalName());
+                update.set(ORIGINAL_NAME, metadataInstancesDto.getOriginalName());
                 update.set("name", metadataInstancesDto.getName());
 
                 Query where = Query.query(Criteria.where("id").is(existsMetadataInstance.getId()));
@@ -959,7 +980,7 @@ public int bulkSave(List<MetadataInstancesDto> metadataInstancesDtos,
                     metadataInstance.setMetaType(MetaType.qingFlowApp.name());
                 }
                 Update update = repository.buildUpdateSet(metadataInstance, userDetail);
-                Query where = Query.query(Criteria.where("qualified_name").is(metadataInstance.getQualifiedName()));
+                Query where = Query.query(Criteria.where(QUALIFIED_NAME).is(metadataInstance.getQualifiedName()));
                 repository.applyUserDetail(where, userDetail);
                 repository.beforeUpsert(update, userDetail);
                 bulkOperations.upsert(where, update);
@@ -1013,10 +1034,10 @@ public int bulkSave(List<MetadataInstancesDto> insertMetaDataDtos,
             //动态新增表做的兼容处理
             String insertUuid = uuid;
             if (saveHistory) {
-                Query query = new Query(Criteria.where("taskId").is(taskId)
-                        .and("is_deleted").ne(true)
-                        .and("transformUuid").exists(true));
-                query.fields().include("transformUuid");
+                Query query = new Query(Criteria.where(TASK_ID).is(taskId)
+                        .and(IS_DELETED).ne(true)
+                        .and(TRANSFORM_UUID).exists(true));
+                query.fields().include(TRANSFORM_UUID);
                 MetadataInstancesDto one = findOne(query);
                 insertUuid = one.getTransformUuid();
             }
@@ -1029,7 +1050,7 @@ public int bulkSave(List<MetadataInstancesDto> insertMetaDataDtos,
                 //这个操作有可能是插入操作，所以需要校验字段是否又id，如果没有就set id进去
                 beforeSave(metadataInstancesDto, userDetail);
                 Update update = repository.buildUpdateSet(metadataInstance, userDetail);
-                Query where = Query.query(Criteria.where("qualified_name").is(metadataInstance.getQualifiedName()));
+                Query where = Query.query(Criteria.where(QUALIFIED_NAME).is(metadataInstance.getQualifiedName()));
                 repository.applyUserDetail(where, userDetail);
                 repository.beforeUpsert(update, userDetail);
                 bulkOperations.upsert(where, update);
@@ -1044,9 +1065,9 @@ public int bulkSave(List<MetadataInstancesDto> insertMetaDataDtos,
             for (MetadataInstancesDto value : updateMetaMap.values()) {
                 findQualifiedNames.add(value.getQualifiedName());
             }
-            Criteria criteria = Criteria.where("qualified_name").in(findQualifiedNames);
+            Criteria criteria = Criteria.where(QUALIFIED_NAME).in(findQualifiedNames);
             Query query = new Query(criteria);
-            query.fields().exclude("histories");
+            query.fields().exclude(HISTORIES);
             List<MetadataInstancesDto> metadataInstancesDtos = findAllDto(query, userDetail);
             metaMap = metadataInstancesDtos.stream().collect(Collectors.toMap(m -> m.getId().toHexString(), m -> m));
 
@@ -1080,7 +1101,7 @@ public int bulkSave(List<MetadataInstancesDto> insertMetaDataDtos,
                     }
                 }
 
-                Query where = Query.query(Criteria.where("qualified_name").is(value.getQualifiedName()));
+                Query where = Query.query(Criteria.where(QUALIFIED_NAME).is(value.getQualifiedName()));
 
                 bulkOperations.updateOne(where, update);
                 write = true;
@@ -1097,7 +1118,7 @@ public int bulkSave(List<MetadataInstancesDto> insertMetaDataDtos,
 
                     MetadataInstancesEntity entityOld = convertToEntity(MetadataInstancesEntity.class, value);
                     Update updateOld = repository.buildUpdateSet(entityOld, userDetail);
-                    Query whereOld = Query.query(Criteria.where("qualified_name").is(value.getQualifiedName()));
+                    Query whereOld = Query.query(Criteria.where(QUALIFIED_NAME).is(value.getQualifiedName()));
 
                     bulkOperations.updateOne(whereOld, updateOld);
                 }
@@ -1117,8 +1138,8 @@ public int bulkSave(List<MetadataInstancesDto> insertMetaDataDtos,
             }
 
             if (StringUtils.isNotBlank(uuid) && !saveHistory) {
-                Criteria deleteOldMetadata = Criteria.where("taskId").is(taskId)
-                        .and("transformUuid").ne(uuid);
+                Criteria deleteOldMetadata = Criteria.where(TASK_ID).is(taskId)
+                        .and(TRANSFORM_UUID).ne(uuid);
                 deleteAll(new Query(deleteOldMetadata), userDetail);
             }
             return result.getModifiedCount();
@@ -1138,7 +1159,7 @@ public Pair<Integer, Integer> bulkUpsetByWhere(List<MetadataInstancesDto> metada
             num++;
 
 
-            Criteria criteria = Criteria.where("qualified_name").is(dto.getQualifiedName());
+            Criteria criteria = Criteria.where(QUALIFIED_NAME).is(dto.getQualifiedName());
             dto.setId(null);
             Query query = new Query(criteria);
             beforeSave(dto, user);
@@ -1163,34 +1184,34 @@ public Pair<Integer, Integer> bulkUpsetByWhere(List<MetadataInstancesDto> metada
     }
 
     public List<String> tables(String connectId, String sourceType) {
-        Criteria criteria = Criteria.where("source._id").is(connectId)
-                .and("sourceType").is(sourceType)
-                .and("is_deleted").ne(true)
-                .and("taskId").exists(false)
-                .and("meta_type").in(MetaType.collection.name(), MetaType.table.name());
+        Criteria criteria = Criteria.where(SOURCE_ID).is(connectId)
+                .and(SOURCE_TYPE).is(sourceType)
+                .and(IS_DELETED).ne(true)
+                .and(TASK_ID).exists(false)
+                .and(META_TYPE).in(MetaType.collection.name(), MetaType.table.name());
         Query query = new Query(criteria);
-        query.fields().include("original_name");
+        query.fields().include(ORIGINAL_NAME);
         List<MetadataInstancesEntity> list = mongoTemplate.find(query, MetadataInstancesEntity.class);
         return list.stream().map(MetadataInstancesEntity::getOriginalName).collect(Collectors.toList());
     }
 
 
     public List<Map<String, String>> tableValues(String connectId, String sourceType) {
-        Criteria criteria = Criteria.where("source._id").is(connectId)
-                .and("sourceType").is(sourceType)
-                .and("is_deleted").ne(true)
-                .and("taskId").exists(false)
-                .and("meta_type").in(MetaType.collection.name(), MetaType.table.name());
+        Criteria criteria = Criteria.where(SOURCE_ID).is(connectId)
+                .and(SOURCE_TYPE).is(sourceType)
+                .and(IS_DELETED).ne(true)
+                .and(TASK_ID).exists(false)
+                .and(META_TYPE).in(MetaType.collection.name(), MetaType.table.name());
         Query query = new Query(criteria);
-        query.fields().include("original_name","comment");
+        query.fields().include(ORIGINAL_NAME,COMMENT);
         List<MetadataInstancesEntity> list = mongoTemplate.find(query, MetadataInstancesEntity.class);
         List<Map<String, String>> values = new ArrayList<>();
         if (CollectionUtils.isNotEmpty(list)) {
             for (MetadataInstancesEntity entity : list) {
                 Map<String, String> value = new HashMap<>();
-                value.put("tableName", entity.getOriginalName());
-                value.put("tableId", entity.getId().toHexString());
-                value.put("tableComment",StringUtils.isNotBlank(entity.getComment()) ? entity.getComment():"");
+                value.put(TABLE_NAME, entity.getOriginalName());
+                value.put(TABLE_ID, entity.getId().toHexString());
+                value.put(TABLE_COMMENT,StringUtils.isNotBlank(entity.getComment()) ? entity.getComment():"");
                 values.add(value);
             }
         }
@@ -1198,25 +1219,25 @@ public List<Map<String, String>> tableValues(String connectId, String sourceType
     }
 
     public Page<Map<String, Object>> pageTables(String connectId, String sourceType, String regex, int skip, int limit) {
-			Criteria criteria = Criteria.where("source._id").is(connectId)
-				.and("sourceType").is(sourceType)
-				.and("is_deleted").ne(true)
-				.and("taskId").exists(false)
-				.and("meta_type").in(MetaType.collection.name(), MetaType.table.name());
+			Criteria criteria = Criteria.where(SOURCE_ID).is(connectId)
+				.and(SOURCE_TYPE).is(sourceType)
+				.and(IS_DELETED).ne(true)
+				.and(TASK_ID).exists(false)
+				.and(META_TYPE).in(MetaType.collection.name(), MetaType.table.name());
 
 			if (null != regex) {
 				regex = "^" + regex + "$";
-				criteria.and("original_name").regex(regex);
+				criteria.and(ORIGINAL_NAME).regex(regex);
 			}
 
             Aggregation aggregation = Aggregation.newAggregation(
                     Aggregation.match(criteria),
-                    Aggregation.unwind("fields"),
+                    Aggregation.unwind(FIELDS),
                     Aggregation.unwind("indices", true),
                     Aggregation.project()
                             .and(AggregationExpression.from(MongoExpression.create("{ \"$toString\": \"$_id\" }"))).as("_id")
-                            .and("original_name").as("tableName")
-                            .and("comment").as("tableComment")
+                            .and(ORIGINAL_NAME).as(TABLE_NAME)
+                            .and(COMMENT).as(TABLE_COMMENT)
                             .and(ConditionalOperators
                                     .when(ComparisonOperators.Eq.valueOf("fields.primaryKey").equalToValue(true))
                                     .then(1)
@@ -1228,26 +1249,26 @@ public Page<Map<String, Object>> pageTables(String connectId, String sourceType,
                                     .otherwise(0)
                             ).as("uniqueIndex"),
                     Aggregation.group("_id")
-                            .first("tableName").as("tableName")
-                            .first("tableComment").as("tableComment")
+                            .first(TABLE_NAME).as(TABLE_NAME)
+                            .first(TABLE_COMMENT).as(TABLE_COMMENT)
                             .sum("primaryKey").as("primaryKeyCounts")
                             .sum("uniqueIndex").as("uniqueIndexCounts"),
                     Aggregation.project()
-                            .and("_id").as("tableId")
-                            .andInclude("tableName", "tableComment", "primaryKeyCounts", "uniqueIndexCounts")
+                            .and("_id").as(TABLE_ID)
+                            .andInclude(TABLE_NAME, TABLE_COMMENT, "primaryKeyCounts", "uniqueIndexCounts")
                             .andExclude("_id"),
-                    Aggregation.sort(Sort.by("tableId"))
+                    Aggregation.sort(Sort.by(TABLE_ID))
             );
 
 			long totals;
 			List<Map<String, Object>> values = new ArrayList<>();
 			if (limit > 0) {
 				Query query = new Query(criteria);
-				query.fields().include("original_name", "comment");
+				query.fields().include(ORIGINAL_NAME, COMMENT);
 				totals = mongoTemplate.count(query, MetadataInstancesEntity.class);
 				if (totals > 0) {
 					aggregation.getPipeline().add(Aggregation.skip((long) skip)).add(Aggregation.limit(limit));
-					AggregationResults<Map> aggregate = mongoTemplate.aggregate(aggregation, "MetadataInstances", Map.class);
+					AggregationResults<Map> aggregate = mongoTemplate.aggregate(aggregation, METADATA_INSTANCES, Map.class);
 					for (Map m : aggregate.getMappedResults()) {
 						values.add(m);
 					}
@@ -1255,7 +1276,7 @@ public Page<Map<String, Object>> pageTables(String connectId, String sourceType,
 					values = new ArrayList<>();
 				}
 			} else {
-				AggregationResults<Map> aggregate = mongoTemplate.aggregate(aggregation, "MetadataInstances", Map.class);
+				AggregationResults<Map> aggregate = mongoTemplate.aggregate(aggregation, METADATA_INSTANCES, Map.class);
 				totals = aggregate.getMappedResults().size();
 				for (Map m : aggregate.getMappedResults()) {
 					values.add(m);
@@ -1267,12 +1288,12 @@ public Page<Map<String, Object>> pageTables(String connectId, String sourceType,
 
     public TableSupportInspectVo tableSupportInspect(String connectId, String tableName) {
         TableSupportInspectVo tableSupportInspectVo = new TableSupportInspectVo();
-        Criteria criteria = Criteria.where("source._id").is(connectId)
-                .and("is_deleted").is(false)
-                .and("original_name").is(tableName)
-                .and("meta_type").in(MetaType.collection.name(), MetaType.table.name());
+        Criteria criteria = Criteria.where(SOURCE_ID).is(connectId)
+                .and(IS_DELETED).is(false)
+                .and(ORIGINAL_NAME).is(tableName)
+                .and(META_TYPE).in(MetaType.collection.name(), MetaType.table.name());
         Query query = new Query(criteria);
-        query.fields().include("fields");
+        query.fields().include(FIELDS);
         MetadataInstancesDto metadataInstancesDtos = findOne(query);
         List<Field> fieldList = metadataInstancesDtos.getFields() == null ? new ArrayList<>() : metadataInstancesDtos.getFields();
         Optional primaryKeyPosition = fieldList.stream().filter(field -> field.getPrimaryKeyPosition() > 0).findAny();
@@ -1283,12 +1304,12 @@ public TableSupportInspectVo tableSupportInspect(String connectId, String tableN
 
     public List<TableSupportInspectVo> tablesSupportInspect(TablesSupportInspectParam tablesSupportInspectParam) {
         List<TableSupportInspectVo> tableSupportInspectVoList = new ArrayList<>();
-        Criteria criteria = Criteria.where("source._id").is(tablesSupportInspectParam.getConnectionId())
-                .and("is_deleted").is(false)
-                .and("original_name").in(tablesSupportInspectParam.getTableNames())
-                .and("meta_type").in(MetaType.collection.name(), MetaType.table.name());
+        Criteria criteria = Criteria.where(SOURCE_ID).is(tablesSupportInspectParam.getConnectionId())
+                .and(IS_DELETED).is(false)
+                .and(ORIGINAL_NAME).in(tablesSupportInspectParam.getTableNames())
+                .and(META_TYPE).in(MetaType.collection.name(), MetaType.table.name());
         Query query = new Query(criteria);
-        query.fields().include("fields", "original_name");
+        query.fields().include(FIELDS, ORIGINAL_NAME);
         List<MetadataInstancesDto> metadataInstancesDtos = findAll(query);
 
         for (MetadataInstancesDto metadataInstancesDto : metadataInstancesDtos) {
@@ -1310,7 +1331,7 @@ public Table getMetadata(String connectionId, String metaType, String tableName,
             return null;
         }
         String qualifiedName = MetaDataBuilderUtils.generateQualifiedName(metaType, connectionDto, tableName);
-        Criteria criteria = Criteria.where("qualified_name").is(qualifiedName);
+        Criteria criteria = Criteria.where(QUALIFIED_NAME).is(qualifiedName);
 
         MetadataInstancesDto metedata = findOne(new Query(criteria));
 
@@ -1336,7 +1357,7 @@ public TapTable getMetadataV2(String connectionId, String metaType, String table
 
 
         String qualifiedName = MetaDataBuilderUtils.generateQualifiedName(metaType, connectionDto, tableName);
-        Criteria criteria = Criteria.where("qualified_name").is(qualifiedName);
+        Criteria criteria = Criteria.where(QUALIFIED_NAME).is(qualifiedName);
 
         MetadataInstancesDto metedata = findOne(new Query(criteria));
 
@@ -1351,7 +1372,7 @@ public List<Table> findOldByNodeId(Filter filter, UserDetail user) {
         if (where == null) {
             return null;
         }
-        String nodeId = (String) where.get("nodeId");
+        String nodeId = (String) where.get(NODE_ID);
         List<MetadataInstancesDto> metadatas = findByNodeId(nodeId, null, user, null);
         if (CollectionUtils.isNotEmpty(metadatas)) {
             return metadatas.stream().map(this::getOldSchema).collect(Collectors.toList());
@@ -1364,7 +1385,7 @@ public Map<String, String> findTableMapByNodeId(Filter filter) {
         if (where == null) {
             return null;
         }
-        String nodeId = (String) where.get("nodeId");
+        String nodeId = (String) where.get(NODE_ID);
         return findKVByNode(nodeId);
     }
 
@@ -1381,7 +1402,7 @@ private Table getOldSchema(MetadataInstancesDto metadata) {
 
 
     public Map<String, String> findKVByNode(String nodeId) {
-        Criteria criteria = Criteria.where("dag.nodes.id").is(nodeId);
+        Criteria criteria = Criteria.where(DAG_NODES_ID).is(nodeId);
         Query query = new Query(criteria);
         TaskDto taskDto = taskService.findOne(query);
 
@@ -1414,7 +1435,7 @@ protected Map<String, String> getNodeMapping(UserDetail user, TaskDto taskDto, M
 				Map<String, LogCollecotrConnConfig> connConfigs = logCollectorNode.getLogCollectorConnConfigs();
 				if (null != connConfigs && !connConfigs.isEmpty()) {
 					need2Parse = false;
-					List<MetadataInstancesDto> metadatas = findByNodeId(node.getId(), Lists.of("original_name", "qualified_name", "source._id"), user, taskDto);
+					List<MetadataInstancesDto> metadatas = findByNodeId(node.getId(), Lists.of(ORIGINAL_NAME, QUALIFIED_NAME, SOURCE_ID), user, taskDto);
 					if (CollectionUtils.isNotEmpty(metadatas)) {
 						Map<String, String> connectionNamespace = new HashMap<>();
 						BiFunction<String, String, String> keyParser = (connectionId, originalName) -> {
@@ -1441,7 +1462,7 @@ protected Map<String, String> getNodeMapping(UserDetail user, TaskDto taskDto, M
 			}
 
 			if (need2Parse) {
-				List<MetadataInstancesDto> metadatas = findByNodeId(node.getId(), Lists.of("original_name", "qualified_name"), user, taskDto);
+				List<MetadataInstancesDto> metadatas = findByNodeId(node.getId(), Lists.of(ORIGINAL_NAME, QUALIFIED_NAME), user, taskDto);
 				if (CollectionUtils.isNotEmpty(metadatas)) {
 					kv = metadatas.stream()
 						.collect(Collectors.toMap(MetadataInstancesDto::getOriginalName
@@ -1458,16 +1479,19 @@ public String findHeartbeatQualifiedNameByNodeId(Filter filter, UserDetail user)
             return null;
         }
 
-        final String nodeId = (String) where.get("nodeId");
+        final String nodeId = (String) where.get(NODE_ID);
         AtomicReference<String> taskId = new AtomicReference<>();
         return Optional.ofNullable(nodeId).map(nid -> {
             // find running task
-            Query query = new Query(Criteria.where("dag.nodes.id").is(nid));
+            Query query = new Query(Criteria.where(DAG_NODES_ID).is(nid));
             query.fields().include("_id");
             return taskService.findOne(query, user);
         }).map(task -> task.getId().toHexString()).map(tid -> {
             // get heartbeat task dag of the connection node
-            Query query = new Query(Criteria.where(ConnHeartbeatUtils.TASK_RELATION_FIELD).is(tid));
+            Criteria criteria = Criteria.where(ConnHeartbeatUtils.TASK_RELATION_FIELD).is(tid);
+            criteria.and("status").ne("deleting");
+            criteria.and(IS_DELETED).ne(true);
+            Query query = new Query(criteria);
             query.fields().include("_id", "dag");
             return taskService.findOne(query, user);
         }).map(taskDto -> {
@@ -1508,9 +1532,9 @@ public String getQualifiedNameByNodeId(Node node, UserDetail user, DataSourceCon
             dataSource.setDefinitionPdkId(definitionDto.getPdkId());
             dataSource.setDefinitionScope(definitionDto.getScope());
             dataSource.setDefinitionVersion(definitionDto.getVersion());
-            String metaType = "table";
-            if ("mongodb".equals(dataSource.getDatabase_type())) {
-                metaType = "collection";
+            String metaType = TABLE;
+            if (MONGODB.equals(dataSource.getDatabase_type())) {
+                metaType = COLLECTION;
             }
 
             String tableName = ((TableNode) node).transformTableName(((TableNode) node).getTableName());
@@ -1523,7 +1547,7 @@ public String getQualifiedNameByNodeId(Node node, UserDetail user, DataSourceCon
 
     public List<String> findDatabaseNodeQualifiedName(String nodeId, UserDetail user, TaskDto taskDto, DataSourceConnectionDto dataSource, DataSourceDefinitionDto definitionDto, List<String> includes) {
         if (taskDto == null || taskDto.getDag() == null) {
-            Criteria criteria = Criteria.where("dag.nodes.id").is(nodeId);
+            Criteria criteria = Criteria.where(DAG_NODES_ID).is(nodeId);
             Query query = new Query(criteria);
             query.fields().include("dag");
             taskDto = taskService.findOne(query, user);
@@ -1549,9 +1573,9 @@ public List<String> findDatabaseNodeQualifiedName(String nodeId, UserDetail user
                 dataSource.setDefinitionPdkId(definitionDto.getPdkId());
                 dataSource.setDefinitionScope(definitionDto.getScope());
                 dataSource.setDefinitionVersion(definitionDto.getVersion());
-                String metaType = "table";
-                if ("mongodb".equals(dataSource.getDatabase_type())) {
-                    metaType = "collection";
+                String metaType = TABLE;
+                if (MONGODB.equals(dataSource.getDatabase_type())) {
+                    metaType = COLLECTION;
                 }
 
                 List<String> tableNames;
@@ -1584,25 +1608,25 @@ public List<String> findDatabaseNodeQualifiedName(String nodeId, UserDetail user
 
     public List<MetadataInstancesDto> findByNodeId(String nodeId, UserDetail userDetail) {
         Criteria criteria = Criteria
-                .where("is_deleted").ne(true)
-                .and("nodeId").is(nodeId);
+                .where(IS_DELETED).ne(true)
+                .and(NODE_ID).is(nodeId);
 
         return findAllDto(Query.query(criteria), userDetail);
     }
 
     public List<MetadataInstancesDto> findByNodeId(String nodeId, UserDetail userDetail, String taskId, String... fields) {
         Criteria criteria = Criteria
-                .where("is_deleted").ne(true)
-                .and("nodeId").is(nodeId)
-                .and("taskId").is(taskId);
+                .where(IS_DELETED).ne(true)
+                .and(NODE_ID).is(nodeId)
+                .and(TASK_ID).is(taskId);
 
         return findAllDto(Query.query(criteria), userDetail);
     }
 
     public List<MetadataInstancesDto> findByTaskId(String taskId, UserDetail userDetail) {
         Criteria criteria = Criteria
-                .where("is_deleted").ne(true)
-                .and("taskId").is(taskId);
+                .where(IS_DELETED).ne(true)
+                .and(TASK_ID).is(taskId);
 
         return findAllDto(Query.query(criteria), userDetail);
     }
@@ -1644,7 +1668,7 @@ public Map<String, List<MetadataInstancesDto>> findByNodeIds(List<String> nodeId
     public Page<MetadataInstancesDto> findByNodeId(String nodeId, List<String> fields, UserDetail user, TaskDto taskDto, String tableFilter, String filterType, int page, int pageSize) {
 			  user.setFreeAuth();
         if (taskDto == null || taskDto.getDag() == null) {
-            Criteria criteria = Criteria.where("dag.nodes.id").is(nodeId);
+            Criteria criteria = Criteria.where(DAG_NODES_ID).is(nodeId);
             Query query = new Query(criteria);
             query.fields().include("dag");
             taskDto = taskService.findOne(query, user);
@@ -1662,8 +1686,8 @@ public Page<MetadataInstancesDto> findByNodeId(String nodeId, List<String> field
         if (taskDto.getDag() != null) {
             Node node = dag.getNode(nodeId);
             if (node != null) {
-                Criteria criteriaTable = Criteria.where("meta_type").in("table", "collection", "view");
-                Criteria criteriaNode = Criteria.where("meta_type").is(MetaType.processor_node.name());
+                Criteria criteriaTable = Criteria.where(META_TYPE).in(TABLE, COLLECTION, "view");
+                Criteria criteriaNode = Criteria.where(META_TYPE).is(MetaType.processor_node.name());
                 Query queryMetadata = new Query();
                 if (pageSize > 0) {
                     queryMetadata.skip((long) (Math.max(1, page) - 1) * pageSize);
@@ -1674,10 +1698,10 @@ public Page<MetadataInstancesDto> findByNodeId(String nodeId, List<String> field
                     queryMetadata.fields().include(fieldArrays);
                 }
                 if (node instanceof MigrateProcessorNode) {
-                    Criteria criteria = Criteria.where("nodeId").is(nodeId).and("is_deleted").ne(true);
+                    Criteria criteria = Criteria.where(NODE_ID).is(nodeId).and(IS_DELETED).ne(true);
                     if (StringUtils.isNotBlank(tableFilter)) {
                         Pattern pattern = Pattern.compile(tableFilter, Pattern.CASE_INSENSITIVE);
-                        criteria.and("originalName").regex(pattern);
+                        criteria.and(LOWER_CAME_ORIGINAL_NAME).regex(pattern);
                     }
                     Query nodeQuery = new Query(criteria);
                     List<MetadataInstancesDto> all = findAll(nodeQuery);
@@ -1709,7 +1733,7 @@ public Page<MetadataInstancesDto> findByNodeId(String nodeId, List<String> field
                 } else if (Node.NodeCatalog.processor.equals(node.getCatalog())) {
                     queryMetadata.addCriteria(criteriaNode);
                     String qualifiedName = MetaDataBuilderUtils.generateQualifiedName(MetaType.processor_node.name(), nodeId, null, taskId);
-                    criteriaNode.and("qualified_name").is(qualifiedName).and("is_deleted").ne(true);
+                    criteriaNode.and(QUALIFIED_NAME).is(qualifiedName).and(IS_DELETED).ne(true);
                     MetadataInstancesDto one = findOne(queryMetadata, user);
                     if (one != null) {
                         metadatas.add(one);
@@ -1720,8 +1744,8 @@ public Page<MetadataInstancesDto> findByNodeId(String nodeId, List<String> field
                     if (StringUtils.isBlank(tableNode.getTableName())) {
                         return new Page<>(0, new ArrayList<>());
                     }
-                    criteriaTable.and("source._id").is(tableNode.getConnectionId())
-                            .and("original_name").is(tableNode.getTableName()).and("taskId").is(taskId).and("is_deleted").ne(true);
+                    criteriaTable.and(SOURCE_ID).is(tableNode.getConnectionId())
+                            .and(ORIGINAL_NAME).is(tableNode.getTableName()).and(TASK_ID).is(taskId).and(IS_DELETED).ne(true);
                     MetadataInstancesDto one = findOne(queryMetadata, user);
                     if (one != null) {
                         metadatas.add(one);
@@ -1753,13 +1777,13 @@ public Page<MetadataInstancesDto> findByNodeId(String nodeId, List<String> field
                     if (CollectionUtils.isEmpty(tableNames)) {
                         metadatas = Lists.newArrayList();
                     } else{
-                        criteriaTable.and("nodeId").is(nodeId)
-                                .and("taskId").is(taskId)
-                                .and("is_deleted").ne(true);
+                        criteriaTable.and(NODE_ID).is(nodeId)
+                                .and(TASK_ID).is(taskId)
+                                .and(IS_DELETED).ne(true);
 
                         if (StringUtils.isNotBlank(tableFilter)) {
                             Pattern pattern = Pattern.compile(tableFilter, Pattern.CASE_INSENSITIVE);
-                            criteriaTable.and("originalName").regex(pattern);
+                            criteriaTable.and(LOWER_CAME_ORIGINAL_NAME).regex(pattern);
                         }
 
                         metadatas = findAllDto(queryMetadata, user);
@@ -1773,10 +1797,10 @@ public Page<MetadataInstancesDto> findByNodeId(String nodeId, List<String> field
 					if (null != connConfigs && !connConfigs.isEmpty()) {
 						List<Criteria> criteriaList = new ArrayList<>();
 						for (LogCollecotrConnConfig config : connConfigs.values()) {
-							criteriaList.add(Criteria.where("source._id").is(config.getConnectionId())
-								.and("originalName").in(config.getTableNames()));
+							criteriaList.add(Criteria.where(SOURCE_ID).is(config.getConnectionId())
+								.and(LOWER_CAME_ORIGINAL_NAME).in(config.getTableNames()));
 						}
-						criteriaTable.and("is_deleted").ne(true).orOperator(criteriaList);
+						criteriaTable.and(IS_DELETED).ne(true).orOperator(criteriaList);
 						queryMetadata.addCriteria(criteriaTable);
 						metadatas = findAllDto(queryMetadata, user);
 						totals = count(queryMetadata, user);
@@ -1785,8 +1809,8 @@ public Page<MetadataInstancesDto> findByNodeId(String nodeId, List<String> field
 						if (CollectionUtils.isNotEmpty(connectionIds)) {
 							String connectionId = connectionIds.get(0);
 							queryMetadata.addCriteria(criteriaTable);
-							criteriaTable.and("source._id").is(connectionId)
-								.and("originalName").in(logNode.getTableNames()).and("is_deleted").ne(true);
+							criteriaTable.and(SOURCE_ID).is(connectionId)
+								.and(LOWER_CAME_ORIGINAL_NAME).in(logNode.getTableNames()).and(IS_DELETED).ne(true);
 							metadatas = findAllDto(queryMetadata, user);
 							totals = count(queryMetadata, user);
 						}
@@ -1799,36 +1823,27 @@ public Page<MetadataInstancesDto> findByNodeId(String nodeId, List<String> field
         return new Page<>(Math.max(totals, metadatas.size()), metadatas);
     }
 
-    public static void main(String[] args) {
-        Criteria criteriaNode = Criteria.where("meta_type").is(MetaType.processor_node.name());
-        Query queryMetadata = new Query();
-        queryMetadata.addCriteria(criteriaNode);
-        String qualifiedName = MetaDataBuilderUtils.generateQualifiedName(MetaType.processor_node.name(), "857f7321-8198-44d1-a73b-ea575897b304", null, "taskId");
-        criteriaNode.and("qualified_name").regex("^"+qualifiedName+".*")
-                .and("is_deleted").ne(true);
 
-        System.out.println(queryMetadata);
-    }
 
     public List<Map<String, Object>> search(String type, String keyword, String lastId, Integer pageSize, UserDetail user) {
-        List<String> metaTypes = Lists.newArrayList("table", "collection");
+        List<String> metaTypes = Lists.newArrayList(TABLE, COLLECTION);
         Criteria criteria;
-        if ("table".equals(type)) {
-            criteria = Criteria.where("meta_type").in(metaTypes)
-                    .orOperator(Criteria.where("original_name").regex(keyword), Criteria.where("name").regex(keyword)
-                            , Criteria.where("comment").regex(keyword));
+        if (TABLE.equals(type)) {
+            criteria = Criteria.where(META_TYPE).in(metaTypes)
+                    .orOperator(Criteria.where(ORIGINAL_NAME).regex(keyword), Criteria.where("name").regex(keyword)
+                            , Criteria.where(COMMENT).regex(keyword));
         } else if ("column".equals(type)) {
-            criteria = Criteria.where("meta_type").in(metaTypes)
+            criteria = Criteria.where(META_TYPE).in(metaTypes)
                     .orOperator(Criteria.where("fields.field_name").regex(keyword), Criteria.where("fields.alias_name").regex(keyword)
                             /*, Criteria.where("fields.comment").regex(keyword)*/);
         } else {
-            throw new BizException("IllegalArgument", type);
+            throw new BizException(ILLEGAL_ARGUMENT, type);
         }
 
         if (StringUtils.isNotBlank(lastId)) {
             criteria.and("_id").gt(MongoUtils.toObjectId(lastId));
         }
-        criteria.and("is_deleted").ne(true);
+        criteria.and(IS_DELETED).ne(true);
         Query query = new Query(criteria);
         query.with(Sort.by("_id").ascending());
         query.limit(pageSize);
@@ -1843,9 +1858,9 @@ public List<Map<String, Object>> search(String type, String keyword, String last
             obj.put("id", item.getId().toString());
             Map<String, Object> table = new HashMap<>();
             table.put("name", item.getName());
-            table.put("original_name", item.getOriginalName());
-            table.put("comment", item.getComment() == null ? "" : item.getComment());
-            obj.put("table", table);
+            table.put(ORIGINAL_NAME, item.getOriginalName());
+            table.put(COMMENT, item.getComment() == null ? "" : item.getComment());
+            obj.put(TABLE, table);
 
             if ("column".equals(type)) {
                 List<Map<String, Object>> column = new ArrayList<>();
@@ -1864,7 +1879,7 @@ public List<Map<String, Object>> search(String type, String keyword, String last
                         Map<String, Object> colObj = new HashMap<>();
                         colObj.put("field_name", field.getFieldName());
                         colObj.put("original_field_name", field.getOriginalFieldName());
-                        colObj.put("comment", field.getComment());
+                        colObj.put(COMMENT, field.getComment());
                         colObj.put("type", field.getJavaType());
                         column.add(colObj);
                     }
@@ -1879,11 +1894,11 @@ public List<Map<String, Object>> search(String type, String keyword, String last
 
     public List<MetaTableVo> tableSearch(String connectionId, String keyword, String lastId, Integer pageSize, UserDetail user) {
         Criteria criteria =
-                Criteria.where("source._id").is(connectionId)
-                        .and("is_deleted").ne(true)
-                        .and("meta_type").in(MetaType.collection.name(), MetaType.table.name())
-                        .orOperator(Criteria.where("original_name").regex(keyword), Criteria.where("name").regex(keyword)
-                                , Criteria.where("comment").regex(keyword));
+                Criteria.where(SOURCE_ID).is(connectionId)
+                        .and(IS_DELETED).ne(true)
+                        .and(META_TYPE).in(MetaType.collection.name(), MetaType.table.name())
+                        .orOperator(Criteria.where(ORIGINAL_NAME).regex(keyword), Criteria.where("name").regex(keyword)
+                                , Criteria.where(COMMENT).regex(keyword));
 
         if (StringUtils.isNotBlank(lastId)) {
             criteria.and("_id").gt(new ObjectId(lastId));
@@ -1914,10 +1929,10 @@ public List<MetaTableVo> tableSearch(String connectionId, String keyword, String
 
     public MetaTableCheckVo checkTableNames(String connectionId, List<String> names, UserDetail user) {
         List<String> metaTypes = Lists.newArrayList(MetaType.table.name());
-        Criteria criteria = Criteria.where("source._id").is(connectionId)
-                .and("is_deleted").is(false)
-                .and("meta_type").in(metaTypes)
-                .and("original_name").in(names);
+        Criteria criteria = Criteria.where(SOURCE_ID).is(connectionId)
+                .and(IS_DELETED).is(false)
+                .and(META_TYPE).in(metaTypes)
+                .and(ORIGINAL_NAME).in(names);
         Query query = new Query(criteria);
 
         List<MetadataInstancesDto> metaData = findAllDto(query, user);
@@ -1951,25 +1966,8 @@ public Page findMetadataList(Filter filter, UserDetail userDetail) {
 
 
     public TableListVo findTablesById(String id) {
-//        List<Map<String, String>> collections = new ArrayList<>();
         MetadataInstancesDto metadataInstancesDto = findById(MongoUtils.toObjectId(id));
-        if (null != metadataInstancesDto.getSource()) {
-//            String databaseId = metadataInstancesDto.getSource().get_id();
-
-      /*         List<MetadataInstancesDto> metadataInstancesTable = findAll(Query.query(Criteria.where("source._id").is(databaseId)
-                    .and("is_deleted").ne(true)
-                    .orOperator(Criteria.where("meta_type").is(MetaType.table.toString()), Criteria.where("meta_type").is(MetaType.collection.toString()))));
-         if (CollectionUtils.isNotEmpty(metadataInstancesTable)) {
-                metadataInstancesTable.forEach(singleTable -> {
-                    Map collectionMap = new HashMap();
-                    collectionMap.put("id", singleTable.getId().toString());
-                    collectionMap.put("name", singleTable.getOriginalName());
-                    collections.add(collectionMap);
-                });
-            }*/
-        }
         TableListVo tableListVo = BeanUtil.copyProperties(metadataInstancesDto, TableListVo.class);
-//        tableListVo.setCollections(collections);
         return tableListVo;
     }
 
@@ -2027,20 +2025,20 @@ public Page<TapTable> getTapTable(Filter filter, UserDetail loginUser) {
     public Page<TapTable> getTapTable(DatabaseNode node, UserDetail loginUser) {
         Filter filter = new Filter();
         filter.setWhere(new Where()
-                .and("source.id", node.getConnectionId())
-                .and("meta_type", MetaType.table)
-                .and("sourceType", SourceTypeEnum.SOURCE)
+                .and(SOURCE_ID_WITHOUT_UNDERLINE, node.getConnectionId())
+                .and(META_TYPE, MetaType.table)
+                .and(SOURCE_TYPE, SourceTypeEnum.SOURCE)
         );
 
         DataSourceConnectionDto dataSource = dataSourceService.findById(toObjectId(node.getConnectionId()));
         if ("expression".equals(node.getMigrateTableSelectType())) {
-            filter.getWhere().and("original_name", new Document("$regex", node.getTableExpression()));
+            filter.getWhere().and(ORIGINAL_NAME, new Document("$regex", node.getTableExpression()));
         } else {
             List<String> qualifiedNames = new ArrayList<>();
             for (String tableName : node.getTableNames()) {
                 qualifiedNames.add(MetaDataBuilderUtils.generateQualifiedName(MetaType.table.name(), dataSource, tableName));
             }
-            filter.getWhere().and("qualified_name", new Document("$in", qualifiedNames));
+            filter.getWhere().and(QUALIFIED_NAME, new Document("$in", qualifiedNames));
         }
 
 
@@ -2140,12 +2138,12 @@ public void linkLogic(List<MetadataInstancesDto> metadataInstancesDtos, UserDeta
             List<MetadataInstancesDto> updateMetadatas = new ArrayList<>();
             for (MetadataInstancesDto metadataInstancesDto : metadataInstancesDtos) {
                 //查询得到所有的关联的逻辑模型表
-                Criteria criteria = Criteria.where("meta_type").is(metadataInstancesDto.getMetaType()).and("original_name").is(metadataInstancesDto.getOriginalName())
-                        .and("source._id").is(metadataInstancesDto.getSource().get_id())
-                        .and("is_deleted").ne(true).and("sourceType").is(SourceTypeEnum.VIRTUAL.name());
+                Criteria criteria = Criteria.where(META_TYPE).is(metadataInstancesDto.getMetaType()).and(ORIGINAL_NAME).is(metadataInstancesDto.getOriginalName())
+                        .and(SOURCE_ID).is(metadataInstancesDto.getSource().get_id())
+                        .and(IS_DELETED).ne(true).and(SOURCE_TYPE).is(SourceTypeEnum.VIRTUAL.name());
 
                 if (StringUtils.isNotBlank(taskId)) {
-                    criteria.and("taskId").is(taskId);
+                    criteria.and(TASK_ID).is(taskId);
                 }
                 Query query = new Query(criteria);
                 List<MetadataInstancesDto> taskMetadatas = findAllDto(query, user);
@@ -2157,7 +2155,7 @@ public void linkLogic(List<MetadataInstancesDto> metadataInstancesDtos, UserDeta
 
                     //对于正在运行中的任务的模型，不需要做下面的合并物理表的操作。
                     //下面这个过滤的逻辑，可能存在任务刚好点启动的时候，会被漏掉
-                    Criteria criteriaTask = Criteria.where("_id").in(taskIds).and("is_deleted").ne(true);
+                    Criteria criteriaTask = Criteria.where("_id").in(taskIds).and(IS_DELETED).ne(true);
                     if (StringUtils.isBlank(taskId)) {
                         criteriaTask.and("status").in(TaskDto.STATUS_EDIT, TaskDto.STATUS_WAIT_START);
                     }
@@ -2205,7 +2203,7 @@ public void linkLogic(List<MetadataInstancesDto> metadataInstancesDtos, UserDeta
 
 
     public void deleteTaskMetadata(String taskId, UserDetail user) {
-        Criteria criteria = Criteria.where("taskId").is(taskId);
+        Criteria criteria = Criteria.where(TASK_ID).is(taskId);
 
         Query query = new Query(criteria);
         deleteAll(query, user);
@@ -2236,27 +2234,27 @@ public Map<String, TapType> dataType2TapType(DataType2TapTypeDto dto, UserDetail
     }
 
     public boolean checkTableExist(String connectionId, String tableName, UserDetail user) {
-        Criteria criteria = Criteria.where("original_name").is(tableName)
-                .and("is_deleted").ne(true)
-                .and("source._id").is(connectionId)
-                .and("sourceType").is(SourceTypeEnum.SOURCE.name())
-                .and("taskId").exists(false);
+        Criteria criteria = Criteria.where(ORIGINAL_NAME).is(tableName)
+                .and(IS_DELETED).ne(true)
+                .and("SOURCE_ID").is(connectionId)
+                .and(SOURCE_TYPE).is(SourceTypeEnum.SOURCE.name())
+                .and(TASK_ID).exists(false);
         Query query = new Query(criteria);
         long count = count(query, user);
         return count > 0;
     }
 
     public void deleteLogicModel(String taskId, String nodeId) {
-        Criteria criteria = Criteria.where("taskId").is(taskId).and("nodeId").is(nodeId);
+        Criteria criteria = Criteria.where(TASK_ID).is(taskId).and(NODE_ID).is(nodeId);
         Query query = new Query(criteria);
         deleteAll(query);
     }
 
     public long countUpdateExNum(String nodeId) {
         Criteria criteria = Criteria
-                .where("is_deleted").ne(true)
-                .and("nodeId").is(nodeId)
-                .and("sourceType").is(SourceTypeEnum.VIRTUAL)
+                .where(IS_DELETED).ne(true)
+                .and(NODE_ID).is(nodeId)
+                .and(SOURCE_TYPE).is(SourceTypeEnum.VIRTUAL)
                 .and("hasPrimaryKey").is(false)
                 .and("hasUnionIndex").is(false)
                 .and("hasUpdateField").is(false);
@@ -2265,25 +2263,25 @@ public long countUpdateExNum(String nodeId) {
 
     public long countTransformExNum(String nodeId) {
         Criteria criteria = Criteria
-                .where("is_deleted").ne(true)
-                .and("nodeId").is(nodeId)
-                .and("sourceType").is(SourceTypeEnum.VIRTUAL)
+                .where(IS_DELETED).ne(true)
+                .and(NODE_ID).is(nodeId)
+                .and(SOURCE_TYPE).is(SourceTypeEnum.VIRTUAL)
                 .and("resultItems").is(true);
         return count(Query.query(criteria));
     }
 
     public long countTotalNum(String nodeId) {
         Criteria criteria = Criteria
-                .where("is_deleted").ne(true)
-                .and("nodeId").is(nodeId)
-                .and("sourceType").is(SourceTypeEnum.VIRTUAL);
+                .where(IS_DELETED).ne(true)
+                .and(NODE_ID).is(nodeId)
+                .and(SOURCE_TYPE).is(SourceTypeEnum.VIRTUAL);
         return count(Query.query(criteria));
     }
 
 
     public void updateTableDesc(MetadataInstancesDto metadataInstances,UserDetail userDetail){
         if(org.springframework.util.StringUtils.isEmpty(metadataInstances.getId())){
-            throw new BizException("IllegalArgument", "Id");
+            throw new BizException(ILLEGAL_ARGUMENT, "Id");
         }
 
         Criteria criteria = Criteria.where("_id").is(metadataInstances.getId());
@@ -2294,7 +2292,7 @@ public void updateTableDesc(MetadataInstancesDto metadataInstances,UserDetail us
 
     public void updateTableFieldDesc(String id, DiscoveryFieldDto discoveryFieldDto,UserDetail userDetail){
         if(org.springframework.util.StringUtils.isEmpty(id)){
-            throw new BizException("IllegalArgument", "Id");
+            throw new BizException(ILLEGAL_ARGUMENT, "Id");
         }
         Criteria criteria = Criteria.where("_id").is(MongoUtils.toObjectId(id)).and("fields.id").is(discoveryFieldDto.getId());
         Query query = new Query(criteria);
@@ -2304,7 +2302,7 @@ public void updateTableFieldDesc(String id, DiscoveryFieldDto discoveryFieldDto,
 
     public MetadataInstancesDto importEntity(MetadataInstancesDto metadataInstancesDto, UserDetail userDetail) {
 
-        Criteria criteria = Criteria.where("qualified_name").is(metadataInstancesDto.getQualifiedName());
+        Criteria criteria = Criteria.where(QUALIFIED_NAME).is(metadataInstancesDto.getQualifiedName());
         Query query = new Query(criteria);
         upsert(query, metadataInstancesDto, userDetail);
         return metadataInstancesDto;
@@ -2313,14 +2311,14 @@ public MetadataInstancesDto importEntity(MetadataInstancesDto metadataInstancesD
     }
 
     public void updateTableCustomDesc(String qualifiedName, String customDesc, UserDetail user) {
-        Criteria criteria = Criteria.where("qualified_name").is(qualifiedName);
+        Criteria criteria = Criteria.where(QUALIFIED_NAME).is(qualifiedName);
         update(new Query(criteria), Update.update("customDesc", customDesc), user);
     }
 
     public void updateFieldCustomDesc(String qualifiedName, Map<String, String> fieldCustomDescMap, UserDetail user) {
-        Criteria criteria = Criteria.where("qualified_name").is(qualifiedName);
+        Criteria criteria = Criteria.where(QUALIFIED_NAME).is(qualifiedName);
         Query query = new Query(criteria);
-        query.fields().include("fields");
+        query.fields().include(FIELDS);
         MetadataInstancesDto dto = findOne(query, user);
         List<Field> fields = dto.getFields();
         if (CollectionUtils.isNotEmpty(fields)) {
@@ -2329,7 +2327,7 @@ public void updateFieldCustomDesc(String qualifiedName, Map<String, String> fiel
                 field.setDescription(customDesc);
             }
         }
-        update(new Query(criteria), Update.update("fields", fields), user);
+        update(new Query(criteria), Update.update(FIELDS, fields), user);
 
     }
 
@@ -2467,7 +2465,7 @@ public Long findDatabaseMetadataInstanceLastUpdate(String connectionId, UserDeta
             throw new BizException("connection is null");
         }
         String qualifiedName = MetaDataBuilderUtils.generateQualifiedName(MetaType.database.name(), connectionDto, null);
-        Criteria criteria = Criteria.where("qualified_name").is(qualifiedName);
+        Criteria criteria = Criteria.where(QUALIFIED_NAME).is(qualifiedName);
         Query query = new Query(criteria);
         query.fields().include("lastUpdate");
         MetadataInstancesDto metedata = findOne(query);
diff --git a/manager/tm/src/main/java/com/tapdata/tm/modules/controller/ModulesController.java b/manager/tm/src/main/java/com/tapdata/tm/modules/controller/ModulesController.java
index d19810f2f6..96b159bb04 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/modules/controller/ModulesController.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/modules/controller/ModulesController.java
@@ -5,6 +5,8 @@
 import com.tapdata.tm.discovery.bean.DiscoveryFieldDto;
 import com.tapdata.tm.metadatadefinition.param.BatchUpdateParam;
 import com.tapdata.tm.metadatadefinition.service.MetadataDefinitionService;
+import com.tapdata.tm.modules.dto.ModulesPermissionsDto;
+import com.tapdata.tm.modules.dto.ModulesTagsDto;
 import com.tapdata.tm.modules.dto.Param;
 import com.tapdata.tm.modules.vo.ModulesDetailVo;
 import com.tapdata.tm.modules.dto.ModulesDto;
@@ -61,6 +63,20 @@ public ResponseMessage<ModulesDto> update(@RequestBody ModulesDto module) {
     return success(modulesService.updateModuleById(module, getLoginUser()));
   }
 
+  @Operation(summary = "更新权限")
+  @PatchMapping("/updatePermissions")
+  public ResponseMessage<Void> updatePermissions(@RequestBody ModulesPermissionsDto permissions) {
+    modulesService.updatePermissions(permissions, getLoginUser());
+    return success();
+  }
+
+  @Operation(summary = "更新所属应用")
+  @PatchMapping("/updateTags")
+  public ResponseMessage<Void> updateTags(@RequestBody ModulesTagsDto modulesTagsDto) {
+    modulesService.updateTags(modulesTagsDto, getLoginUser());
+    return success();
+  }
+
 
   /**
    * @author derin
diff --git a/manager/tm/src/main/java/com/tapdata/tm/modules/dto/ModulesPermissionsDto.java b/manager/tm/src/main/java/com/tapdata/tm/modules/dto/ModulesPermissionsDto.java
new file mode 100644
index 0000000000..f3a51077f0
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/modules/dto/ModulesPermissionsDto.java
@@ -0,0 +1,11 @@
+package com.tapdata.tm.modules.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ModulesPermissionsDto {
+    private String moduleId;
+    private List<String> acl;
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/modules/dto/ModulesTagsDto.java b/manager/tm/src/main/java/com/tapdata/tm/modules/dto/ModulesTagsDto.java
new file mode 100644
index 0000000000..c388e8d29e
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/modules/dto/ModulesTagsDto.java
@@ -0,0 +1,12 @@
+package com.tapdata.tm.modules.dto;
+
+import com.tapdata.tm.commons.schema.Tag;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ModulesTagsDto {
+    private String moduleId;
+    private List<Tag> listtags;
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/modules/service/ModulesService.java b/manager/tm/src/main/java/com/tapdata/tm/modules/service/ModulesService.java
index 24167c4d93..4ce19c61dd 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/modules/service/ModulesService.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/modules/service/ModulesService.java
@@ -98,9 +98,15 @@ public class ModulesService extends BaseService<ModulesDto, ModulesEntity, Objec
     public ModulesService(@NonNull ModulesRepository repository) {
         super(repository, ModulesDto.class, ModulesEntity.class);
     }
-
+    @Override
     protected void beforeSave(ModulesDto modules, UserDetail user) {
-
+        if(CollectionUtils.isNotEmpty(modules.getPaths())){
+            modules.getPaths().forEach(path -> {
+                if(CollectionUtils.isNotEmpty(path.getFields())){
+                    path.setFields(path.getFields().stream().filter(Objects::nonNull).collect(Collectors.toList()));
+                }
+            });
+        }
     }
 
 
@@ -215,18 +221,6 @@ public ModulesDto updateModuleById(ModulesDto modulesDto, UserDetail userDetail)
         return super.upsertByWhere(where, modulesDto, userDetail);
     }
 
-    public Map batchUpdateListtags(AttrsParam attrsParam, UserDetail userDetail) {
-        List ids = (List) attrsParam.getAttrs().get("ids");
-        List listTags = (List) attrsParam.getAttrs().get("listtags");
-        Query query = Query.query(Criteria.where("id").in(ids));
-        Update update = new Update();
-        update.set("listtags", listTags);
-        UpdateResult updateResult = update(query, update);
-        Map retMap = new HashMap();
-        retMap.put("rows", updateResult.getModifiedCount());
-        retMap.put("failed_ids", new ArrayList());
-        return retMap;
-    }
 
 
     /**
@@ -1457,4 +1451,18 @@ public void saveWord(HttpServletResponse response, List<String> ids,String ip,Us
         );
         fileService.viewWord(template,response,fileName.get());
     }
+
+    public void updatePermissions(ModulesPermissionsDto permissions,UserDetail userDetail){
+        if(CollectionUtils.isEmpty(permissions.getAcl()))throw new BizException("Modules.Permission.Scope.Null");
+        Update update =  new Update();
+        update.set("paths.$[].acl",permissions.getAcl());
+        updateById(permissions.getModuleId(), update, userDetail);
+    }
+
+    public void updateTags(ModulesTagsDto modulesTagsDto,UserDetail userDetail){
+        if(CollectionUtils.isEmpty(modulesTagsDto.getListtags()))throw new BizException("Modules.Tags.Null");
+        Update update =  new Update();
+        update.set("listtags",modulesTagsDto.getListtags());
+        updateById(modulesTagsDto.getModuleId(), update, userDetail);
+    }
 }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/roleMapping/controller/RoleMappingController.java b/manager/tm/src/main/java/com/tapdata/tm/roleMapping/controller/RoleMappingController.java
index 14f5238559..0c926b93af 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/roleMapping/controller/RoleMappingController.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/roleMapping/controller/RoleMappingController.java
@@ -30,6 +30,7 @@
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
 import org.bson.Document;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
 @RestController
@@ -201,7 +202,7 @@ public ResponseMessage<RoleMappingDto> replaceById2(@PathVariable("id") String i
     @Operation(summary = "Delete a model instance by {{id}} from the data source")
     @DeleteMapping("{id}")
     public ResponseMessage<Void> delete(@PathVariable("id") String id) {
-        roleMappingService.deleteById(MongoUtils.toObjectId(id));
+        roleMappingService.removeRoleFromUser(id);
         return success();
     }
 
diff --git a/manager/tm/src/main/java/com/tapdata/tm/schedule/TaskResetSchedule.java b/manager/tm/src/main/java/com/tapdata/tm/schedule/TaskResetSchedule.java
index 76b8624735..4d84faf77a 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/schedule/TaskResetSchedule.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/schedule/TaskResetSchedule.java
@@ -38,7 +38,7 @@ public class TaskResetSchedule {
     @Autowired
     private UserService userService;
 
-    @Value("${task.reset.times: 2}")
+    @Value("${task.reset.times: 3}")
     private int resetAllTimes;
 
 
@@ -48,6 +48,14 @@ public class TaskResetSchedule {
     @Autowired
     private StateMachineService stateMachineService;
 
+    private static final String IS_DELETED = "is_deleted";
+
+    private static final String STATUS = "status";
+
+    private static final String RESET_TIMES = "resetTimes";
+
+    private static final String ID = "_id";
+
 
     @Scheduled(fixedDelay = 90000)
     @SchedulerLock(name ="checkTaskReset", lockAtMostFor = "120s", lockAtLeastFor = "60s")
@@ -189,6 +197,7 @@ public void resetRetry() {
             Query query = new Query(criteria);
             List<TaskDto> taskDtos = taskService.findAll(query);
             if (CollectionUtils.isEmpty(taskDtos)) {
+                checkMoreThanResetTimes();
                 return;
             }
 
@@ -223,6 +232,32 @@ public void resetRetry() {
         }
     }
 
+    protected void checkMoreThanResetTimes(){
+        Criteria criteria = Criteria.where(STATUS).in(TaskDto.STATUS_RENEW_FAILED, TaskDto.STATUS_DELETE_FAILED).and(IS_DELETED).ne(true)
+                .orOperator(Criteria.where(RESET_TIMES).exists(false), Criteria.where(RESET_TIMES).gte(resetAllTimes));
+        Query query = new Query(criteria);
+        List<TaskDto> taskDtos = taskService.findAll(query);
+
+        if (CollectionUtils.isEmpty(taskDtos)) {
+            return;
+        }
+
+        List<String> userLists = taskDtos.stream().map(BaseDto::getUserId).collect(Collectors.toList());
+
+        Map<String, UserDetail> userDetailMap = userService.getUserMapByIdList(userLists);
+
+        for (TaskDto taskDto : taskDtos) {
+            UserDetail user = userDetailMap.get(taskDto.getUserId());
+            if (user == null) {
+                continue;
+            }
+            Update update = Update.update(IS_DELETED,true);
+            Query queryTask = new Query(Criteria.where(ID).is(taskDto.getId()));
+            taskService.update(queryTask, update);
+        }
+
+    }
+
     public void cleanTaskResetTimes() {
         try {
             Criteria taskCriteria = Criteria.where("status").nin(TaskDto.STATUS_RENEW_FAILED, TaskDto.STATUS_DELETE_FAILED, TaskDto.STATUS_RENEWING, TaskDto.STATUS_DELETING)
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskRecordService.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskRecordService.java
index 0f06565685..5aecb4527e 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskRecordService.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskRecordService.java
@@ -15,4 +15,6 @@ public interface TaskRecordService {
     Page<TaskRecordListVo> queryRecords(TaskRecordDto dto);
 
     TaskDto queryTask(String taskRecordId, String userId);
+
+    void cleanTaskRecord(String taskId);
 }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java
index 42eee0df6d..a59e2fba3c 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java
@@ -25,10 +25,16 @@
 import com.tapdata.tm.base.dto.ResponseMessage;
 import com.tapdata.tm.base.dto.TmPageable;
 import com.tapdata.tm.base.dto.Where;
-import com.tapdata.tm.monitor.param.MeasurementQueryParam;
+import com.tapdata.tm.commons.dag.logCollector.LogCollecotrConnConfig;
+import com.tapdata.tm.commons.dag.logCollector.LogCollectorNode;
+import com.tapdata.tm.commons.schema.bean.ResponseBody;
+import com.tapdata.tm.commons.schema.bean.ValidateDetail;
 import com.tapdata.tm.monitor.service.BatchService;
+import com.tapdata.tm.shareCdcTableMapping.service.ShareCdcTableMappingService;
 import com.tapdata.tm.task.bean.*;
 import com.tapdata.tm.task.vo.*;
+import io.tapdata.pdk.apis.entity.Capability;
+import io.tapdata.pdk.apis.entity.TestItem;
 import io.tapdata.pdk.core.api.PDKIntegration;
 import org.apache.commons.io.FileUtils;
 import org.mockito.Mockito;
@@ -107,7 +113,6 @@
 import com.tapdata.tm.monitor.entity.MeasurementEntity;
 import com.tapdata.tm.monitor.param.IdParam;
 import com.tapdata.tm.monitor.service.MeasurementServiceV2;
-import com.tapdata.tm.monitor.service.impl.BatchServiceImpl;
 import com.tapdata.tm.monitoringlogs.param.MonitoringLogQueryParam;
 import com.tapdata.tm.monitoringlogs.service.MonitoringLogsService;
 import com.tapdata.tm.permissions.DataPermissionHelper;
@@ -158,13 +163,10 @@
 import java.lang.management.ThreadMXBean;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
 import io.tapdata.entity.utils.InstanceFactory;
-import io.tapdata.entity.utils.JsonParser;
 import io.tapdata.exception.TapCodeException;
-import io.tapdata.modules.api.net.error.NetErrors;
 import io.tapdata.modules.api.net.service.EngineMessageExecutionService;
 import io.tapdata.pdk.apis.entity.message.ServiceCaller;
 import lombok.AllArgsConstructor;
@@ -195,8 +197,6 @@
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.servlet.AsyncContext;
@@ -213,7 +213,6 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
@@ -373,6 +372,7 @@ private static String getTableName() {
     private ChartViewService chartViewService;
     private UserDataReportService userDataReportService;
     private BatchService batchService;
+    private ShareCdcTableMappingService shareCdcTableMappingService;
 
     public TaskServiceImpl(@NonNull TaskRepository repository) {
         super(repository);
@@ -536,6 +536,13 @@ private void setDefault(TaskDto taskDto) {
     protected void beforeSave(TaskDto task, UserDetail user) {
         //setDefault(task);
 
+        if (null != task.getId()) {
+            TaskDto existsTask = findByTaskId(task.getId(), "oldVersionTimezone");
+            if (null != existsTask) {
+                task.setOldVersionTimezone(existsTask.getOldVersionTimezone());
+            }
+        }
+
         DAG dag = task.getDag();
         if (dag == null) {
             return;
@@ -789,9 +796,54 @@ public TaskDto confirmById(TaskDto taskDto, UserDetail user, boolean confirm) {
 
         checkShareCdcStatus(taskDto, user);
 
+        checkSourceCdcSupport(taskDto);
+
         return confirmById(taskDto, user, confirm, false);
     }
 
+    protected void checkSourceCdcSupport(TaskDto taskDto) {
+
+        if (taskDto.getType().contains("cdc")) {
+            List<Node> nodes = taskDto.getDag().getSourceNodes();
+            if (CollectionUtils.isEmpty(nodes)) {
+                return;
+            }
+            List<String> alreadyRequestConnectionId = new ArrayList();
+            List<String> noSupportConnectionName = new ArrayList<>();
+            nodes.forEach(node -> {
+                if(node instanceof DataParentNode) {
+                    DataParentNode dataParentNode = (DataParentNode) node;
+                    if (alreadyRequestConnectionId.contains(dataParentNode.getConnectionId())) {
+                        return;
+                    }
+                    alreadyRequestConnectionId.add(dataParentNode.getConnectionId());
+                    DataSourceConnectionDto connectionDto = dataSourceService.findByIdByCheck(MongoUtils.toObjectId(dataParentNode.getConnectionId()));
+                    if (Lists.of("Dummy").contains(connectionDto.getDatabase_type())) {
+                        return;
+                    }
+                    List<String> capList = connectionDto.getCapabilities().stream().map(Capability::getId).collect(Collectors.toList());
+                    if (capList.contains("stream_read_function")) {
+                        ResponseBody responseBody = connectionDto.getResponse_body();
+                        if (responseBody != null) {
+                            List<ValidateDetail> validateDetails = responseBody.getValidateDetails();
+                            Map<String, ValidateDetail> map = new HashMap<>();
+                            if (org.apache.commons.collections.CollectionUtils.isNotEmpty(validateDetails)) {
+                                map = validateDetails.stream().collect(Collectors.toMap(ValidateDetail::getShowMsg, Function.identity(), (key1, key2) -> key2));
+                            }
+                            if (!map.containsKey(TestItem.ITEM_READ_LOG) || !"passed".equals(map.get(TestItem.ITEM_READ_LOG).getStatus())) {
+                                noSupportConnectionName.add(connectionDto.getName());
+                            }
+                        }
+                    }
+                }
+            });
+             if(CollectionUtils.isNotEmpty(noSupportConnectionName)){
+                 throw new BizException("source.setting.check.cdc",String.join(",",noSupportConnectionName));
+             }
+        }
+    }
+
+
     protected void checkShareCdcStatus(TaskDto taskDto,UserDetail user){
         if( null != taskDto.getShareCdcEnable() && !taskDto.getShareCdcEnable() &&  null != taskDto.getShareCdcStop() && StringUtils.isNotBlank(taskDto.getShareCdcStopMessage())){
             Update set = new Update();
@@ -1035,7 +1087,6 @@ public TaskDto remove(ObjectId id, UserDetail user) {
             }
 
         }
-        //afterRemove(taskDto, user);
 
         return taskDto;
     }
@@ -1066,6 +1117,7 @@ public void afterRemove(TaskDto taskDto, UserDetail user) {
 
         //删除收集的对象
         taskCollectionObjService.deleteById(taskDto.getId());
+        taskRecordService.cleanTaskRecord(taskDto.getId().toHexString());
     }
 
     /**
@@ -1777,13 +1829,12 @@ protected Page<TaskDto> findDataCopyList(Filter filter, UserDetail userDetail) {
         tmPageable.setPage(page);
         tmPageable.setSize(filter.getLimit());
 
-        String order = filter.getOrder() == null ? "createTime DESC" : String.valueOf(filter.getOrder());
-        String sortKey = order.contains(CURRENT_EVENT_TIMESTAMP) ? CURRENT_EVENT_TIMESTAMP : CREATE_TIME;
-        if (order.contains("ASC")) {
-            tmPageable.setSort(Sort.by(sortKey).ascending());
-        } else {
-            tmPageable.setSort(Sort.by(sortKey).descending());
-        }
+        tmPageable.setSort(Optional
+            .ofNullable(filter.getOrder())
+            .map(String::valueOf)
+            .map(QueryUtil::parseOrder)
+            .orElse(Sort.by(CREATE_TIME).descending())
+        );
 
         long total = repository.getMongoOperations().count(query, TaskEntity.class);
         List<TaskEntity> taskEntityList = repository.getMongoOperations().find(query.with(tmPageable), TaskEntity.class);
@@ -3872,10 +3923,14 @@ public boolean findAgent(TaskDto taskDto, UserDetail user) {
 
             List<String> processIds = availableAgent.stream().map(Worker::getProcessId).collect(Collectors.toList());
             String agentId = null;
-            for (String p : accessNodeProcessIdList) {
-                if (processIds.contains(p)) {
-                    agentId = p;
-                    break;
+            if(StringUtils.isNotEmpty(taskDto.getPriorityProcessId()) && processIds.contains(taskDto.getPriorityProcessId())){
+                agentId = taskDto.getPriorityProcessId();
+            }else{
+                for (String p : accessNodeProcessIdList) {
+                    if (processIds.contains(p)) {
+                        agentId = p;
+                        break;
+                    }
                 }
             }
             if (StringUtils.isBlank(agentId)) {
@@ -4012,6 +4067,7 @@ public void start(TaskDto taskDto, UserDetail user, String startFlag) {
             log.warn("heartbeat task current status not allow to start, task = {}, status = {}, please restore the task manually.", taskDto.getName(), taskDto.getStatus());
             return;
         }
+        checkHeartTableInLogCollectorTaskTable(taskDto, user);
         //校验当前状态是否允许启动。
         if (!TaskOpStatusEnum.to_start_status.v().contains(taskDto.getStatus())) {
             log.warn("task current status not allow to start, task = {}, status = {}", taskDto.getName(), taskDto.getStatus());
@@ -4045,6 +4101,45 @@ public void start(TaskDto taskDto, UserDetail user, String startFlag) {
         }
     }
 
+    protected void checkHeartTableInLogCollectorTaskTable(TaskDto taskDto, UserDetail user) {
+        if (TaskDto.SYNC_TYPE_LOG_COLLECTOR.equals(taskDto.getSyncType())) {
+            Node logCollectorTaskSourceNode = taskDto.getDag().getSourceNodes().get(0);
+            if ((logCollectorTaskSourceNode instanceof LogCollectorNode)) {
+                LogCollectorNode logCollectorNode = (LogCollectorNode) logCollectorTaskSourceNode;
+                List<String> connectionIds = logCollectorNode.getConnectionIds();
+                Set<String> tableNameSet = new HashSet<>(logCollectorNode.getTableNames());
+                Map<String, LogCollecotrConnConfig> logCollectorConnConfigs = logCollectorNode.getLogCollectorConnConfigs();
+                List<DataSourceConnectionDto> allDataSourceConnection = dataSourceService.findAllByIds(connectionIds);
+                boolean hasUpdate = addHeartBeatTable2LogCollector(allDataSourceConnection, logCollectorConnConfigs, tableNameSet);
+                if (hasUpdate) {
+                    List<String> finalTableNames = new ArrayList<>(tableNameSet);
+                    logCollectorNode.setTableNames(finalTableNames);
+                    shareCdcTableMappingService.genShareCdcTableMappingsByLogCollectorTask(taskDto, false, user);
+                    updateById(taskDto, user);
+                }
+            }
+        }
+    }
+
+    protected boolean addHeartBeatTable2LogCollector(List<DataSourceConnectionDto> allDataSourceConnection, Map<String, LogCollecotrConnConfig> logCollectorConnConfigs, Set<String> tableNameSet) {
+        boolean updateConfig = false;
+        int beforeTableNamesSize = tableNameSet.size();
+        for (DataSourceConnectionDto dataSourceConnectionDto : allDataSourceConnection) {
+            if (Boolean.TRUE.equals(dataSourceConnectionDto.getHeartbeatEnable())) {
+                if (MapUtils.isNotEmpty(logCollectorConnConfigs) && null != logCollectorConnConfigs.get(dataSourceConnectionDto.getId().toHexString())) {
+                    LogCollecotrConnConfig logCollecotrConnConfig = logCollectorConnConfigs.get(dataSourceConnectionDto.getId().toHexString());
+                    List<String> tableNames = logCollecotrConnConfig.getTableNames();
+                    if (tableNames.stream().noneMatch(ConnHeartbeatUtils.TABLE_NAME::equals)) {
+                        logCollecotrConnConfig.getTableNames().add(ConnHeartbeatUtils.TABLE_NAME);
+                        updateConfig = true;
+                    }
+                }
+                tableNameSet.add(ConnHeartbeatUtils.TABLE_NAME);
+            }
+        }
+        return updateConfig || beforeTableNamesSize < tableNameSet.size();
+    }
+
 
     /**
      * @see com.tapdata.tm.statemachine.enums.DataFlowEvent#START
@@ -4796,6 +4891,7 @@ public List<TaskDto> findHeartbeatByConnectionId(String connectionId, String...
         Query query = Query.query(Criteria.where(DAG_NODES_CONNECTION_ID).is(connectionId)
                 .and(SYNC_TYPE).is(TaskDto.SYNC_TYPE_CONN_HEARTBEAT)
                 .and(IS_DELETED).is(false)
+                .and(STATUS).nin(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED)
         );
         if (null != includeFields && includeFields.length > 0) {
             query.fields().include(includeFields);
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/TransformSchemaService.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/TransformSchemaService.java
index d96ec41b9a..82b1aaf432 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/TransformSchemaService.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/TransformSchemaService.java
@@ -299,7 +299,7 @@ public List<SchemaTransformerResult> getSchemaTransformResult(String nodeId) {
 
 
         TransformerWsMessageDto transformerWsMessageDto = new TransformerWsMessageDto(taskDto, options, metadataList, dataSourceMap, definitionDtoMap, user.getUserId()
-                , user.getUsername(), metadataTransformerDtoMap, MessageType.TRANSFORMER.getType());
+                , user.getUsername(), metadataTransformerDtoMap, MessageType.TRANSFORMER.getType(),getTableNodeSameMetadataInstances(taskDto,user));
         return transformerWsMessageDto;
 
     }
@@ -334,6 +334,14 @@ protected void getLogCollectorMetadataInstancesDto(DAG dag,UserDetail user,List<
         });
     }
 
+    protected Map<String,List<String>> getTableNodeSameMetadataInstances(TaskDto taskDto,UserDetail user){
+
+        return taskDto.getDag().getSourceNodes().stream().filter(node -> node instanceof TableNode)
+                .collect(Collectors.groupingBy(node -> {
+                    return metadataInstancesService.getQualifiedNameByNodeId(node,user,null,null,taskDto.getId().toHexString());
+                },Collectors.mapping(Node::getId, Collectors.toList())));
+    }
+
     public void transformSchema(TaskDto taskDto, UserDetail user) {
         transformSchema(taskDto, user, true);
     }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/batchin/ParseRelMigFile.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/batchin/ParseRelMigFile.java
index b17089b59c..bda934fd31 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/batchin/ParseRelMigFile.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/batchin/ParseRelMigFile.java
@@ -622,8 +622,8 @@ public Map<String, Object> scanOneChildren(GenericPropertiesParam param, String
             case KeyWords.EMBEDDED_DOCUMENT_ARRAY:
                 final String embeddedPath = String.valueOf(setting.get(KeyWords.EMBEDDED_PATH));
                 targetPath = KeyWords.EMPTY.equals(parentTargetPath) ?
-                        String.format(KeyWords.FROMAT, parentTargetPath, embeddedPath)
-                        : embeddedPath;
+                        embeddedPath
+                        : String.format(KeyWords.FROMAT, parentTargetPath, embeddedPath);
                 mergeType = KeyWords.UPDATE_INTO_ARRAY;
                 Map<String, Object> fields = parseMap(getFromMap(map, KeyWords.FIELDS));
                 scanAllFieldKeys(currentRenameFields, childNode, fields);
@@ -744,9 +744,9 @@ public void replaceRelationShipsKey(Map<String, String> globalIdMap, Map<String,
     }
 
     public Map<String, Object> getTableSchema(Map<String, Object> full, TablePathInfo tableInfo) {
-        Map<String, Object> databasesLayer = parseMap(getFromMap(full, KeyWords.DATABASE));
+        Map<String, Object> databasesLayer = parseMap(getFromMap(full, KeyWords.DATABASES));
         Map<String, Object> currentDatabaseSchema = parseMap(getFromMap(databasesLayer, tableInfo.getDatabase()));
-        Map<String, Object> schemasLayer = parseMap(getFromMap(currentDatabaseSchema, KeyWords.SCHEMA));
+        Map<String, Object> schemasLayer = parseMap(getFromMap(currentDatabaseSchema, KeyWords.SCHEMAS));
         Map<String, Object> currentSchema = parseMap(getFromMap(schemasLayer, tableInfo.getSchema()));
         return parseMap(getFromMap(currentSchema, KeyWords.TABLES));
     }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/batchin/constant/KeyWords.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/batchin/constant/KeyWords.java
index 17b82948f7..d56bb78790 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/batchin/constant/KeyWords.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/batchin/constant/KeyWords.java
@@ -6,6 +6,8 @@ public class KeyWords {
     public static final String VERSION = "version";
     public static final String DATABASE = "database";
     public static final String SCHEMA = "schema";
+    public static final String DATABASES = "databases";
+    public static final String SCHEMAS = "schemas";
     public static final String TABLE = "table";
     public static final String TABLES = "tables";
     public static final String PROJECT = "project";
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/LdpServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/LdpServiceImpl.java
index fbe9a041ad..1c7dcdd8a8 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/LdpServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/LdpServiceImpl.java
@@ -123,6 +123,10 @@ public class LdpServiceImpl implements LdpService {
 
 	@Autowired
 	private AgentGroupService agentGroupService;
+	private static final String META_TYPE = "meta_type";
+	private static final String TABLE = "table";
+	private static final String TASK_ID = "taskId";
+	private static final String SOURCE_TYPE = "sourceType";
 
     @Override
 	@Lock(value = "user.userId", type = LockType.START_LDP_FDM, expireSeconds = 15)
@@ -668,6 +672,7 @@ private void createLdpMetaByTask(TaskDto task, UserDetail user) {
 				MetadataInstancesDto metadataInstancesDto = buildSourceMeta(conTag, metaData, oldMeta);
 				saveMetaDatas.add(metadataInstancesDto);
 			}
+			cleanLdpMeta(saveMetaDatas,user);
 			metadataInstancesService.bulkUpsetByWhere(saveMetaDatas, user);
 		} else {
 
@@ -722,6 +727,26 @@ private void createLdpMetaByTask(TaskDto task, UserDetail user) {
 
 		}
 	}
+	protected void cleanLdpMeta(List<MetadataInstancesDto> metadataInstancesDtos,UserDetail user){
+		MetadataInstancesDto metadataInstancesDto = metadataInstancesDtos.get(0);
+		String tagId = null;
+		ObjectId source_id = null;
+		if(CollectionUtils.isNotEmpty(metadataInstancesDto.getListtags()) && null != metadataInstancesDto.getSource()){
+			tagId = metadataInstancesDto.getListtags().stream().map(Tag::getId).findFirst().orElse(null);
+			source_id = metadataInstancesDto.getSource().getId();
+		}
+		Set<String> ancestorsNameSet = metadataInstancesDtos.stream().map(MetadataInstancesDto::getAncestorsName).filter(Objects::nonNull).collect(Collectors.toSet());
+		if(StringUtils.isNotBlank(tagId) && null != source_id && CollectionUtils.isNotEmpty(ancestorsNameSet)){
+			Criteria metadataCriteria = Criteria.where(SOURCE_TYPE).is(SourceTypeEnum.SOURCE.name())
+					.and(TASK_ID).exists(false)
+					.and(META_TYPE).is(TABLE)
+					.and("source.id").is(source_id)
+					.and("listtags.id").is(tagId)
+					.and("ancestorsName").in(ancestorsNameSet);
+			metadataInstancesService.deleteAll(new Query(metadataCriteria),user);
+		}
+
+	}
 
 	@Override
 	public Map<String, List<TaskDto>> queryFdmTaskByTags(List<String> tagIds, UserDetail user) {
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskNodeServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskNodeServiceImpl.java
index 57d8c64380..aa6f940dd0 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskNodeServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskNodeServiceImpl.java
@@ -1,19 +1,18 @@
 package com.tapdata.tm.task.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
 import com.google.common.collect.Maps;
 import com.tapdata.tm.agent.service.AgentGroupService;
 import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.base.dto.PageParameter;
 import com.tapdata.tm.base.dto.ResponseMessage;
 import com.tapdata.tm.base.exception.BizException;
 import com.tapdata.tm.commons.dag.*;
 import com.tapdata.tm.commons.dag.logCollector.VirtualTargetNode;
 import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
 import com.tapdata.tm.commons.dag.nodes.TableNode;
-import com.tapdata.tm.commons.dag.process.JsProcessorNode;
-import com.tapdata.tm.commons.dag.process.MigrateFieldRenameProcessorNode;
-import com.tapdata.tm.commons.dag.process.MigrateJsProcessorNode;
-import com.tapdata.tm.commons.dag.process.TableRenameProcessNode;
+import com.tapdata.tm.commons.dag.process.*;
 import com.tapdata.tm.commons.dag.process.script.MigrateScriptProcessNode;
 import com.tapdata.tm.commons.dag.process.script.ScriptProcessNode;
 import com.tapdata.tm.commons.dag.vo.FieldInfo;
@@ -38,10 +37,7 @@
 import com.tapdata.tm.task.utils.CacheUtils;
 import com.tapdata.tm.task.vo.JsResultDto;
 import com.tapdata.tm.task.vo.JsResultVo;
-import com.tapdata.tm.utils.FunctionUtils;
-import com.tapdata.tm.utils.Lists;
-import com.tapdata.tm.utils.MongoUtils;
-import com.tapdata.tm.utils.OEMReplaceUtil;
+import com.tapdata.tm.utils.*;
 import com.tapdata.tm.worker.entity.Worker;
 import com.tapdata.tm.worker.service.WorkerService;
 import io.tapdata.entity.codec.TapCodecsRegistry;
@@ -74,7 +70,6 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
-import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 import static io.tapdata.entity.simplify.TapSimplify.fromJson;
@@ -119,7 +114,7 @@ public Page<MetadataTransformerItemDto> getNodeTableInfo(String taskId, String t
                 return result;
             }
 
-            return getNodeInfoByMigrate(taskId, nodeId, searchTableName, page, pageSize, userDetail, result, dag);
+            return getNodeInfoByMigrate(taskId, nodeId, searchTableName, new PageParameter(page,pageSize), userDetail, result, dag);
         } else if (TaskDto.SYNC_TYPE_SYNC.equals(taskDto.get().getSyncType())) {
 
             List<MetadataInstancesDto> metadataInstancesDtos = metadataInstancesService.findByNodeId(nodeId, userDetail);
@@ -153,64 +148,14 @@ public Page<MetadataTransformerItemDto> getNodeTableInfo(String taskId, String t
         }
     }
 
-    private Page<MetadataTransformerItemDto> getNodeInfoByMigrate(String taskId, String nodeId, String searchTableName, Integer page, Integer pageSize, UserDetail userDetail, Page<MetadataTransformerItemDto> result, DAG dag) {
+    private Page<MetadataTransformerItemDto> getNodeInfoByMigrate(String taskId, String nodeId, String searchTableName, PageParameter pageParameter, UserDetail userDetail, Page<MetadataTransformerItemDto> result, DAG dag) {
         DatabaseNode sourceNode = dag.getSourceNode(nodeId);
         if (Objects.isNull(sourceNode)) {
             return result;
         }
         DatabaseNode targetNode = CollectionUtils.isNotEmpty(dag.getTargetNode()) ? dag.getTargetNode(nodeId) : null;
-        List<String> tableNames = sourceNode.getTableNames();
-        if (StringUtils.equals("expression", sourceNode.getMigrateTableSelectType())) {
-            List<MetadataInstancesDto> metaInstances = metadataInstancesService.findSourceSchemaBySourceId(sourceNode.getConnectionId(), null, userDetail, "original_name");
-//            if (CollectionUtils.isEmpty(metaInstances)) {
-//                metaInstances = metadataInstancesService.findBySourceIdAndTableNameListNeTaskId(sourceNode.getConnectionId(), null, userDetail);
-//            }
-					Function<MetadataInstancesDto, Boolean> filterTableByNoPrimaryKey = Optional
-						.of(NoPrimaryKeyTableSelectType.parse(sourceNode.getNoPrimaryKeyTableSelectType()))
-						.map(type -> {
-							switch (type) {
-								case HasKeys:
-									return (Function<MetadataInstancesDto, Boolean>) metadataInstancesDto -> {
-										if (null != metadataInstancesDto.getFields()) {
-											for (Field field : metadataInstancesDto.getFields()) {
-												if (Boolean.TRUE.equals(field.getPrimaryKey())) return false;
-											}
-										}
-										return true;
-									};
-								case NoKeys:
-									return (Function<MetadataInstancesDto, Boolean>) metadataInstancesDto -> {
-										if (null != metadataInstancesDto.getFields()) {
-											for (Field field : metadataInstancesDto.getFields()) {
-												if (Boolean.TRUE.equals(field.getPrimaryKey())) return true;
-											}
-										}
-										return false;
-									};
-								default:
-							}
-							return null;
-						}).orElse(metadataInstancesDto -> false);
-
-            tableNames = metaInstances.stream()
-							.map(metadataInstancesDto -> {
-								if (filterTableByNoPrimaryKey.apply(metadataInstancesDto)) {
-									return null;
-								}
-								return metadataInstancesDto.getOriginalName();
-							})
-                    .filter(originalName -> {
-											if (null == originalName) {
-												return false;
-											} else if (StringUtils.isEmpty(sourceNode.getTableExpression())) {
-                            return false;
-                        } else {
-                            return Pattern.matches(sourceNode.getTableExpression(), originalName);
-                        }
-                    })
-                    .collect(Collectors.toList());
-        }
-
+        List<String> tableNames = getMigrateTableNames(sourceNode,userDetail);
+        checkUnionProcess(dag,nodeId, tableNames);
         List<String> currentTableList = Lists.newArrayList();
         if (StringUtils.isNotBlank(searchTableName)) {
             currentTableList.add(searchTableName);
@@ -221,7 +166,7 @@ private Page<MetadataTransformerItemDto> getNodeInfoByMigrate(String taskId, Str
             return result;
         }
 
-        currentTableList = ListUtils.partition(tableNames, pageSize).get(page - 1);
+        currentTableList = ListUtils.partition(tableNames, pageParameter.getPageSize()).get(pageParameter.getPage() - 1);
 
         DataSourceConnectionDto targetDataSource;
         if (targetNode != null) {
@@ -300,7 +245,7 @@ private Page<MetadataTransformerItemDto> getMetaByJsNode(String nodeId, Page<Met
 
         Map<String, String> sourceMetaMap = new HashMap<>();
         if (sourceNode != null && StringUtils.isNotBlank(sourceNode.getId())) {
-            List<MetadataInstancesDto> sourceMetas = metadataInstancesService.findByNodeId(sourceNode.getId(), user, taskId);
+            List<MetadataInstancesDto> sourceMetas = metadataInstancesService.findByNodeId(getNodeIdIfContainsMigrateUnionNode(sourceNode), user, taskId);
             if (CollectionUtils.isNotEmpty(sourceMetas)) {
                 sourceMetaMap = sourceMetas.stream().collect(Collectors.
                         toMap(MetadataInstancesDto::getAncestorsName, MetadataInstancesDto::getQualifiedName, (k1, k2) -> k1));
@@ -378,8 +323,32 @@ private Page<MetadataTransformerItemDto> getMetaByJsNode(String nodeId, Page<Met
         return result;
     }
 
+    protected List<String> getMigrateTableNames(DatabaseNode sourceNode, UserDetail userDetail){
+        if (StringUtils.equals("expression", sourceNode.getMigrateTableSelectType())) {
+            List<MetadataInstancesDto> metaInstances = metadataInstancesService.findSourceSchemaBySourceId(sourceNode.getConnectionId(), null, userDetail, "original_name");
+            return MetadataInstancesFilterUtil.getFilteredOriginalNames(metaInstances,sourceNode);
+        }else{
+            return sourceNode.getTableNames();
+        }
+    }
+
+    protected void checkUnionProcess(DAG dag, String nodeId, List<String> tableNames){
+        LinkedList<Node<?>> preNodes = dag.getPreNodes(nodeId);
+        Node<?> node = dag.getNode(nodeId);
+        if(!(node instanceof MigrateScriptProcessNode) && CollectionUtils.isNotEmpty(preNodes)){
+            LinkedList<MigrateUnionProcessorNode> migrateUnionProcessorNodes = dag.getPreNodes(nodeId).stream().filter(MigrateUnionProcessorNode.class::isInstance)
+                    .map(MigrateUnionProcessorNode.class::cast)
+                    .collect(Collectors.toCollection(LinkedList::new));
+            if(CollectionUtils.isNotEmpty(migrateUnionProcessorNodes)){
+                tableNames.clear();
+                tableNames.add(migrateUnionProcessorNodes.getLast().getTableName());
+            }
+        }
+    }
+
     @NotNull
     private Page<MetadataTransformerItemDto> getMetadataTransformerItemDtoPage(UserDetail userDetail
+
             , Page<MetadataTransformerItemDto> result, DatabaseNode sourceNode, DatabaseNode targetNode
             , List<String> tableNames, List<String> currentTableList, DataSourceConnectionDto targetDataSource
             , final String taskId, List<Node<?>> predecessors, Node<?> currentNode) {
@@ -1030,4 +999,27 @@ private MetadataInstancesDto processFieldToDB(Schema schema, MetadataInstancesDt
 
         return metadataInstancesDto;
     }
+
+    // TAP-3822 数据复制，字段编辑节点，变更了字段名，但是没法保存，模型推演也没有展示出来(字段编辑节点的 /getTapNodeInfo 结果中查不到源表的sourceQualifiedName)
+    protected String getNodeIdIfContainsMigrateUnionNode(DatabaseNode sourceNode) {
+        DAG dag = sourceNode.getDag();
+        if (null == dag) {
+            return sourceNode.getId();
+        }
+        List<Node> nodes = dag.getNodes();
+        if (CollUtil.isEmpty(nodes)) {
+            return sourceNode.getId();
+        }
+        Optional<Node> first = nodes.stream()
+                .filter(Objects::nonNull)
+                .filter(n -> NodeEnum.migrate_union_processor.name().equalsIgnoreCase(n.getType()))
+                .findFirst();
+        if (first.isPresent()) {
+            Node node = first.get();
+            return node.getId();
+        } else {
+            return sourceNode.getId();
+        }
+    }
+
 }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskRecordServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskRecordServiceImpl.java
index d891031614..23b26bd949 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskRecordServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskRecordServiceImpl.java
@@ -171,4 +171,10 @@ public TaskDto queryTask(String taskRecordId, String userId) {
 
         return taskDto;
     }
+
+    @Override
+    public void cleanTaskRecord(String taskId) {
+        Query query = new Query(Criteria.where("taskId").is(taskId));
+        mongoTemplate.remove(query, TaskRecord.class);
+    }
 }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskSaveServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskSaveServiceImpl.java
index 69aea6485e..cfa886575d 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskSaveServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskSaveServiceImpl.java
@@ -19,12 +19,12 @@
 import com.tapdata.tm.commons.task.dto.alarm.AlarmRuleVO;
 import com.tapdata.tm.commons.task.dto.alarm.AlarmSettingDto;
 import com.tapdata.tm.commons.task.dto.alarm.AlarmSettingVO;
-import com.tapdata.tm.commons.util.NoPrimaryKeyTableSelectType;
 import com.tapdata.tm.config.security.UserDetail;
 import com.tapdata.tm.metadatainstance.service.MetadataInstancesService;
 import com.tapdata.tm.task.service.TaskSaveService;
 import com.tapdata.tm.utils.FunctionUtils;
 import com.tapdata.tm.utils.Lists;
+import com.tapdata.tm.utils.MetadataInstancesFilterUtil;
 import io.tapdata.entity.conversion.PossibleDataTypes;
 import lombok.Setter;
 import org.apache.commons.collections.CollectionUtils;
@@ -34,7 +34,6 @@
 
 import java.util.*;
 import java.util.function.Function;
-import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 @Service
@@ -56,51 +55,8 @@ public void syncTaskSetting(TaskDto taskDto, UserDetail userDetail) {
                     String connectionId = sourceNode.getConnectionId();
                     List<MetadataInstancesDto> metaList = metadataInstancesService.findBySourceIdAndTableNameListNeTaskId(connectionId, null, userDetail);
                     if (CollectionUtils.isNotEmpty(metaList)) {
-											Function<MetadataInstancesDto, Boolean> filterTableByNoPrimaryKey = Optional
-												.of(NoPrimaryKeyTableSelectType.parse(sourceNode.getNoPrimaryKeyTableSelectType()))
-												.map(type -> {
-													switch (type) {
-														case HasKeys:
-															return (Function<MetadataInstancesDto, Boolean>) metadataInstancesDto -> {
-																if (null != metadataInstancesDto.getFields()) {
-																	for (Field field : metadataInstancesDto.getFields()) {
-																		if (Boolean.TRUE.equals(field.getPrimaryKey())) return false;
-																	}
-																}
-																return true;
-															};
-														case NoKeys:
-															return (Function<MetadataInstancesDto, Boolean>) metadataInstancesDto -> {
-																if (null != metadataInstancesDto.getFields()) {
-																	for (Field field : metadataInstancesDto.getFields()) {
-																		if (Boolean.TRUE.equals(field.getPrimaryKey())) return true;
-																	}
-																}
-																return false;
-															};
-														default:
-													}
-													return null;
-												}).orElse(metadataInstancesDto -> false);
-
-											List<String> collect = metaList.stream()
-												.map(metadataInstancesDto -> {
-													if (filterTableByNoPrimaryKey.apply(metadataInstancesDto)) {
-														return null;
-													}
-													return metadataInstancesDto.getOriginalName();
-												})
-												.filter(originalName -> {
-													if (null == originalName) {
-														return false;
-													} else if (StringUtils.isEmpty(sourceNode.getTableExpression())) {
-														return false;
-													} else {
-														return Pattern.matches(sourceNode.getTableExpression(), originalName);
-													}
-												})
-												.collect(Collectors.toList());
-											sourceNode.setTableNames(collect);
+                        List<String> collect = MetadataInstancesFilterUtil.getFilteredOriginalNames(metaList,sourceNode);
+                        sourceNode.setTableNames(collect);
                     }
                 }
 
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskScheduleServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskScheduleServiceImpl.java
index 9631c1d56f..698d5bee8e 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskScheduleServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskScheduleServiceImpl.java
@@ -47,6 +47,7 @@
 import java.text.MessageFormat;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
 
 
 @Service
@@ -164,11 +165,22 @@ public CalculationEngineVo cloudTaskLimitNum(TaskDto taskDto, UserDetail user, b
                 }
             }
         });
-
+        List<String> accessNodeProcessIdList = agentGroupService.getProcessNodeListWithGroup(taskDto, user);
         if (needCalculateAgent.get()) {
             if (AccessNodeTypeEnum.MANUALLY_SPECIFIED_BY_THE_USER.name().equals(taskDto.getAccessNodeType())
                     && CollectionUtils.isNotEmpty(taskDto.getAccessNodeProcessIdList())) {
                 taskDto.setAgentId(taskDto.getAccessNodeProcessIdList().get(0));
+            } else if(AccessNodeTypeEnum.isGroupManually(taskDto.getAccessNodeType())
+                    && CollectionUtils.isNotEmpty(accessNodeProcessIdList)){
+                List<Worker> availableAgent = workerService.findAvailableAgentByAccessNode(user,accessNodeProcessIdList);
+                List<String> processIds = availableAgent.stream().map(Worker::getProcessId).collect(Collectors.toList());
+                String finalAgentId = null;
+                if(StringUtils.isNotEmpty(taskDto.getPriorityProcessId()) && processIds.contains(taskDto.getPriorityProcessId())){
+                    finalAgentId = taskDto.getPriorityProcessId();
+                }else{
+                    finalAgentId = processIds.get(0);
+                }
+                taskDto.setAgentId(finalAgentId);
             } else {
                 taskDto.setAgentId(null);
             }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/dagcheckstrategy/ModelProcessStrategyImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/dagcheckstrategy/ModelProcessStrategyImpl.java
index 3956878b5d..ba3999cfa2 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/dagcheckstrategy/ModelProcessStrategyImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/dagcheckstrategy/ModelProcessStrategyImpl.java
@@ -2,10 +2,8 @@
 
 import cn.hutool.core.date.DateUtil;
 import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
-import com.tapdata.tm.commons.schema.Field;
 import com.tapdata.tm.commons.schema.MetadataInstancesDto;
 import com.tapdata.tm.commons.task.dto.TaskDto;
-import com.tapdata.tm.commons.util.NoPrimaryKeyTableSelectType;
 import com.tapdata.tm.config.security.UserDetail;
 import com.tapdata.tm.message.constant.Level;
 import com.tapdata.tm.metadatainstance.service.MetadataInstancesService;
@@ -14,9 +12,9 @@
 import com.tapdata.tm.task.service.DagLogStrategy;
 import com.tapdata.tm.utils.Lists;
 import com.tapdata.tm.utils.MessageUtil;
+import com.tapdata.tm.utils.MetadataInstancesFilterUtil;
 import lombok.Setter;
 import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -26,9 +24,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
-import java.util.Optional;
-import java.util.function.Function;
-import java.util.regex.Pattern;
 
 @Component("modelProcessStrategy")
 @Setter(onMethod_ = {@Autowired})
@@ -51,50 +46,7 @@ public List<TaskDagCheckLog> getLogs(TaskDto taskDto, UserDetail userDetail, Loc
             if ("expression".equals(sourceNode.getMigrateTableSelectType())) {
                 List<MetadataInstancesDto> metaInstances = metadataInstancesService.findSourceSchemaBySourceId(sourceNode.getConnectionId(), null, userDetail, "original_name");
                 if (CollectionUtils.isNotEmpty(metaInstances)) {
-									Function<MetadataInstancesDto, Boolean> filterTableByNoPrimaryKey = Optional
-										.of(NoPrimaryKeyTableSelectType.parse(sourceNode.getNoPrimaryKeyTableSelectType()))
-										.map(type -> {
-											switch (type) {
-												case HasKeys:
-													return (Function<MetadataInstancesDto, Boolean>) metadataInstancesDto -> {
-														if (null != metadataInstancesDto.getFields()) {
-															for (Field field : metadataInstancesDto.getFields()) {
-																if (Boolean.TRUE.equals(field.getPrimaryKey())) return false;
-															}
-														}
-														return true;
-													};
-												case NoKeys:
-													return (Function<MetadataInstancesDto, Boolean>) metadataInstancesDto -> {
-														if (null != metadataInstancesDto.getFields()) {
-															for (Field field : metadataInstancesDto.getFields()) {
-																if (Boolean.TRUE.equals(field.getPrimaryKey())) return true;
-															}
-														}
-														return false;
-													};
-												default:
-											}
-											return null;
-										}).orElse(metadataInstancesDto -> false);
-
-                    total = metaInstances.stream()
-											.map(metadataInstancesDto -> {
-												if (filterTableByNoPrimaryKey.apply(metadataInstancesDto)) {
-													return null;
-												}
-												return metadataInstancesDto.getOriginalName();
-											})
-                            .filter(originalName -> {
-															if (null == originalName) {
-																return false;
-															} else if (StringUtils.isEmpty(sourceNode.getTableExpression())) {
-                                    return false;
-                                } else {
-                                    return Pattern.matches(sourceNode.getTableExpression(), originalName);
-                                }
-                            })
-                            .count();
+                    total = MetadataInstancesFilterUtil.countFilteredOriginalNames(metaInstances,sourceNode);
                 }
             } else {
                 total = sourceNode.getTableNames().size();
diff --git a/manager/tm/src/main/java/com/tapdata/tm/user/controller/UserController.java b/manager/tm/src/main/java/com/tapdata/tm/user/controller/UserController.java
index 5d408c5980..41701d64d6 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/user/controller/UserController.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/user/controller/UserController.java
@@ -25,6 +25,7 @@
 import com.tapdata.tm.userLog.constant.Modular;
 import com.tapdata.tm.userLog.service.UserLogService;
 import com.tapdata.tm.utils.RC4Util;
+import com.tapdata.tm.utils.SendStatus;
 import com.tapdata.tm.utils.WebUtils;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -462,9 +463,9 @@ public ResponseMessage sendValidateCode(@RequestBody ResetPasswordParam resetPas
         if (null == user) {
             throw new BizException("User.Not.Exist");
         }
-        Boolean sendResult = userService.sendValidateCde(email);
-        if (!sendResult){
-            throw new BizException("Email.Send.fail");
+        SendStatus sendResult = userService.sendValidateCde(email);
+        if (!"true".equals(sendResult.getStatus())){
+            throw new BizException("Email.verify.code.Send.fail", sendResult.getErrorMessage());
         }
         return success();
     }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/user/service/UserServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/user/service/UserServiceImpl.java
index 00ab75a54d..91b43638d3 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/user/service/UserServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/user/service/UserServiceImpl.java
@@ -399,10 +399,8 @@ public <T extends BaseDto> UserDto save(CreateUserRequest request, UserDetail us
     }
     private List<RoleMappingDto> updateRoleMapping(String userId, List<Object> roleusers, UserDetail userDetail) {
         // delete old role mapping
-        if (!userDetail.getEmail().equals("admin@admin.com")) {
             long deleted = roleMappingService.deleteAll(Query.query(Criteria.where("principalId").is(userId).and("principalType").is("USER")));
             log.info("delete old role mapping for userId {}, deleted: {}", userId, deleted);
-        }
         // add new role mapping
         if (CollectionUtils.isNotEmpty(roleusers)) {
             List<RoleMappingDto> roleMappingDtos = roleusers.stream().map(r -> (String) r).map(roleId -> {
@@ -413,7 +411,7 @@ private List<RoleMappingDto> updateRoleMapping(String userId, List<Object> roleu
                 return roleMappingDto;
             }).collect(Collectors.toList());
             if (CollectionUtils.isNotEmpty(roleMappingDtos)) {
-                return roleMappingService.save(roleMappingDtos, userDetail);
+                return roleMappingService.updateUserRoleMapping(roleMappingDtos, userDetail);
             }
         }
         return null;
@@ -463,12 +461,12 @@ public UserDto updateEmail(UserDetail loginUser, BindEmailReq bindEmailReq) {
      *
      * @return
      */
-    public Boolean sendValidateCde(String email) {
-        Boolean sendResult = false;
+    public SendStatus sendValidateCde(String email) {
+        SendStatus sendStatus = new SendStatus();
         User user = findOneByEmail(email);
         if (null != user) {
             String validateCode = RandomUtil.randomNumbers(6);
-            SendStatus sendStatus = mailUtils.sendValidateCode(email, user.getUsername(), validateCode);
+            sendStatus = mailUtils.sendValidateCodeForResetPWD(email, user.getUsername(), validateCode);
             if ("true".equals(sendStatus.getStatus())) {
                 Date validateCodeSendTime = new Date();
                 Update update = new Update();
@@ -476,13 +474,15 @@ public Boolean sendValidateCde(String email) {
                 update.set("validateCodeSendTime", validateCodeSendTime);
 
                 Query query = Query.query(Criteria.where("_id").is(user.getId()));
-                repository.getMongoOperations().updateFirst(query, update, "user");
-                sendResult = true;
+                repository.getMongoOperations().updateFirst(query, update, "User");
             } else {
-                log.error("重置密码，邮件发送失败： msg", sendStatus.getErrorMessage());
+                log.error("重置密码，邮件发送失败： " + sendStatus.getErrorMessage());
             }
+        } else {
+            sendStatus.setStatus("false");
+            sendStatus.setErrorMessage("User not find: " + email);
         }
-        return sendResult;
+        return sendStatus;
     }
 
     /**
diff --git a/manager/tm/src/main/java/com/tapdata/tm/ws/handler/TestConnectionHandler.java b/manager/tm/src/main/java/com/tapdata/tm/ws/handler/TestConnectionHandler.java
index f57b26f44b..8d95fa5d13 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/ws/handler/TestConnectionHandler.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/ws/handler/TestConnectionHandler.java
@@ -182,6 +182,7 @@ public void handleMessage(WebSocketContext context) throws Exception{
 
 
 							List<Worker> availableAgents = workerService.findAvailableAgentByAccessNode(userDetail, agentGroupService.getProcessNodeListByGroupId(Lists.newArrayList(processId), String.valueOf(accessNodeType), userDetail));
+							availableAgents = checkPriorityEngine(availableAgents,(String) data.get("priorityProcessId"));
 							if (CollectionUtils.isEmpty(availableAgents)) {
 								data.put("status", "error");
 								data.put("msg", "Worker " + processId + " not available, receiver is blank");
@@ -213,7 +214,7 @@ public void handleMessage(WebSocketContext context) throws Exception{
 
 		handleData(agentId, context);
 	}
-		private void handleData(String receiver, WebSocketContext context) {
+		protected void handleData(String receiver, WebSocketContext context) {
 			Map<String, Object> data = context.getMessageInfo().getData();
 			String database_type = MapUtils.getAsString(data, "database_type");
 			String database_uri = MapUtils.getAsString(data, "database_uri");
@@ -357,4 +358,12 @@ private Map<String, Object> testConnectErrorData(String id, String failMessage)
 				{"status", "SUCCESS"},
 		}).collect(Collectors.toMap(data -> (String) data[0], data -> data[1]));
 	}
+
+	protected List<Worker> checkPriorityEngine(List<Worker> availableAgents,String priorityProcessId){
+		List<String> processIds = availableAgents.stream().map(Worker::getProcessId).collect(Collectors.toList());
+		if(org.apache.commons.lang3.StringUtils.isNotEmpty(priorityProcessId) && processIds.contains(priorityProcessId)){
+			return availableAgents.stream().filter(worker -> worker.getProcessId().equals(priorityProcessId)).collect(Collectors.toList());
+		}
+		return availableAgents;
+	}
 }
diff --git a/manager/tm/src/main/resources/ValidationMessages_en.properties b/manager/tm/src/main/resources/ValidationMessages_en.properties
index 01926b6952..e085386d0a 100644
--- a/manager/tm/src/main/resources/ValidationMessages_en.properties
+++ b/manager/tm/src/main/resources/ValidationMessages_en.properties
@@ -29,6 +29,7 @@ MetadataDefinition.Value.Exist=MetadataDefinition already exists
 #reset password
 Email.Not.Exist=email not exist
 Email.Send.fail=send email failed
+Email.verify.code.Send.fail=Send email failed, message: {0}
 
 
 #modules
diff --git a/manager/tm/src/main/resources/ValidationMessages_zh_CN.properties b/manager/tm/src/main/resources/ValidationMessages_zh_CN.properties
index 8eb9fb1c39..e6724295eb 100644
--- a/manager/tm/src/main/resources/ValidationMessages_zh_CN.properties
+++ b/manager/tm/src/main/resources/ValidationMessages_zh_CN.properties
@@ -14,29 +14,30 @@ Resources.NotEnough=\u8D44\u6E90\u4E0D\u8DB3
 
 
 #Inspect
-inspect.name.exist=\u540d\u79f0\u5df2\u5b58\u5728\uff0c\u8bf7\u4fee\u6539
-Inspect.agentTag.null=\u0074\u0061\u0067\u4e0d\u80fd\u4e3a\u7a7a
-Inspect.task.null=\u0074\u0061\u0073\u006b\u4e0d\u80fd\u4e3a\u7a7a
+inspect.name.exist=\u540D\u79F0\u5DF2\u5B58\u5728\uFF0C\u8BF7\u4FEE\u6539
+Inspect.agentTag.null=tag\u4E0D\u80FD\u4E3A\u7A7A
+Inspect.task.null=task\u4E0D\u80FD\u4E3A\u7A7A
 #function
-function.name.exist=\u540d\u79f0\u5df2\u5b58\u5728\uff0c\u8bf7\u4fee\u6539
+function.name.exist=\u540D\u79F0\u5DF2\u5B58\u5728\uFF0C\u8BF7\u4FEE\u6539
 
 #MetadataDefinition
-MetadataDefinition.Value.Exist=\u5206\u7c7b\u540d\u79f0\u5df2\u5b58\u5728
+MetadataDefinition.Value.Exist=\u5206\u7C7B\u540D\u79F0\u5DF2\u5B58\u5728
 
 
 #reset password
-Email.Not.Exist=\u90ae\u7bb1\u4e0d\u5b58\u5728
-Email.Send.fail=\u90ae\u4ef6\u53d1\u9001\u5931\u8d25
+Email.Not.Exist=\u90AE\u7BB1\u4E0D\u5B58\u5728
+Email.Send.fail=\u90AE\u4EF6\u53D1\u9001\u5931\u8D25
+Email.verify.code.Send.fail=\u90AE\u4EF6\u53D1\u9001\u5931\u8D25
 
 #modules
-Modules.Name.Existed=api\u540d\u79f0\u5df2\u5b58\u5728
-Modules.BasePathAndVersion.Existed=api \u57fa\u7840\u8def\u5f84\u5df2\u5b58\u5728
-Modules.Connection.Null= \u6570\u636e\u5e93\u8fde\u63a5\u4e0d\u80fd\u4e3a\u7a7a\uff0c\u8bf7\u4fee\u6539
-Datasource.LinkModules=\u5b58\u5728\u5173\u8054\u7684api\uff0c\u4e0d\u80fd\u5220\u9664
-
-User.Not.Exist= \u7528\u6237\u4e0d\u5b58\u5728
-User.email.Found=\u627e\u4e0d\u5230\u5bf9\u5e94\u7684\u90ae\u7bb1
-ValidateCode.Not.Incorrect=\u9a8c\u8bc1\u7801\u9519\u8bef
-ValidateCode.Is.expired=\u9a8c\u8bc1\u7801\u5df2\u8fc7\u671f
+Modules.Name.Existed=api\u540D\u79F0\u5DF2\u5B58\u5728
+Modules.BasePathAndVersion.Existed=api \u57FA\u7840\u8DEF\u5F84\u5DF2\u5B58\u5728
+Modules.Connection.Null= \u6570\u636E\u5E93\u8FDE\u63A5\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u8BF7\u4FEE\u6539
+Datasource.LinkModules=\u5B58\u5728\u5173\u8054\u7684api\uFF0C\u4E0D\u80FD\u5220\u9664
+
+User.Not.Exist= \u7528\u6237\u4E0D\u5B58\u5728
+User.email.Found=\u627E\u4E0D\u5230\u5BF9\u5E94\u7684\u90AE\u7BB1
+ValidateCode.Not.Incorrect=\u9A8C\u8BC1\u7801\u9519\u8BEF
+ValidateCode.Is.expired=\u9A8C\u8BC1\u7801\u5DF2\u8FC7\u671F
 
 
diff --git a/manager/tm/src/main/resources/alarmTemplate_en_US.properties b/manager/tm/src/main/resources/alarmTemplate_en_US.properties
index 22c95dbc29..bcf9fdbcf7 100644
--- a/manager/tm/src/main/resources/alarmTemplate_en_US.properties
+++ b/manager/tm/src/main/resources/alarmTemplate_en_US.properties
@@ -2,9 +2,9 @@ TASK_FULL_COMPLETE = Task#{[taskName]} full sync has completed, time#{[costTime]
 TASK_INCREMENT_START = Task #{[taskName]} incremental synchronization has started, incremental time point #{[cdcTime]}, alarm time: #{[alarmDate]}
 TASK_STATUS_STOP_ERROR = Task#{[taskName]} has been stopped with an error, please process as soon as possible! Alarm time: #{[alarmDate]}
 TASK_STATUS_STOP_MANUAL = Task#{[taskName]} has been stopped by user [#{[updatorName]}], please attend to it! Alert time: #{[alarmDate]}
-TASK_INCREMENT_DELAY_START = Task#{[taskName]}incremental delay#{[flag]}threshold#{[threshold]}ms, current value: #{[currentValue]}ms, please pay attention! Alarm time: #{[alarmDate]}
-TASK_INCREMENT_DELAY_ALWAYS = Task#{[taskName]}incremental delay#{[flag]}threshold#{[threshold]}ms, has lasted#{[continueTime]}minutes, current value: #{[currentValue]}ms, please pay attention! Alarm time: #{[alarmDate]}
-TASK_INCREMENT_DELAY_RECOVER = Task#{[taskName]} incremental delay is back to normal, current value: #{[currentValue]}ms. alarmTime: #{[alarmDate]}
+TASK_INCREMENT_DELAY_START = Task #{[taskName]} incremental delay #{[flag]} threshold #{[threshold]}ms, current value: #{[currentValue]}ms, please pay attention! Alarm time: #{[alarmDate]}
+TASK_INCREMENT_DELAY_ALWAYS = Task #{[taskName]} incremental delay #{[flag]} threshold #{[threshold]}ms, has lasted #{[continueTime]}minutes, current value: #{[currentValue]}ms, please pay attention! Alarm time: #{[alarmDate]}
+TASK_INCREMENT_DELAY_RECOVER = Task #{[taskName]} incremental delay is back to normal, current value: #{[currentValue]}ms. alarmTime: #{[alarmDate]}
 DATANODE_SOURCE_CANNOT_CONNECT = Task #{[taskName]} used source connection [#{[sourceName]}] is currently not connecting properly, please take care of it as soon as possible! Alarm time: #{[alarmDate]}
 DATANODE_SOURCE_CANNOT_CONNECT_ALWAYS = The source connection [#{[sourceName]}] used by task #{[taskName]} is currently not connecting properly and has lasted #{[continueTime]} minutes, please handle it as soon as possible! Alarm time: #{[alarmDate]}
 DATANODE_SOURCE_CANNOT_CONNECT_RECOVER = The source connection [#{[sourceName]}] used by task #{[taskName]} has been restored to a normal connection. Alarm time: #{[alarmDate]}
@@ -14,7 +14,7 @@ DATANODE_AVERAGE_HANDLE_CONSUME_RECOVER = The average processing elapsed time of
 PROCESSNODE_AVERAGE_HANDLE_CONSUME_START = The average processing elapsed time#{[flag]}threshold#{[threshold]}ms for the processing node [#{[nodeName]}] of task#{[taskName]}, current value: #{[currentValue ]}ms, stay tuned! Alarm time: #{[alarmDate]}
 PROCESSNODE_AVERAGE_HANDLE_CONSUME_ALWAYS = Average processing elapsed time#{[flag]}threshold#{[threshold]}ms for task#{[taskName]}'s processing node [#{[nodeName]}], has lasted#{[continueTime ]} minutes, current value: #{[currentValue]}ms, stay tuned! Alarm time: #{[alarmDate]}
 PROCESSNODE_AVERAGE_HANDLE_CONSUME_RECOVER = The average processing elapsed time of the processing node [#{[nodeName]}] of task #{[taskName]} has returned to normal, current value: #{[currentValue]}ms. alarmDate: #{[ alarmDate]}
-TARGET_AVERAGE_HANDLE_CONSUME_START = Average processing elapsed time#{[flag]}threshold#{[threshold]}ms for target node [#{[nodeName]}] of task#{[taskName]}, current value: #{[currentValue]}ms. Stay tuned! Alarm time: #{[alarmDate]}
+TARGET_AVERAGE_HANDLE_CONSUME_START = Average processing elapsed time #{[flag]} threshold #{[threshold]}ms for target node [#{[nodeName]}] of task#{[taskName]}, current value: #{[currentValue]}ms. Stay tuned! Alarm time: #{[alarmDate]}
 TARGET_AVERAGE_HANDLE_CONSUME_ALWAYS = The target node [#{[nodeName]}] of task #{[taskName]} has taken an average processing time of #{[flag]} threshold #{[threshold]}ms, has lasted #{[continueTime]} minutes, and Current value: #{[currentValue]}ms, stay tuned! Alarm time: #{[alarmDate]}
 TARGET_AVERAGE_HANDLE_CONSUME_RECOVER = The average processing time of the target node [#{[nodeName]}] of task #{[taskName]} has returned to normal, current value: #{[currentValue]}ms. alarmDate:#{[alarmDate]}
 SYSTEM_FLOW_EGINGE_DOWN_CHANGE_AGENT = The Agent[#{[agentName]}] where task #{[taskName]} is located has stopped running, there are #{[number]} available Agents, the task will be rescheduled to Agent[#{[ otherAgentName]}]] to run, please pay attention! Alarm time: #{[alarmDate]}
diff --git a/manager/tm/src/main/resources/messages.properties b/manager/tm/src/main/resources/messages.properties
index fab104a823..09bc94cadb 100644
--- a/manager/tm/src/main/resources/messages.properties
+++ b/manager/tm/src/main/resources/messages.properties
@@ -102,6 +102,7 @@ MetadataDefinition.Value.Exist=MetadataDefinition already exists
 #reset password
 Email.Not.Exist=email not exist
 Email.Send.fail=send email failed
+Email.verify.code.Send.fail=Send email failed, message: {0}
 Email.Format.wrong = Email format error
 Account.disabled=User is frozen
 
@@ -109,6 +110,9 @@ Account.disabled=User is frozen
 Modules.Name.Existed=api name already exists
 Modules.BasePathAndVersion.Existed=api basePath already exists
 Modules.Connection.Null= this Connection is null
+Modules.Permission.Scope.Null = permission scope cannot be null
+Modules.Tags.Null = tags cannot be null
+
 Datasource.LinkModules= has referenced api
 
 
@@ -265,3 +269,7 @@ webhook.history.page.size.error=The paging parameter [pageSize] is invalid, the
 #auth
 insufficient.permissions=Insufficient permissions: {0}, current operation requires permission: {1}
 inspect.result.not.exists=The verification record for the current verification task no longer exists: {0}
+
+source.setting.check.cdc=[cdc check] The configuration of the data source: {0} does not support incremental functionality, and incremental tasks cannot be configured. 
+
+export.inspectDetails.failed = Export inspectDetails failed :{0}
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/messages_en_US.properties b/manager/tm/src/main/resources/messages_en_US.properties
index 61654f3688..f0f1ad8a9a 100644
--- a/manager/tm/src/main/resources/messages_en_US.properties
+++ b/manager/tm/src/main/resources/messages_en_US.properties
@@ -60,6 +60,7 @@ Too.Many.Login.Failures=Login failed, please try again later
 #reset password
 Email.Not.Exist=email not exist
 Email.Send.fail=send email failed
+Email.verify.code.Send.fail=Send email failed, message: {0}
 Email.Format.wrong = Email format error
 Account.disabled=User is frozen
 
@@ -67,6 +68,8 @@ Account.disabled=User is frozen
 Modules.Name.Existed=api name already exists
 Modules.BasePathAndVersion.Existed=api basePath already exists
 Modules.Connection.Null= this Connection is null
+Modules.Permission.Scope.Null = permission scope cannot be null
+Modules.Tags.Null = tags cannot be null
 
 
 User.Not.Exist= user not exist
@@ -196,3 +199,7 @@ webhook.history.page.size.error=The paging parameter [pageSize] is invalid, the
 #auth
 insufficient.permissions=Insufficient permissions: {0}, current operation requires permission: {1}
 inspect.result.not.exists=The verification record for the current verification task no longer exists: {0}
+
+source.setting.check.cdc=[cdc check] The configuration of the data source:{0} does not support incremental functionality, and incremental tasks cannot be configured. 
+
+export.inspectDetails.failed = Export inspectDetails failed :{0}
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/messages_zh_CN.properties b/manager/tm/src/main/resources/messages_zh_CN.properties
index e53c16330f..742ba2d208 100644
--- a/manager/tm/src/main/resources/messages_zh_CN.properties
+++ b/manager/tm/src/main/resources/messages_zh_CN.properties
@@ -121,6 +121,8 @@ Sid.is.wrong=sid\u9519\u8BEF
 
 Modules.BasePathAndVersion.Existed=\u8BE5\u7248\u672C\u4E0Bbasepath\u540D\u79F0\u5DF2\u5B58\u5728
 Modules.Connection.Null=API\u0020\u6570\u636E\u5E93\u8FDE\u63A5\u4E0D\u80FD\u4E3A\u7A7A
+Modules.Permission.Scope.Null=\u6743\u9650\u8303\u56f4\u4e0d\u80fd\u4e3a\u7a7a
+Modules.Tags.Null = \u6240\u5c5e\u5e94\u7528\u4e0d\u80fd\u4e3a\u7a7a
 
 AccessCode.Is.Null=AccessCode\u0020\u4E3A\u7A7A
 AccessCode.No.User=AccessCode\u0020\u627E\u4E0D\u5230\u5BF9\u5E94\u7684\u7528\u6237
@@ -131,6 +133,7 @@ MetadataDefinition.Value.Exist=\u5206\u7C7B\u540D\u79F0\u5DF2\u5B58\u5728
 #reset password
 Email.Not.Exist=\u90AE\u7BB1\u4E0D\u5B58\u5728
 Email.Send.fail=\u90AE\u4EF6\u53D1\u9001\u5931\u8D25
+Email.verify.code.Send.fail=\u90AE\u4EF6\u53D1\u9001\u5931\u8D25\uFF0C{0}
 Email.Format.wrong=\u90AE\u7BB1\u683C\u5F0F\u9519\u8BEF
 
 
@@ -296,3 +299,6 @@ webhook.history.page.size.error=\u5206\u9875\u53C2\u6570[pageSize]\u65E0\u6548\u
 insufficient.permissions=\u6743\u9650\u4E0D\u8DB3: {0}\uFF0C\u5F53\u524D\u64CD\u4F5C\u9700\u8981\u6743\u9650\uFF1A{1}
 inspect.result.not.exists=\u5F53\u524D\u6821\u9A8C\u4EFB\u52A1\u7684\u6821\u9A8C\u8BB0\u5F55\u5DF2\u7ECF\u4E0D\u5B58\u5728\uFF1A{0}
 
+source.setting.check.cdc= \u589e\u91cf\u68c0\u67e5\u3011\u0020\u6570\u636e\u6e90\u003a\u007b\u0030\u007d\u7684\u589e\u91cf\u529f\u80fd\u4e0d\u652f\u6301\uff0c\u4e0d\u80fd\u914d\u7f6e\u589e\u91cf\u4efb\u52a1\u000d\u000a
+
+export.inspectDetails.failed =\u4e0b\u8f7d\u6821\u9a8c\u8be6\u60c5\u5931\u8d25 :{0}
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/messages_zh_TW.properties b/manager/tm/src/main/resources/messages_zh_TW.properties
index bbcc9d7b4d..1e12ca342b 100644
--- a/manager/tm/src/main/resources/messages_zh_TW.properties
+++ b/manager/tm/src/main/resources/messages_zh_TW.properties
@@ -134,3 +134,10 @@ webhook.history.page.size.error=\u5206\u9801\u53C3\u6578[pageSize]\u7121\u6548\u
 #auth
 insufficient.permissions=\u8A31\u53EF\u6B0A\u4E0D\u8DB3: {0}\uFF0C\u5F53\u524D\u64CD\u4F5C\u9700\u8981\u6743\u9650\uFF1A{1}
 inspect.result.not.exists=\u7576\u524D\u6821\u9A57\u4EFB\u52D9\u7684\u6821\u9A57\u8A18\u9304\u5DF2\u7D93\u4E0D\u5B58\u5728\uFF1A{0}
+
+source.setting.check.cdc= \u3010\u589e\u91cf\u6aa2\u67e5\u3011\u0020\u6578\u64da\u6e90\u003a\u007b\u0030\u007d\u7684\u914d\u7f6e\u589e\u91cf\u529f\u80fd\u4e0d\u652f\u6301\uff0c\u4e0d\u80fd\u914d\u7f6e\u589e\u91cf\u4efb\u52d9\u000d\u000a
+
+Modules.Permission.Scope.Null=\u6b0a\u9650\u7bc4\u570d\u4e0d\u80fd\u7232\u7a7a
+Modules.Tags.Null=\u6240\u5c6c\u61c9\u7528\u4e0d\u80fd\u70ba\u7a7a
+
+export.inspectDetails.failed = \u4e0b\u8f09\u6821\u9a57\u8a73\u60c5\u5931\u6557 :{0}
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/oem/datapp/connector/replace.json b/manager/tm/src/main/resources/oem/datapp/connector/replace.json
new file mode 100644
index 0000000000..14b5064f10
--- /dev/null
+++ b/manager/tm/src/main/resources/oem/datapp/connector/replace.json
@@ -0,0 +1,9 @@
+{
+  "tapdata": "DGG",
+  "Tapdata": "DGG",
+  "TapData": "DGG",
+  "TAPDATA": "DGG",
+
+
+  "ikas\\-bucket\\-01": "tapdata-bucket-01"
+}
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/oem/datapp/email/replace.json b/manager/tm/src/main/resources/oem/datapp/email/replace.json
new file mode 100644
index 0000000000..1017db9e5a
--- /dev/null
+++ b/manager/tm/src/main/resources/oem/datapp/email/replace.json
@@ -0,0 +1,6 @@
+{
+  "tapdata": "DGG",
+  "Tapdata": "DGG",
+  "TapData": "DGG",
+  "TAPDATA": "DGG"
+}
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/oem/datapp/log/replace.json b/manager/tm/src/main/resources/oem/datapp/log/replace.json
new file mode 100644
index 0000000000..1017db9e5a
--- /dev/null
+++ b/manager/tm/src/main/resources/oem/datapp/log/replace.json
@@ -0,0 +1,6 @@
+{
+  "tapdata": "DGG",
+  "Tapdata": "DGG",
+  "TapData": "DGG",
+  "TAPDATA": "DGG"
+}
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/resetPasswordTemplate.html b/manager/tm/src/main/resources/resetPasswordTemplate.html
new file mode 100644
index 0000000000..79c3ba73dc
--- /dev/null
+++ b/manager/tm/src/main/resources/resetPasswordTemplate.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8" />
+    <title>Password Reset</title>
+</head>
+<body>
+<div style="margin: 50px auto 0; font-size: 12px; color: #000; max-width: 700px">
+    <div style="overflow: hidden; line-height: 30px">
+        <img src="https://cloud.tapdata.net/assets/img/logo2.png" alt="" style="float: left; height: 30px" />
+        <div style="float: right; color: #76808e">让数据随时可用</div>
+    </div>
+    <div style="margin-top: 17px; background: #fff; box-shadow: -2px 0px 8px 0px rgb(0 0 0 / 10%)">
+        <div style="padding: 24px">
+            <div>Hi，<font id="username" style="color: #2c65ff">Gavin</font>：</div>
+            <div style="margin-top: 24px">
+                <span id="sysName">您的验证码是：</span>
+                <span id="code" style="color: #2c65ff"> 234565 </span>
+            </div>
+
+            <div style="padding: 22px;color: #76808e">
+                <div>
+                    <div><font id="validateTimes"  style="font-weight: bolder;color: #2c65ff">5</font> 内分钟有效 </div>
+                </div>
+                <div style="margin-top: 24px" id="mailContent">将用于账号： <font id="account" style="font-weight: bolder">Gavin</font> 进行<font style="color: #2c65ff">修改密码</font>，请回到页面填写此验证码</div>
+            </div>
+        </div>
+        <div style="padding: 22px; text-align: center; border-top: 1px solid #cccccc; color: #76808e">
+            <div>如您对Tapdata的使用有任何疑问，请随时与我们联系。</div>
+            <div style="margin-top: 8px">
+                <a href="https://cloud.tapdata.net/" target="_blank" style="text-decoration: none; color: #76808e"
+                >访问官网</a
+                >
+                <span>｜</span>
+                <a
+                        href="https://cloud.tapdata.net/console/#/workbench"
+                        target="_blank"
+                        style="text-decoration: none; color: #76808e"
+                >访问控制台</a
+                >
+                <span>｜</span>
+                <a
+                        href="https://cloud.tapdata.net/contact.html"
+                        target="_blank"
+                        style="text-decoration: none; color: #76808e"
+                >联系我们</a
+                >
+            </div>
+        </div>
+    </div>
+</div>
+</body>
+</html>
diff --git a/pom.xml b/pom.xml
index ffa977a373..c1657604e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,7 +36,8 @@
             **/*VO.java,
             **/src/main/java/io/tapdata/**/exception/**,
             iengine/api/src/main/java/io/tapdata/aspect/CreateTableFuncAspect.java,
-            iengine/api/src/main/java/io/tapdata/aspect/DropTableFuncAspect.java
+            iengine/api/src/main/java/io/tapdata/aspect/DropTableFuncAspect.java,
+            **/*Context.java
         </sonar.coverage.exclusions>
         <sonar.exclusions>
             manager/**/dto/*,
@@ -53,7 +54,8 @@
             tapdata-cli/**,
             manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/**,
             manager/tm/src/main/java/com/tapdata/tm/init/patches/daas/*,
-            manager/tm/src/main/java/com/tapdata/tm/init/patches/dfs/*
+            manager/tm/src/main/java/com/tapdata/tm/init/patches/dfs/*,
+            **/*Context.java
         </sonar.exclusions>
         <sonar.test.exclusions>
             **/src/test/**


Test patch:
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/common/DAGDataEngineServiceImplTest.java b/iengine/iengine-app/src/test/java/io/tapdata/common/DAGDataEngineServiceImplTest.java
index 3105d886d4..5db9f8d311 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/common/DAGDataEngineServiceImplTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/common/DAGDataEngineServiceImplTest.java
@@ -590,4 +590,81 @@ void testUploadModel(){
             }
         }
     }
+    @Nested
+    class CheckTableNodeSameMetadataInstancesDto{
+        @Test
+        void test_main(){
+            TaskDto taskDto = new TaskDto();
+            taskDto.setId(new ObjectId());
+            transformerWsMessageDto.setTaskDto(taskDto);
+            Map<String,List<String>> tableNodeSameMetadataInstances = new HashMap<>();
+            List<String> nodes = new ArrayList<>();
+            nodes.add("node_1");
+            nodes.add("node_2");
+            tableNodeSameMetadataInstances.put("qualifiedName_test",nodes);
+            transformerWsMessageDto.setTableNodeSameMetadataInstances(tableNodeSameMetadataInstances);
+            MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+            metadataInstancesDto.setOriginalName("test");
+            metadataInstancesDto.setQualifiedName("qualifiedName_test");
+            metadataInstancesDto.setNodeId("node_1");
+            ObsLoggerFactory obsLoggerFactory = mock(ObsLoggerFactory.class);
+            try(MockedStatic<ObsLoggerFactory> mockedStatic = mockStatic(ObsLoggerFactory.class)){
+                mockedStatic.when(ObsLoggerFactory::getInstance).thenReturn(obsLoggerFactory);
+                ObsLogger obsLogger = mock(ObsLogger.class);
+                when(obsLoggerFactory.getObsLogger(any(TaskDto.class))).thenReturn(obsLogger);
+                dagDataEngineService = new DAGDataEngineServiceImpl(transformerWsMessageDto,taskService,new ConcurrentHashMap<>(),clientMongoOperator);
+                dagDataEngineService.checkTableNodeSameMetadataInstancesDto(metadataInstancesDto,new TapTable());
+                Map<String,TapTableMap<String, TapTable>> result = (Map<String, TapTableMap<String, TapTable>>) ReflectionTestUtils.getField(dagDataEngineService,"tapTableMapHashMap");
+                Assertions.assertEquals(1,result.size());
+            }
+        }
+        @Test
+        void test_notSameMetadataInstances(){
+            TaskDto taskDto = new TaskDto();
+            taskDto.setId(new ObjectId());
+            transformerWsMessageDto.setTaskDto(taskDto);
+            Map<String,List<String>> tableNodeSameMetadataInstances = new HashMap<>();
+            List<String> nodes = new ArrayList<>();
+            nodes.add("node_1");
+            nodes.add("node_2");
+            tableNodeSameMetadataInstances.put("qualifiedName_test1",nodes);
+            transformerWsMessageDto.setTableNodeSameMetadataInstances(tableNodeSameMetadataInstances);
+            MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+            metadataInstancesDto.setOriginalName("test");
+            metadataInstancesDto.setQualifiedName("qualifiedName_test2");
+            metadataInstancesDto.setNodeId("node_1");
+            ObsLoggerFactory obsLoggerFactory = mock(ObsLoggerFactory.class);
+            try(MockedStatic<ObsLoggerFactory> mockedStatic = mockStatic(ObsLoggerFactory.class)){
+                mockedStatic.when(ObsLoggerFactory::getInstance).thenReturn(obsLoggerFactory);
+                ObsLogger obsLogger = mock(ObsLogger.class);
+                when(obsLoggerFactory.getObsLogger(any(TaskDto.class))).thenReturn(obsLogger);
+                dagDataEngineService = new DAGDataEngineServiceImpl(transformerWsMessageDto,taskService,new ConcurrentHashMap<>(),clientMongoOperator);
+                dagDataEngineService.checkTableNodeSameMetadataInstancesDto(metadataInstancesDto,new TapTable());
+                Map<String,TapTableMap<String, TapTable>> result = (Map<String, TapTableMap<String, TapTable>>) ReflectionTestUtils.getField(dagDataEngineService,"tapTableMapHashMap");
+                Assertions.assertEquals(0,result.size());
+            }
+        }
+
+        @Test
+        void test_tableNodeSameMetadataInstancesIsNull(){
+            TaskDto taskDto = new TaskDto();
+            taskDto.setId(new ObjectId());
+            transformerWsMessageDto.setTaskDto(taskDto);
+            transformerWsMessageDto.setTableNodeSameMetadataInstances(null);
+            MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+            metadataInstancesDto.setOriginalName("test");
+            metadataInstancesDto.setQualifiedName("qualifiedName_test2");
+            metadataInstancesDto.setNodeId("node_1");
+            ObsLoggerFactory obsLoggerFactory = mock(ObsLoggerFactory.class);
+            try(MockedStatic<ObsLoggerFactory> mockedStatic = mockStatic(ObsLoggerFactory.class)){
+                mockedStatic.when(ObsLoggerFactory::getInstance).thenReturn(obsLoggerFactory);
+                ObsLogger obsLogger = mock(ObsLogger.class);
+                when(obsLoggerFactory.getObsLogger(any(TaskDto.class))).thenReturn(obsLogger);
+                dagDataEngineService = new DAGDataEngineServiceImpl(transformerWsMessageDto,taskService,new ConcurrentHashMap<>(),clientMongoOperator);
+                dagDataEngineService.checkTableNodeSameMetadataInstancesDto(metadataInstancesDto,new TapTable());
+                Map<String,TapTableMap<String, TapTable>> result = (Map<String, TapTableMap<String, TapTable>>) ReflectionTestUtils.getField(dagDataEngineService,"tapTableMapHashMap");
+                Assertions.assertEquals(0,result.size());
+            }
+        }
+    }
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNodeTest.java
index 9f14b8d9cd..7de8646b48 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNodeTest.java
@@ -1,6 +1,7 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.data.pdk;
 
 import base.hazelcast.BaseHazelcastNodeTest;
+import cn.hutool.core.collection.ConcurrentHashSet;
 import com.tapdata.entity.task.config.TaskConfig;
 import com.tapdata.entity.task.config.TaskRetryConfig;
 import com.tapdata.mongo.HttpClientMongoOperator;
@@ -23,6 +24,8 @@
 import io.tapdata.pdk.apis.entity.ConnectorCapabilities;
 import io.tapdata.pdk.core.entity.params.PDKMethodInvoker;
 import io.tapdata.schema.TapTableMap;
+import io.tapdata.supervisor.TaskNodeInfo;
+import io.tapdata.threadgroup.ConnectorOnTaskThreadGroup;
 import org.bson.Document;
 import org.bson.types.ObjectId;
 import org.junit.jupiter.api.*;
@@ -537,4 +540,41 @@ void testOldVersionTimezone1() {
 			assertFalse((Boolean) nodeConfig.get(HazelcastPdkBaseNode.OLD_VERSION_TIMEZONE));
 		}
 	}
+	@Nested
+	class testGetLeakedOrThreadGroupClass{
+		@DisplayName("test ")
+		@Test
+		void test1(){
+			String nodeName = "leakNode";
+			Node leakNode = mock(Node.class);
+			when(leakNode.getId()).thenReturn(nodeName);
+			TaskNodeInfo taskNodeInfo = new TaskNodeInfo();
+			taskNodeInfo.setNode(leakNode);
+			taskNodeInfo.setHasLeaked(true);
+			ConnectorOnTaskThreadGroup connectorOnTaskThreadGroup = new ConnectorOnTaskThreadGroup(dataProcessorContext);
+			taskNodeInfo.setNodeThreadGroup(connectorOnTaskThreadGroup);
+			ConcurrentHashSet<TaskNodeInfo> taskNodeInfos=new ConcurrentHashSet<>();
+			taskNodeInfos.add(taskNodeInfo);
+			Node node = mock(Node.class);
+			when(node.getId()).thenReturn(nodeName);
+			when(hazelcastPdkBaseNode.getNode()).thenReturn(node);
+			hazelcastPdkBaseNode.getReuseOrNewThreadGroup(taskNodeInfos);
+		}
+		@Test
+		void test2(){
+			String nodeName = "leakNode";
+			Node leakNode = mock(Node.class);
+			when(leakNode.getId()).thenReturn(nodeName);
+			TaskNodeInfo taskNodeInfo = new TaskNodeInfo();
+			taskNodeInfo.setNode(leakNode);
+			ConnectorOnTaskThreadGroup connectorOnTaskThreadGroup = new ConnectorOnTaskThreadGroup(dataProcessorContext);
+			taskNodeInfo.setNodeThreadGroup(connectorOnTaskThreadGroup);
+			ConcurrentHashSet<TaskNodeInfo> taskNodeInfos=new ConcurrentHashSet<>();
+			taskNodeInfos.add(taskNodeInfo);
+			Node node = mock(Node.class);
+			when(node.getId()).thenReturn(nodeName);
+			when(hazelcastPdkBaseNode.getNode()).thenReturn(node);
+			hazelcastPdkBaseNode.getReuseOrNewThreadGroup(taskNodeInfos);
+		}
+	}
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSampleSourcePdkDataNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSampleSourcePdkDataNodeTest.java
new file mode 100644
index 0000000000..db096e435a
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSampleSourcePdkDataNodeTest.java
@@ -0,0 +1,93 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.data.pdk;
+
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.jet.core.Processor;
+import com.tapdata.entity.task.context.DataProcessorContext;
+import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
+import io.tapdata.pdk.core.api.ConnectorNode;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.mockito.Mockito.*;
+
+class HazelcastSampleSourcePdkDataNodeTest {
+    HazelcastSampleSourcePdkDataNode node;
+    Processor.Context jetContext;
+    DataProcessorContext dataProcessorContext;
+
+    @BeforeEach
+    void init() {
+        node = mock(HazelcastSampleSourcePdkDataNode.class);
+        jetContext = mock( Processor.Context.class);
+        dataProcessorContext = mock(DataProcessorContext.class);
+
+        ReflectionTestUtils.setField(node, "jetContext", jetContext);
+        ReflectionTestUtils.setField(node, "dataProcessorContext", dataProcessorContext);
+    }
+
+    @Nested
+    class InitNodeTest {
+        HazelcastInstance hazelcastInstance;
+        ConnectorNode connectorNode;
+        TapCodecsFilterManager codecsFilterManager;
+
+        @BeforeEach
+        void init() {
+            hazelcastInstance = mock(HazelcastInstance.class);
+            connectorNode = mock(ConnectorNode.class);
+            codecsFilterManager = mock(TapCodecsFilterManager.class);
+
+            doNothing().when(node).createPdkConnectorNode(dataProcessorContext, hazelcastInstance);
+            doNothing().when(node).connectorNodeInit(dataProcessorContext);
+            when(node.getConnectorNode()).thenReturn(connectorNode);
+            when(connectorNode.getCodecsFilterManager()).thenReturn(codecsFilterManager);
+            when(jetContext.hazelcastInstance()).thenReturn(hazelcastInstance);
+            when(node.initNode()).thenCallRealMethod();
+        }
+
+        @Test
+        void testNormal() {
+            Assertions.assertDoesNotThrow(node::initNode);
+            verify(node).createPdkConnectorNode(dataProcessorContext, hazelcastInstance);
+            verify(node).connectorNodeInit(dataProcessorContext);
+            verify(node).getConnectorNode();
+            verify(connectorNode).getCodecsFilterManager();
+            verify(jetContext).hazelcastInstance();
+        }
+    }
+
+    @Nested
+    class GetSourceTablesTest {
+        @Test
+        void testNormal() {
+            TaskDto taskDto = new TaskDto();
+            taskDto.setSyncType("testRun");
+            when(dataProcessorContext.getTaskDto()).thenReturn(taskDto);
+            DatabaseNode databaseNode = new DatabaseNode();
+            databaseNode.setTableNames(Arrays.asList("test1"));
+            doCallRealMethod().when(node).getSourceTables(any(),anyList());
+            List<String> result = node.getSourceTables(databaseNode,new ArrayList<>());
+            Assertions.assertEquals(1,result.size());
+        }
+        @Test
+        void test_deduceSchema() {
+            TaskDto taskDto = new TaskDto();
+            taskDto.setSyncType("deduceSchema");
+            when(dataProcessorContext.getTaskDto()).thenReturn(taskDto);
+            DatabaseNode databaseNode = new DatabaseNode();
+            databaseNode.setTableNames(Arrays.asList("test1"));
+            doCallRealMethod().when(node).getSourceTables(any(),anyList());
+            List<String> result = node.getSourceTables(databaseNode,new ArrayList<>());
+            Assertions.assertEquals(0,result.size());
+        }
+    }
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePartitionReadDataNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePartitionReadDataNodeTest.java
new file mode 100644
index 0000000000..c167eca7c5
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePartitionReadDataNodeTest.java
@@ -0,0 +1,89 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.data.pdk;
+
+import base.hazelcast.BaseHazelcastNodeTest;
+import com.tapdata.entity.SyncStage;
+import com.tapdata.entity.TapdataEvent;
+import com.tapdata.entity.TapdataHeartbeatEvent;
+import com.tapdata.tm.commons.cdcdelay.CdcDelay;
+import io.tapdata.aspect.StreamReadFuncAspect;
+import io.tapdata.entity.event.TapEvent;
+import io.tapdata.entity.event.control.HeartbeatEvent;
+import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
+import org.apache.logging.log4j.Logger;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Mockito.*;
+
+public class HazelcastSourcePartitionReadDataNodeTest extends BaseHazelcastNodeTest {
+    private HazelcastSourcePartitionReadDataNode hazelcastSourcePartitionReadDataNode;
+    private CdcDelay cdcDelay;
+    StreamReadFuncAspect streamReadFuncAspect;
+
+
+    @BeforeEach
+    void setUp() {
+        super.allSetup();
+        Logger logger = mock(Logger.class);
+        cdcDelay = mock(CdcDelay.class);
+        hazelcastSourcePartitionReadDataNode = mock(HazelcastSourcePartitionReadDataNode.class);
+
+        ReflectionTestUtils.setField(hazelcastSourcePartitionReadDataNode, "logger", logger);
+        ReflectionTestUtils.setField(hazelcastSourcePartitionReadDataNode, "cdcDelayCalculation", cdcDelay);
+        ReflectionTestUtils.setField(hazelcastSourcePartitionReadDataNode, "dataProcessorContext", dataProcessorContext);
+
+    }
+
+    @DisplayName("test filterAndCalcDelay table is heartBeat")
+    @Test
+    void test1() {
+        streamReadFuncAspect=new StreamReadFuncAspect();
+        ReflectionTestUtils.setField(hazelcastSourcePartitionReadDataNode, "streamReadFuncAspect", streamReadFuncAspect);
+        List<TapEvent> tapEvents = new ArrayList<>();
+        TapUpdateRecordEvent tapUpdateRecordEvent = new TapUpdateRecordEvent();
+        tapUpdateRecordEvent.setTableId("testTableId");
+        tapUpdateRecordEvent.setTime(System.currentTimeMillis());
+        tapEvents.add(tapUpdateRecordEvent);
+        doCallRealMethod().when(hazelcastSourcePartitionReadDataNode).handleStreamEventsReceived(tapEvents, null);
+
+        List<TapdataEvent> tapdataEvents = new ArrayList<>();
+        TapdataHeartbeatEvent heartbeatEvent = new TapdataHeartbeatEvent();
+        tapdataEvents.add(heartbeatEvent);
+
+        when(hazelcastSourcePartitionReadDataNode.wrapTapdataEvent(any(), any(), any())).thenReturn(tapdataEvents);
+        HeartbeatEvent heartbeatEvent2 = new HeartbeatEvent();
+        TapEvent tapEvent = tapUpdateRecordEvent;
+        tapEvent.clone(heartbeatEvent2);
+        when(cdcDelay.filterAndCalcDelay(tapEvent, null, null)).thenReturn(heartbeatEvent2);
+        hazelcastSourcePartitionReadDataNode.handleStreamEventsReceived(tapEvents, null);
+        verify(cdcDelay, times(1)).filterAndCalcDelay(any(), any(), any());
+    }
+    @DisplayName("test streamReadFuncAspect is null ")
+    @Test
+    void test2(){
+        List<TapEvent> tapEvents = new ArrayList<>();
+        TapUpdateRecordEvent tapUpdateRecordEvent = new TapUpdateRecordEvent();
+        tapUpdateRecordEvent.setTableId("testId");
+        tapUpdateRecordEvent.setTime(System.currentTimeMillis());
+        tapEvents.add(tapUpdateRecordEvent);
+        doCallRealMethod().when(hazelcastSourcePartitionReadDataNode).handleStreamEventsReceived(tapEvents, null);
+
+        List<TapdataEvent> tapdataEvents = new ArrayList<>();
+        TapdataHeartbeatEvent heartbeatEvent = new TapdataHeartbeatEvent();
+        tapdataEvents.add(heartbeatEvent);
+
+        when(hazelcastSourcePartitionReadDataNode.wrapTapdataEvent(any(), any(), any())).thenReturn(tapdataEvents);
+        HeartbeatEvent hbEvent = new HeartbeatEvent();
+        TapEvent tapEvent = tapUpdateRecordEvent;
+        tapEvent.clone(hbEvent);
+        when(cdcDelay.filterAndCalcDelay(tapEvent, null, null)).thenReturn(hbEvent);
+        hazelcastSourcePartitionReadDataNode.handleStreamEventsReceived(tapEvents, null);
+        verify(cdcDelay, times(1)).filterAndCalcDelay(any(), any(), any());
+    }
+
+
+}
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNodeTest.java
index 1365c4c0d0..36ec7c0cac 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNodeTest.java
@@ -12,6 +12,8 @@
 import com.tapdata.entity.task.config.TaskConfig;
 import com.tapdata.entity.task.config.TaskRetryConfig;
 import com.tapdata.entity.task.context.DataProcessorContext;
+import com.tapdata.tm.commons.cdcdelay.CdcDelay;
+import com.tapdata.tm.commons.cdcdelay.ICdcDelay;
 import com.tapdata.tm.commons.dag.DAG;
 import com.tapdata.tm.commons.dag.DAGDataServiceImpl;
 import com.tapdata.tm.commons.dag.DDLConfiguration;
@@ -29,11 +31,13 @@
 import io.tapdata.entity.event.ddl.TapDDLEvent;
 import io.tapdata.entity.event.ddl.TapDDLUnknownEvent;
 import io.tapdata.entity.event.dml.TapInsertRecordEvent;
+import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
 import io.tapdata.entity.mapping.DefaultExpressionMatchingMap;
 import io.tapdata.entity.schema.TapField;
 import io.tapdata.entity.schema.TapTable;
 import io.tapdata.entity.utils.InstanceFactory;
 import io.tapdata.error.TaskProcessorExCode_11;
+import io.tapdata.exception.NodeException;
 import io.tapdata.exception.TapCodeException;
 import io.tapdata.flow.engine.V2.ddl.DDLFilter;
 import io.tapdata.flow.engine.V2.ddl.DDLSchemaHandler;
@@ -54,6 +58,7 @@
 import io.tapdata.pdk.core.async.ThreadPoolExecutorEx;
 import io.tapdata.pdk.core.monitor.PDKInvocationMonitor;
 import io.tapdata.schema.TapTableMap;
+import io.tapdata.supervisor.TaskResourceSupervisorManager;
 import lombok.SneakyThrows;
 import org.bson.types.ObjectId;
 import org.junit.Assert;
@@ -68,6 +73,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BiConsumer;
@@ -108,6 +114,8 @@ void startSourceRunner() {
 	class DoInitTest {
 		@BeforeEach
 		void beforeEach() {
+			TaskResourceSupervisorManager taskResourceSupervisorManager=new TaskResourceSupervisorManager();
+			ReflectionTestUtils.setField(mockInstance, "taskResourceSupervisorManager", taskResourceSupervisorManager);
 			doCallRealMethod().when(mockInstance).doInit(jetContext);
 		}
 
@@ -889,13 +897,14 @@ void testLocalTZSyncPoint() {
 		}
 
 		@Test
+		@SneakyThrows
 		@DisplayName("test connection timezone sync point")
 		void testConnTZSyncPoint() {
 			TaskDto.SyncPoint syncPoint = new TaskDto.SyncPoint();
 			syncPoint.setNodeId(instance.getNode().getId());
 			syncPoint.setPointType("connTZ");
 			long syncDateTime = System.currentTimeMillis();
-			syncPoint.setDateTime(System.currentTimeMillis());
+			syncPoint.setDateTime(syncDateTime);
 			List<TaskDto.SyncPoint> syncPoints = new ArrayList<>();
 			syncPoints.add(syncPoint);
 			dataProcessorContext.getTaskDto().setSyncPoints(syncPoints);
@@ -1212,5 +1221,64 @@ public void accept(List<TapEvent> tapEvents, Object o) {
 				excepted,tableName,consumer);
 	}
 
+	@Nested
+	@DisplayName("Method wrapSingleTapdataEvent test")
+	class wrapSingleTapdataEventTest {
+		@BeforeEach
+		void setUp() {
+			instance = spy(instance);
+			ReflectionTestUtils.setField(instance, "obsLogger", mockObsLogger);
+		}
+
+		@Test
+		@DisplayName("test source mode=LOG_COLLECTOR, TapEvent is a TapDDLUnknownEvent, expect return null")
+		void test1() {
+			HazelcastSourcePdkBaseNode.SourceMode sourceMode = HazelcastSourcePdkBaseNode.SourceMode.LOG_COLLECTOR;
+			ReflectionTestUtils.setField(instance, "sourceMode", sourceMode);
+			TapDDLUnknownEvent tapDDLUnknownEvent = new TapDDLUnknownEvent();
+			tapDDLUnknownEvent.setReferenceTime(System.currentTimeMillis());
+			tapDDLUnknownEvent.setTime(System.currentTimeMillis());
+			tapDDLUnknownEvent.setOriginDDL("alter table xxx add new_field number(8,0)");
+			TapdataEvent tapdataEvent = instance.wrapSingleTapdataEvent(tapDDLUnknownEvent, SyncStage.CDC, null, true);
+			assertNull(tapdataEvent);
+			verify(mockObsLogger).warn(any());
+		}
+	}
+
+	@Nested
+	class WrapTapdataEventTest {
+		private HazelcastSourcePdkDataNode hazelcastSourcePdkDataNode;
+		private ICdcDelay cdcDelay;
+
+		@BeforeEach
+		void setUp() {
+			hazelcastSourcePdkDataNode = spy(new HazelcastSourcePdkDataNode(dataProcessorContext));
+			cdcDelay = mock(CdcDelay.class);
+		}
+
+		@DisplayName("test wrapTapdataEvent tapEventTime not null")
+		@Test
+		void test1() {
+			List<TapEvent> tapEvents = new ArrayList<>();
+			TapUpdateRecordEvent tapUpdateRecordEvent = new TapUpdateRecordEvent();
+			tapUpdateRecordEvent.setTableId("testTableId");
+			tapUpdateRecordEvent.setTime(System.currentTimeMillis());
+			tapEvents.add(tapUpdateRecordEvent);
+			ReflectionTestUtils.setField(hazelcastSourcePdkDataNode, "cdcDelayCalculation", cdcDelay);
+			hazelcastSourcePdkDataNode.wrapTapdataEvent(tapEvents, null, null);
+		}
+
+		@DisplayName("test wrapTapdataEvent tapEventTime is null")
+		@Test
+		void test2() {
+			List<TapEvent> tapEvents = new ArrayList<>();
+			TapUpdateRecordEvent tapUpdateRecordEvent = new TapUpdateRecordEvent();
+			tapUpdateRecordEvent.setTableId("testTableId");
+			tapEvents.add(tapUpdateRecordEvent);
+			assertThrows(NodeException.class, () -> hazelcastSourcePdkDataNode.wrapTapdataEvent(tapEvents, null, null));
+		}
+	}
+
+
 
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java
index d5f0e33002..ed60779e06 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java
@@ -10,6 +10,8 @@
 import com.tapdata.entity.task.context.DataProcessorContext;
 import com.tapdata.entity.task.context.ProcessorBaseContext;
 import com.tapdata.mongo.ClientMongoOperator;
+import com.tapdata.tm.commons.cdcdelay.CdcDelay;
+import com.tapdata.tm.commons.cdcdelay.ICdcDelay;
 import com.tapdata.tm.commons.dag.Node;
 import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
 import com.tapdata.tm.commons.dag.nodes.TableNode;
@@ -34,6 +36,8 @@
 import io.tapdata.error.TaskProcessorExCode_11;
 import io.tapdata.exception.NodeException;
 import io.tapdata.exception.TapCodeException;
+import io.tapdata.flow.engine.V2.sharecdc.ShareCdcContext;
+import io.tapdata.flow.engine.V2.sharecdc.ShareCdcTaskContext;
 import io.tapdata.observable.logging.ObsLogger;
 import io.tapdata.pdk.apis.consumer.StreamReadConsumer;
 import io.tapdata.pdk.apis.context.TapConnectorContext;
@@ -57,6 +61,7 @@
 import io.tapdata.schema.SchemaProxy;
 import io.tapdata.schema.TapTableMap;
 import lombok.SneakyThrows;
+import org.apache.logging.log4j.Logger;
 import org.junit.Assert;
 import org.junit.jupiter.api.*;
 import org.mockito.MockedStatic;
@@ -1640,4 +1645,103 @@ void test1(){
 			}
 		}
 	}
+	@Nested
+	class DoShareCdcTest{
+		class HazelcastSourcePdkDataNodeTestNested extends HazelcastSourcePdkDataNode{
+			public HazelcastSourcePdkDataNodeTestNested(DataProcessorContext dataProcessorContext) {
+				super(dataProcessorContext);
+			}
+			@Override
+			public void doShareCdc() throws Exception {
+				super.doShareCdc();
+			}
+
+			@Override
+			protected boolean isRunning() {
+				return super.isRunning();
+			}
+		}
+		class TestTapTableMap extends TapTableMap<String, TapTable> {
+			public TestTapTableMap(String nodeId, long time, Map<String, String> tableNameAndQualifiedNameMap) {
+				super(nodeId, time, tableNameAndQualifiedNameMap);
+			}
+		}
+
+		private HazelcastSourcePdkDataNodeTestNested hazelcastSourcePdkDataNode;
+		private ICdcDelay cdcDelay;
+		@BeforeEach
+		void setUp(){
+			hazelcastSourcePdkDataNode = mock(HazelcastSourcePdkDataNodeTestNested.class);
+
+			Map<String, String> tableNameAndQualifiedNameMap =new HashMap<>();
+			tableNameAndQualifiedNameMap.put("testTable","QualifiedNameTestTable");
+			TapTableMap<String, TapTable> tapTableMap=new TestTapTableMap(",",System.currentTimeMillis(),tableNameAndQualifiedNameMap);
+			TapTable tapTable=new TapTable();
+			tapTableMap.put("testTable",tapTable);
+
+			when(dataProcessorContext.getTapTableMap()).thenReturn(tapTableMap);
+			ReflectionTestUtils.setField(hazelcastSourcePdkDataNode,"dataProcessorContext",dataProcessorContext);
+			when(hazelcastSourcePdkDataNode.isRunning()).thenReturn(true);
+			cdcDelay=mock(CdcDelay.class);
+			ReflectionTestUtils.setField(hazelcastSourcePdkDataNode,"cdcDelayCalculation", cdcDelay);
+
+		}
+		@DisplayName("test do ShareCdc addHeartbeatTable")
+		@Test
+		void test1() throws Exception {
+			doCallRealMethod().when(hazelcastSourcePdkDataNode).doShareCdc();
+			ShareCdcTaskContext shareCdcTaskContext = mock(ShareCdcTaskContext.class);
+			when(hazelcastSourcePdkDataNode.createShareCDCTaskContext()).thenReturn(shareCdcTaskContext);
+			hazelcastSourcePdkDataNode.doShareCdc();
+			verify(cdcDelay,times(1)).addHeartbeatTable(any());
+		}
+	}
+
+	@Nested
+	class generateStreamReadConsumerTest{
+		class HazelcastSourceStreamReadNested extends HazelcastSourcePdkDataNode{
+			public HazelcastSourceStreamReadNested(DataProcessorContext dataProcessorContext) {
+				super(dataProcessorContext);
+			}
+
+			@Override
+			protected boolean isRunning() {
+				return super.isRunning();
+			}
+		}
+		private HazelcastSourceStreamReadNested hazelcastSourcePdkDataNode;
+		private CdcDelay cdcDelay;
+		private SyncProgress syncProgress;
+		@BeforeEach
+		void setUp(){
+			hazelcastSourcePdkDataNode = mock(HazelcastSourceStreamReadNested.class);
+			ReflectionTestUtils.setField(hazelcastSourcePdkDataNode,"dataProcessorContext",dataProcessorContext);
+			cdcDelay=mock(CdcDelay.class);
+			ReflectionTestUtils.setField(hazelcastSourcePdkDataNode,"cdcDelayCalculation", cdcDelay);
+			ReflectionTestUtils.setField(hazelcastSourcePdkDataNode,"logger",mock(Logger.class));
+			syncProgress=new SyncProgress();
+		}
+		@DisplayName("test generateStreamReadConsumer")
+		@Test
+		void test1() throws InterruptedException {
+			PDKMethodInvoker pdkMethodInvoker = mock(PDKMethodInvoker.class);
+			ConnectorNode connectorNode = mock(ConnectorNode.class);
+			when(hazelcastSourcePdkDataNode.isRunning()).thenReturn(true);
+			ReentrantLock reentrantLock = mock(ReentrantLock.class);
+			when(reentrantLock.tryLock(1L, TimeUnit.SECONDS)).thenReturn(true);
+			ReflectionTestUtils.setField(hazelcastSourcePdkDataNode,"sourceRunnerLock",reentrantLock);
+			ReflectionTestUtils.setField(hazelcastSourcePdkDataNode,"cdcDelayCalculation", cdcDelay);
+			ReflectionTestUtils.setField(hazelcastSourcePdkDataNode,"syncProgress", syncProgress);
+			List<TapEvent> tapEvents = new ArrayList<>();
+			TapUpdateRecordEvent tapUpdateRecordEvent = new TapUpdateRecordEvent();
+			tapUpdateRecordEvent.setTableId("testTableId");
+			tapUpdateRecordEvent.setTime(System.currentTimeMillis());
+			tapEvents.add(tapUpdateRecordEvent);
+			doCallRealMethod().when(hazelcastSourcePdkDataNode).generateStreamReadConsumer(any(),any());
+			StreamReadConsumer streamReadConsumer = hazelcastSourcePdkDataNode.generateStreamReadConsumer(connectorNode,pdkMethodInvoker);
+			streamReadConsumer.accept(tapEvents,null);
+			verify(cdcDelay,times(1)).filterAndCalcDelay(any(),any(),any());
+		}
+	}
+
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNodeTest.java
index a66356ff56..ab009f3d6d 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNodeTest.java
@@ -1,17 +1,28 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.processor;
 
 import base.hazelcast.BaseHazelcastNodeTest;
+import com.tapdata.entity.TapdataEvent;
+import com.tapdata.processor.ScriptUtil;
+import com.tapdata.processor.constant.JSEngineEnum;
+import io.tapdata.entity.event.TapBaseEvent;
+import io.tapdata.entity.event.dml.TapDeleteRecordEvent;
+import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
+import lombok.SneakyThrows;
 import org.apache.logging.log4j.LogManager;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
+import org.mockito.ArgumentCaptor;
+import org.mockito.internal.verification.Times;
 import org.springframework.test.util.ReflectionTestUtils;
 
+import javax.script.Invocable;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.function.BiConsumer;
 
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
 
 /**
  * @author samuel
@@ -53,4 +64,132 @@ void testDoCloseThreadLocal() {
 			assertEquals(0, map.size());
 		}
 	}
+	@Nested
+	class tryProcessTest{
+		private TapdataEvent tapdataEvent;
+		private BiConsumer<TapdataEvent, HazelcastProcessorBaseNode.ProcessResult> consumer;
+		private Invocable engine;
+		private TapUpdateRecordEvent tapEvent;
+		@BeforeEach
+		@SneakyThrows
+		void beforeEach(){
+			consumer = mock(BiConsumer.class);
+			ThreadLocal<Map<String, Object>> processContextThreadLocal = ThreadLocal.withInitial(HashMap::new);
+			ReflectionTestUtils.setField(hazelcastJavaScriptProcessorNode,"processContextThreadLocal",processContextThreadLocal);
+			engine = spy(ScriptUtil.getScriptEngine(
+					JSEngineEnum.GRAALVM_JS.getEngineName(),
+					"",
+					null,
+					null,
+					null,
+					null,
+					null,
+					null,
+					true));
+			ReflectionTestUtils.setField(hazelcastJavaScriptProcessorNode,"engine",engine);
+			tapdataEvent = mock(TapdataEvent.class);
+			tapEvent = mock(TapUpdateRecordEvent.class);
+			when(((TapBaseEvent) tapEvent).getTableId()).thenReturn("tableId");
+			when(tapdataEvent.getTapEvent()).thenReturn(tapEvent);
+		}
+		@Test
+		@SneakyThrows
+		void testForStandardJSHandleBefore(){
+			boolean standard = true;
+			ReflectionTestUtils.setField(hazelcastJavaScriptProcessorNode,"standard",standard);
+			Map<String, Object> after = mock(HashMap.class);
+			Map<String, Object> before = mock(HashMap.class);
+			when(tapEvent.getAfter()).thenReturn(after);
+			when(tapEvent.getBefore()).thenReturn(before);
+			when(tapEvent.getType()).thenReturn(302);
+			hazelcastJavaScriptProcessorNode.tryProcess(tapdataEvent, consumer);
+			verify(engine,new Times(2)).invokeFunction(anyString(),any());
+		}
+		@Test
+		@SneakyThrows
+		void testForStandardJSHandleWithoutBefore(){
+			boolean standard = true;
+			ReflectionTestUtils.setField(hazelcastJavaScriptProcessorNode,"standard",standard);
+			Map<String, Object> after = mock(HashMap.class);
+			when(tapEvent.getAfter()).thenReturn(after);
+			when(tapEvent.getBefore()).thenReturn(null);
+			hazelcastJavaScriptProcessorNode.tryProcess(tapdataEvent, consumer);
+			verify(engine,new Times(1)).invokeFunction(anyString(),any());
+		}
+		@Test
+		@SneakyThrows
+		void testForDelEvent(){
+			boolean standard = true;
+			ReflectionTestUtils.setField(hazelcastJavaScriptProcessorNode,"standard",standard);
+			Map<String, Object> before = mock(HashMap.class);
+			TapDeleteRecordEvent tapDelEvent = mock(TapDeleteRecordEvent.class);
+			when(tapDelEvent.getBefore()).thenReturn(before);
+			when(((TapBaseEvent) tapDelEvent).getTableId()).thenReturn("tableId");
+			when(tapdataEvent.getTapEvent()).thenReturn(tapDelEvent);
+			hazelcastJavaScriptProcessorNode.tryProcess(tapdataEvent, consumer);
+			verify(engine,new Times(1)).invokeFunction(anyString(),any());
+		}
+		@Test
+		@SneakyThrows
+		void testForListResult(){
+			boolean standard = true;
+			ReflectionTestUtils.setField(hazelcastJavaScriptProcessorNode,"standard",standard);
+			Map<String, Object> after = mock(HashMap.class);
+			Map<String, Object> before = mock(HashMap.class);
+			tapEvent = new TapUpdateRecordEvent();
+			tapEvent.setAfter(after);
+			tapEvent.setBefore(before);
+			tapEvent.setTableId("tableId");
+			when(tapdataEvent.getTapEvent()).thenReturn(tapEvent);
+			List<Map<String ,Object>> listResult = new ArrayList<>();
+			Map<String, Object> mapRes1 = new HashMap<>();
+			mapRes1.put("col1",1);
+			mapRes1.put("col2",1);
+			Map<String, Object> mapRes2 = new HashMap<>();
+			mapRes2.put("col2",1);
+			mapRes2.put("col3",1);
+			listResult.add(mapRes1);
+			listResult.add(mapRes2);
+			List<Map<String ,Object>> beforeResult = new ArrayList<>();
+			Map<String, Object> mapRes3 = new HashMap<>();
+			mapRes3.put("col2",2);
+			mapRes3.put("col3",2);
+			beforeResult.add(mapRes1);
+			beforeResult.add(mapRes3);
+			doReturn(listResult).doReturn(beforeResult).when(engine).invokeFunction(anyString(),any());
+			when(tapdataEvent.clone()).thenReturn(tapdataEvent);
+			hazelcastJavaScriptProcessorNode.tryProcess(tapdataEvent, consumer);
+
+			ArgumentCaptor<TapdataEvent> captor = ArgumentCaptor.forClass(TapdataEvent.class);
+			ArgumentCaptor<HazelcastProcessorBaseNode.ProcessResult> intCaptor = ArgumentCaptor.forClass(HazelcastProcessorBaseNode.ProcessResult.class);
+			verify(consumer, new Times(2)).accept(captor.capture(), intCaptor.capture());
+			TapdataEvent event = captor.getValue();
+			assertEquals(tapEvent,event.getTapEvent());
+			assertEquals(beforeResult.get(1),((TapUpdateRecordEvent) event.getTapEvent()).getBefore());
+			assertEquals(listResult.get(1),((TapUpdateRecordEvent) event.getTapEvent()).getAfter());
+		}
+		@Test
+		@SneakyThrows
+		void testForFinalJsHandleBefore(){
+			boolean standard = false;
+			ReflectionTestUtils.setField(hazelcastJavaScriptProcessorNode,"standard",standard);
+			Map<String, Object> after = mock(HashMap.class);
+			Map<String, Object> before = mock(HashMap.class);
+			when(tapEvent.getAfter()).thenReturn(after);
+			when(tapEvent.getBefore()).thenReturn(before);
+			hazelcastJavaScriptProcessorNode.tryProcess(tapdataEvent, consumer);
+			verify(engine,new Times(1)).invokeFunction(anyString(),any());
+		}
+		@Test
+		@SneakyThrows
+		void testForFinalJSHandleWithoutBefore(){
+			boolean standard = false;
+			ReflectionTestUtils.setField(hazelcastJavaScriptProcessorNode,"standard",standard);
+			Map<String, Object> after = mock(HashMap.class);
+			when(tapEvent.getAfter()).thenReturn(after);
+			when(tapEvent.getBefore()).thenReturn(null);
+			hazelcastJavaScriptProcessorNode.tryProcess(tapdataEvent, consumer);
+			verify(engine,new Times(1)).invokeFunction(anyString(),any());
+		}
+	}
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateUnionProcessorNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateUnionProcessorNodeTest.java
new file mode 100644
index 0000000000..8b2f30fe83
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateUnionProcessorNodeTest.java
@@ -0,0 +1,47 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.processor;
+
+import base.hazelcast.BaseHazelcastNodeTest;
+import com.tapdata.entity.TapdataEvent;
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.process.MigrateUnionProcessorNode;
+import io.tapdata.entity.event.TapBaseEvent;
+import io.tapdata.entity.event.TapEvent;
+import io.tapdata.entity.event.dml.TapInsertRecordEvent;
+import io.tapdata.pdk.core.utils.CommonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.function.BiConsumer;
+
+import static org.mockito.Mockito.when;
+
+public class HazelcastMigrateUnionProcessorNodeTest extends BaseHazelcastNodeTest {
+    HazelcastMigrateUnionProcessorNode hazelcastMigrateUnionProcessorNode;
+    MigrateUnionProcessorNode unionProcessorNode;
+
+    @BeforeEach
+    void beforeEach() {
+        super.allSetup();
+        unionProcessorNode = new MigrateUnionProcessorNode();
+        unionProcessorNode.setTableName("union_test");
+        when(dataProcessorContext.getNode()).thenReturn((Node) unionProcessorNode);
+        CommonUtils.setProperty("app_type", "DAAS");
+        hazelcastMigrateUnionProcessorNode = new HazelcastMigrateUnionProcessorNode(dataProcessorContext);
+        ReflectionTestUtils.setField(hazelcastMigrateUnionProcessorNode, "obsLogger", mockObsLogger);
+        ReflectionTestUtils.setField(hazelcastMigrateUnionProcessorNode, "clientMongoOperator", mockClientMongoOperator);
+    }
+    @Test
+    void test_main(){
+        TapdataEvent tapdataEvent = new TapdataEvent();
+        TapInsertRecordEvent insertRecordEvent = new TapInsertRecordEvent();
+        insertRecordEvent.setTableId("test");
+        tapdataEvent.setTapEvent(insertRecordEvent);
+        BiConsumer<TapdataEvent, HazelcastProcessorBaseNode.ProcessResult> consumer = (tapdataEvent1, processResult)->{
+            TapBaseEvent tapBaseEvent = (TapBaseEvent) tapdataEvent1.getTapEvent();
+            Assertions.assertEquals("union_test",tapBaseEvent.getTableId());
+        };
+        hazelcastMigrateUnionProcessorNode.tryProcess(tapdataEvent,consumer);
+    }
+}
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskClientTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskClientTest.java
new file mode 100644
index 0000000000..b4da6bdb6c
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskClientTest.java
@@ -0,0 +1,51 @@
+package io.tapdata.flow.engine.V2.task.impl;
+
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.jet.Job;
+import com.tapdata.constant.ConfigurationCenter;
+import com.tapdata.mongo.ClientMongoOperator;
+import com.tapdata.tm.commons.dag.DAG;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.ArrayList;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class HazelcastTaskClientTest {
+    @Test
+    void test_deduceSchema(){
+        TaskDto taskDto = new TaskDto();
+        DAG dag = mock(DAG.class);
+        taskDto.setDag(dag);
+        when(dag.getNodes()).thenReturn(new ArrayList<>());
+        taskDto.setSyncType(TaskDto.SYNC_TYPE_DEDUCE_SCHEMA);
+        HazelcastTaskClient hazelcastTaskClient = new HazelcastTaskClient(mock(Job.class),taskDto,mock(ClientMongoOperator.class),mock(ConfigurationCenter.class),mock(HazelcastInstance.class));
+        Assertions.assertNull(ReflectionTestUtils.getField(hazelcastTaskClient,"autoRecovery"));
+    }
+    @Test
+    void test_testRun(){
+        TaskDto taskDto = new TaskDto();
+        DAG dag = mock(DAG.class);
+        taskDto.setDag(dag);
+        when(dag.getNodes()).thenReturn(new ArrayList<>());
+        taskDto.setSyncType(TaskDto.SYNC_TYPE_TEST_RUN);
+        HazelcastTaskClient hazelcastTaskClient = new HazelcastTaskClient(mock(Job.class),taskDto,mock(ClientMongoOperator.class),mock(ConfigurationCenter.class),mock(HazelcastInstance.class));
+        Assertions.assertNull(ReflectionTestUtils.getField(hazelcastTaskClient,"autoRecovery"));
+    }
+    @Test
+    void test_migrate(){
+        TaskDto taskDto = new TaskDto();
+        taskDto.setId(new ObjectId());
+        DAG dag = mock(DAG.class);
+        taskDto.setDag(dag);
+        when(dag.getNodes()).thenReturn(new ArrayList<>());
+        taskDto.setSyncType(TaskDto.SYNC_TYPE_MIGRATE);
+        HazelcastTaskClient hazelcastTaskClient = new HazelcastTaskClient(mock(Job.class),taskDto,mock(ClientMongoOperator.class),mock(ConfigurationCenter.class),mock(HazelcastInstance.class));
+        Assertions.assertNotNull(ReflectionTestUtils.getField(hazelcastTaskClient,"autoRecovery"));
+    }
+}
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskServiceTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskServiceTest.java
index 908090629a..fc957e2b31 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskServiceTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskServiceTest.java
@@ -458,6 +458,17 @@ void testCreateNodeMigrateTaskDisableIsFalse(){
             HazelcastBaseNode actual = HazelcastTaskService.createNode(taskDto, nodes, edges, node, predecessors, successors, config, connection, databaseType, mergeTableMap, tapTableMap, taskConfig);
             assertEquals(HazelcastJavaScriptProcessorNode.class, actual.getClass());
         }
+		@Test
+		@SneakyThrows
+		@DisplayName("test createNode method for migrate union processor")
+		void testCreateNodeMigrateUnionProcessorNode(){
+			node = mock(MigrateUnionProcessorNode.class);
+			when(node.getType()).thenReturn("migrate_union_processor");
+			when(taskDto.getType()).thenReturn("initial_sync");
+			when(taskDto.getSyncType()).thenReturn("migrate");
+			HazelcastBaseNode actual = HazelcastTaskService.createNode(taskDto, nodes, edges, node, predecessors, successors, config, connection, databaseType, mergeTableMap, tapTableMap, taskConfig);
+			assertEquals(HazelcastMigrateUnionProcessorNode.class, actual.getClass());
+		}
     }
 
     @Nested
diff --git a/iengine/modules/task-resource-supervisor-module/src/test/java/io/tapdata/supervisor/TaskResourceSupervisorManagerTest.java b/iengine/modules/task-resource-supervisor-module/src/test/java/io/tapdata/supervisor/TaskResourceSupervisorManagerTest.java
new file mode 100644
index 0000000000..2e483b4bab
--- /dev/null
+++ b/iengine/modules/task-resource-supervisor-module/src/test/java/io/tapdata/supervisor/TaskResourceSupervisorManagerTest.java
@@ -0,0 +1,124 @@
+package io.tapdata.supervisor;
+
+import com.tapdata.tm.commons.dag.Node;
+import org.junit.jupiter.api.*;
+import org.springframework.test.util.ReflectionTestUtils;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.*;
+
+public class TaskResourceSupervisorManagerTest {
+
+    @Nested
+    class CleanThreadGroupTest{
+
+        @DisplayName("test cleanThreadGroup when destroy success ")
+        @Test
+        void test1(){
+            ThreadGroup threadGroup=new ThreadGroup("testThreadGroup");
+            SupervisorAspectTask supervisorAspectTask=new SupervisorAspectTask();
+            Map<ThreadGroup, TaskNodeInfo> threadGroupMap = new ConcurrentHashMap<>();
+
+            TaskNodeInfo taskNodeInfo=new TaskNodeInfo();
+            taskNodeInfo.setHasLeaked(true);
+            taskNodeInfo.setNodeThreadGroup(threadGroup);
+            threadGroupMap.put(threadGroup,taskNodeInfo);
+            ReflectionTestUtils.setField(supervisorAspectTask,"threadGroupMap",threadGroupMap);
+            taskNodeInfo.setSupervisorAspectTask(supervisorAspectTask);
+
+            TaskResourceSupervisorManager taskResourceSupervisorManager = new TaskResourceSupervisorManager();
+
+            ReflectionTestUtils.setField(taskResourceSupervisorManager,"CLEAN_LEAKED_THREAD_GROUP_THRESHOLD","0");
+            taskResourceSupervisorManager.addTaskSubscribeInfo(taskNodeInfo);
+            taskResourceSupervisorManager.cleanThreadGroup();
+            assertEquals(Boolean.FALSE,taskNodeInfo.isHasLaked());
+        }
+        @DisplayName("test cleanThreadGroup when destroy failed ")
+        @Test
+        void test2(){
+            ThreadGroup spyThreadGroup = new ThreadGroup("123");
+            Thread[] threads = new Thread[1];
+            AtomicBoolean atomicBoolean=new AtomicBoolean(false);
+            threads[0] = new Thread(spyThreadGroup, () -> {
+                while (!atomicBoolean.get()) {
+                }
+            });
+            Node node = mock(Node.class);
+            when(node.getId()).thenReturn("testId");
+
+            TaskNodeInfo taskNodeInfo=new TaskNodeInfo();
+            taskNodeInfo.setHasLeaked(true);
+            taskNodeInfo.setNodeThreadGroup(spyThreadGroup);
+
+            TaskResourceSupervisorManager taskResourceSupervisorManager = new TaskResourceSupervisorManager();
+            ReflectionTestUtils.setField(taskResourceSupervisorManager,"CLEAN_LEAKED_THREAD_GROUP_THRESHOLD","0");
+            taskResourceSupervisorManager.addTaskSubscribeInfo(taskNodeInfo);
+            taskResourceSupervisorManager.cleanThreadGroup();
+            atomicBoolean.set(true);
+            assertEquals(Boolean.TRUE,taskNodeInfo.isHasLaked());
+        }
+    }
+    @Nested
+    class TestSummary{
+        Set<SupervisorAspectTask> aliveSet;
+        Set<SupervisorAspectTask> leakedSet;
+        TaskNodeInfo taskNodeInfo;
+        TaskResourceSupervisorManager taskResourceSupervisorManager;
+        @BeforeEach
+        void init(){
+            aliveSet=new HashSet<>();
+            leakedSet=new HashSet<>();
+            taskNodeInfo=new TaskNodeInfo();
+            taskResourceSupervisorManager=new TaskResourceSupervisorManager();
+        }
+        @DisplayName("test summary when task is stop ,and thread group destroy success")
+        @Test
+        void test1(){
+            SupervisorAspectTask supervisorAspectTask = new SupervisorAspectTask();
+            ThreadGroup threadGroup=new ThreadGroup("test");
+            taskNodeInfo.setHasLeaked(true);
+            taskNodeInfo.setNodeThreadGroup(threadGroup);
+            taskNodeInfo.setSupervisorAspectTask(supervisorAspectTask);
+            Set<TaskNodeInfo> summarySet =new HashSet<>();
+            summarySet.add(taskNodeInfo);
+            taskResourceSupervisorManager.summary(summarySet,aliveSet,leakedSet);
+            assertEquals(Boolean.FALSE,taskNodeInfo.isHasLaked());
+        }
+        @DisplayName("test summary when task is alive")
+        @Test
+        void test2(){
+            ThreadGroup threadGroup=new ThreadGroup("test");
+            taskNodeInfo.setHasLeaked(false);
+            taskNodeInfo.setNodeThreadGroup(threadGroup);
+            taskNodeInfo.setSupervisorAspectTask(new SupervisorAspectTask());
+            Set<TaskNodeInfo> summarySet =new HashSet<>();
+            summarySet.add(taskNodeInfo);
+            taskResourceSupervisorManager.summary(summarySet,aliveSet,leakedSet);
+            assertEquals(1,aliveSet.size());
+        }
+        @DisplayName("test summary when task is stop,thread group destroy failed lead to leaked")
+        @Test
+        void test3(){
+            ThreadGroup spyThreadGroup = new ThreadGroup("123");
+            Thread[] threads = new Thread[1];
+            AtomicBoolean atomicBoolean=new AtomicBoolean(false);
+            threads[0] = new Thread(spyThreadGroup, () -> {
+                while (!atomicBoolean.get()) {
+                }
+            });
+            threads[0].start();
+            taskNodeInfo.setHasLeaked(true);
+            taskNodeInfo.setNodeThreadGroup(spyThreadGroup);
+            taskNodeInfo.setSupervisorAspectTask(new SupervisorAspectTask());
+            Set<TaskNodeInfo> summarySet =new HashSet<>();
+            summarySet.add(taskNodeInfo);
+            taskResourceSupervisorManager.summary(summarySet,aliveSet,leakedSet);
+            atomicBoolean.set(true);
+            assertEquals(1,leakedSet.size());
+        }
+    }
+}
diff --git a/iengine/modules/task-resource-supervisor-module/src/test/java/io/tapdata/supervisor/TestSupervisorAspectTask.java b/iengine/modules/task-resource-supervisor-module/src/test/java/io/tapdata/supervisor/TestSupervisorAspectTask.java
new file mode 100644
index 0000000000..2562ef7cc8
--- /dev/null
+++ b/iengine/modules/task-resource-supervisor-module/src/test/java/io/tapdata/supervisor/TestSupervisorAspectTask.java
@@ -0,0 +1,146 @@
+package io.tapdata.supervisor;
+
+import com.tapdata.entity.task.context.DataProcessorContext;
+import com.tapdata.tm.commons.dag.Node;
+import io.tapdata.aspect.TaskStopAspect;
+import io.tapdata.aspect.supervisor.DataNodeThreadGroupAspect;
+import io.tapdata.aspect.supervisor.ThreadGroupAspect;
+import io.tapdata.threadgroup.ConnectorOnTaskThreadGroup;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.*;
+
+public class TestSupervisorAspectTask {
+    private String associateId;
+    private SupervisorAspectTask supervisorAspectTask;
+    TaskResourceSupervisorManager taskResourceSupervisorManager;
+    DataProcessorContext dataProcessorContext;
+    Node mockNode;
+    @BeforeEach
+    void setUp(){
+        associateId = "associateId";
+        supervisorAspectTask=new SupervisorAspectTask();
+        taskResourceSupervisorManager=new TaskResourceSupervisorManager();
+        mockNode = mock(Node.class);
+        when(mockNode.getId()).thenReturn("testNodeId");
+        dataProcessorContext = mock(DataProcessorContext.class);
+        when(dataProcessorContext.getNode()).thenReturn(mockNode);
+    }
+
+
+    @DisplayName("test method addAspect reuse taskNodeInfo")
+    @Test
+    void test1(){
+
+        Node mockNode = mock(Node.class);
+
+        DataProcessorContext dataProcessorContext = mock(DataProcessorContext.class);
+        when(dataProcessorContext.getNode()).thenReturn(mockNode);
+        when(mockNode.getId()).thenReturn("testNodeId");
+        when(mockNode.getName()).thenReturn("testName");
+
+        ConnectorOnTaskThreadGroup connectorOnTaskThreadGroup = new ConnectorOnTaskThreadGroup(dataProcessorContext);
+        TaskNodeInfo taskNodeInfo = new TaskNodeInfo();
+        taskNodeInfo.setNode(mockNode);
+        taskNodeInfo.setSupervisorAspectTask(supervisorAspectTask);
+        taskNodeInfo.setAssociateId(associateId);
+        taskNodeInfo.setNodeThreadGroup(connectorOnTaskThreadGroup);
+
+        taskResourceSupervisorManager.addTaskSubscribeInfo(taskNodeInfo);
+
+        ReflectionTestUtils.setField(supervisorAspectTask, "taskResourceSupervisorManager", taskResourceSupervisorManager);
+
+
+        ThreadGroupAspect aspect=new DataNodeThreadGroupAspect(mockNode,associateId,connectorOnTaskThreadGroup);
+
+        supervisorAspectTask.addAspect(aspect);
+        assertEquals(1,supervisorAspectTask.getThreadGroupMap().size());
+        assertEquals(taskNodeInfo,supervisorAspectTask.getThreadGroupMap().get(connectorOnTaskThreadGroup));
+    }
+    @DisplayName("test method addAspect not reuse taskNodeInfo")
+    @Test
+    void test2(){
+
+
+        Node mockNode = mock(Node.class);
+        when(mockNode.getId()).thenReturn("testNodeId");
+
+        DataProcessorContext dataProcessorContext = mock(DataProcessorContext.class);
+        when(dataProcessorContext.getNode()).thenReturn(mockNode);
+
+        ConnectorOnTaskThreadGroup connectorOnTaskThreadGroup = new ConnectorOnTaskThreadGroup(dataProcessorContext);
+        ReflectionTestUtils.setField(supervisorAspectTask, "taskResourceSupervisorManager", taskResourceSupervisorManager);
+
+        String associateId = "associateId";
+        ThreadGroupAspect aspect=new DataNodeThreadGroupAspect(mockNode,associateId,connectorOnTaskThreadGroup);
+        supervisorAspectTask.addAspect(aspect);
+        assertEquals(1,taskResourceSupervisorManager.getTaskNodeInfos().size());
+
+    }
+    @DisplayName("test on stop release threadGroup success")
+    @Test
+    void test3(){
+        ReflectionTestUtils.setField(supervisorAspectTask, "taskResourceSupervisorManager", taskResourceSupervisorManager);
+
+        Node mockNode = mock(Node.class);
+        when(mockNode.getId()).thenReturn("testNodeId");
+        DataProcessorContext dataProcessorContext = mock(DataProcessorContext.class);
+        when(dataProcessorContext.getNode()).thenReturn(mockNode);
+        ConnectorOnTaskThreadGroup connectorOnTaskThreadGroup = new ConnectorOnTaskThreadGroup(dataProcessorContext);
+
+        TaskNodeInfo taskNodeInfo = new TaskNodeInfo();
+        taskNodeInfo.setNode(mockNode);
+        taskNodeInfo.setSupervisorAspectTask(supervisorAspectTask);
+        taskNodeInfo.setAssociateId(associateId);
+        taskNodeInfo.setNodeThreadGroup(connectorOnTaskThreadGroup);
+        taskResourceSupervisorManager.addTaskSubscribeInfo(taskNodeInfo);
+
+        Map<ThreadGroup, TaskNodeInfo> threadGroupMap = new ConcurrentHashMap<>();
+        threadGroupMap.put(connectorOnTaskThreadGroup,taskNodeInfo);
+
+        ReflectionTestUtils.setField(supervisorAspectTask, "threadGroupMap", threadGroupMap);
+        TaskStopAspect taskStopAspect=new TaskStopAspect();
+        supervisorAspectTask.onStop(taskStopAspect);
+        assertEquals(Boolean.FALSE,taskNodeInfo.isHasLaked());
+    }
+    @DisplayName("test on stop release threadGroup failed")
+    @Test
+    void test4(){
+
+        ReflectionTestUtils.setField(supervisorAspectTask, "taskResourceSupervisorManager", taskResourceSupervisorManager);
+
+        ConnectorOnTaskThreadGroup connectorOnTaskThreadGroup = new ConnectorOnTaskThreadGroup(dataProcessorContext);
+        Thread[] threads = new Thread[1];
+        AtomicBoolean atomicBoolean=new AtomicBoolean(false);
+        threads[0] = new Thread(connectorOnTaskThreadGroup, () -> {
+            while (!atomicBoolean.get()) {
+            }
+        });
+        threads[0].start();
+        TaskNodeInfo taskNodeInfo = new TaskNodeInfo();
+        taskNodeInfo.setNode(mockNode);
+        taskNodeInfo.setSupervisorAspectTask(supervisorAspectTask);
+        taskNodeInfo.setAssociateId(associateId);
+        taskNodeInfo.setNodeThreadGroup(connectorOnTaskThreadGroup);
+
+        taskResourceSupervisorManager.addTaskSubscribeInfo(taskNodeInfo);
+
+        Map<ThreadGroup, TaskNodeInfo> threadGroupMap = new ConcurrentHashMap<>();
+        threadGroupMap.put(connectorOnTaskThreadGroup,taskNodeInfo);
+        ReflectionTestUtils.setField(supervisorAspectTask, "threadGroupMap", threadGroupMap);
+
+        TaskStopAspect taskStopAspect=new TaskStopAspect();
+        supervisorAspectTask.onStop(taskStopAspect);
+        assertEquals(Boolean.TRUE,taskNodeInfo.isHasLaked());
+        assertEquals(1,taskResourceSupervisorManager.getTaskNodeInfos().size());
+        atomicBoolean.set(true);
+    }
+}
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/base/aop/MeasureAOPTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/base/aop/MeasureAOPTest.java
new file mode 100644
index 0000000000..9975bb85a9
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/base/aop/MeasureAOPTest.java
@@ -0,0 +1,241 @@
+package com.tapdata.tm.base.aop;
+
+import com.google.common.collect.Maps;
+import com.tapdata.tm.alarm.constant.AlarmComponentEnum;
+import com.tapdata.tm.alarm.constant.AlarmStatusEnum;
+import com.tapdata.tm.alarm.constant.AlarmTypeEnum;
+import com.tapdata.tm.alarm.entity.AlarmInfo;
+import com.tapdata.tm.alarm.service.AlarmService;
+import com.tapdata.tm.commons.task.constant.AlarmKeyEnum;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.commons.task.dto.alarm.AlarmRuleDto;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.message.constant.Level;
+import com.tapdata.tm.task.service.TaskService;
+import com.tapdata.tm.user.service.UserService;
+import com.tapdata.tm.utils.MongoUtils;
+import io.tapdata.common.sample.request.Sample;
+import io.tapdata.common.sample.request.SampleRequest;
+import org.aspectj.lang.JoinPoint;
+import org.bson.types.ObjectId;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+public class MeasureAOPTest {
+    @Nested
+    class TaskIncrementDelayAlarmTest{
+        private TaskService taskService;
+        private AlarmService alarmService;
+        private UserService userService;
+        Map<String, Map<String, AtomicInteger>> obsMap = Maps.newConcurrentMap();
+        String taskId = "66837fb973828322f83f7074";
+        List<AlarmInfo> alarmInfos=new ArrayList<>();
+
+        @BeforeEach
+        void setUp() {
+            taskService = mock(TaskService.class);
+            alarmService = mock(AlarmService.class);
+            userService = mock(UserService.class);
+            Map<String,AtomicInteger> infoMap=new HashMap<>();
+            infoMap.put(taskId+"-replicateLag",new AtomicInteger(50000));
+            obsMap.put(taskId,infoMap);
+            AlarmInfo alarmInfo = AlarmInfo.builder().component(AlarmComponentEnum.FE).status(AlarmStatusEnum.ING).type(AlarmTypeEnum.SYNCHRONIZATIONTASK_ALARM).metric(AlarmKeyEnum.TASK_INCREMENT_DELAY).firstOccurrenceTime(new Date(System.currentTimeMillis()-50000)).build();
+            alarmInfos.add(alarmInfo);
+            when(alarmService.find(anyString(), any(), eq(AlarmKeyEnum.TASK_INCREMENT_DELAY))).thenReturn(alarmInfos);
+        }
+        @DisplayName("test taskIncrementDelayAlarm method when equalsFlag is greater and replicateLag not greater alarmRule ms then taskReplicateLagCount is less than alarmRule point")
+        @Test
+        void test1(){
+            AlarmRuleDto alarmRuleDto = getAlarmRuleDto();
+            TaskDto taskDto = new TaskDto();
+            taskDto.setId(new ObjectId(taskId));
+            taskDto.setCurrentEventTimestamp(System.currentTimeMillis());
+            MeasureAOP measureAOP = new MeasureAOP(taskService, alarmService, userService);
+            ReflectionTestUtils.setField(measureAOP, "obsMap", obsMap);
+            doAnswer(invocationOnMock -> {
+                AlarmInfo alarmInfo = (AlarmInfo)invocationOnMock.getArgument(0);
+                assertEquals(Level.RECOVERY,alarmInfo.getLevel());
+                assertEquals(AlarmStatusEnum.RECOVER,alarmInfo.getStatus());
+                assertEquals("TASK_INCREMENT_DELAY_RECOVER",alarmInfo.getSummary());
+                return null;
+            }).when(alarmService).save(any());
+            measureAOP.taskIncrementDelayAlarm(taskDto,taskId,10,alarmRuleDto);
+
+        }
+        @DisplayName("test taskIncrementDelayAlarm method when equalsFlag is greater and replicateLag greater alarmRule ms and taskReplicateLagCount is greater than alarmRule point")
+        @Test
+        void test2(){
+            AlarmRuleDto alarmRuleDto = getAlarmRuleDto();
+            TaskDto taskDto = new TaskDto();
+            taskDto.setId(new ObjectId(taskId));
+            taskDto.setCurrentEventTimestamp(System.currentTimeMillis());
+            MeasureAOP measureAOP = new MeasureAOP(taskService, alarmService, userService);
+            ReflectionTestUtils.setField(measureAOP, "obsMap", obsMap);
+            doAnswer(invocationOnMock -> {
+                AlarmInfo alarmInfo = (AlarmInfo)invocationOnMock.getArgument(0);
+                assertEquals(Level.WARNING,alarmInfo.getLevel());
+                assertEquals(AlarmStatusEnum.ING,alarmInfo.getStatus());
+                Map<String, Object> param = alarmInfo.getParam();
+                String flag = param.get("flag").toString();
+                assertEquals(MeasureAOP.GREATER,flag);
+                return null;
+            }).when(alarmService).save(any());
+            measureAOP.taskIncrementDelayAlarm(taskDto, taskId, 500000, alarmRuleDto);
+        }
+        @DisplayName("test taskIncrementDelayAlarm method when equalsFlag is greater and replicateLag greater alarmRule ms and and info map is null ,so taskReplicateLagCount is less than alarmRule point")
+        @Test
+        void test3(){
+            AlarmRuleDto alarmRuleDto = getAlarmRuleDto();
+            TaskDto taskDto = new TaskDto();
+            taskDto.setId(new ObjectId(taskId));
+            taskDto.setCurrentEventTimestamp(System.currentTimeMillis());
+            MeasureAOP measureAOP = new MeasureAOP(taskService, alarmService, userService);
+            doAnswer(invocationOnMock -> {
+                AlarmInfo alarmInfo = (AlarmInfo)invocationOnMock.getArgument(0);
+                assertEquals(Level.RECOVERY,alarmInfo.getLevel());
+                assertEquals(AlarmStatusEnum.RECOVER,alarmInfo.getStatus());
+                assertEquals("TASK_INCREMENT_DELAY_RECOVER",alarmInfo.getSummary());
+                return null;
+            }).when(alarmService).save(any());
+            measureAOP.taskIncrementDelayAlarm(taskDto, taskId, 500000, alarmRuleDto);
+        }
+        @DisplayName("test taskIncrementDelayAlarm method when equalsFlag is less and replicateLag less alarmRule ms and and info map is not null ,so taskReplicateLagCount is greater than alarmRule point")
+        @Test
+        void test4(){
+            AlarmRuleDto alarmRuleDto = getAlarmRuleDto();
+            alarmRuleDto.setEqualsFlag(-1);
+            TaskDto taskDto = new TaskDto();
+            taskDto.setId(new ObjectId(taskId));
+            taskDto.setCurrentEventTimestamp(System.currentTimeMillis());
+            MeasureAOP measureAOP = new MeasureAOP(taskService, alarmService, userService);
+            ReflectionTestUtils.setField(measureAOP, "obsMap", obsMap);
+            doAnswer(invocationOnMock -> {
+                AlarmInfo alarmInfo = (AlarmInfo)invocationOnMock.getArgument(0);
+                assertEquals(Level.WARNING,alarmInfo.getLevel());
+                assertEquals(AlarmStatusEnum.ING,alarmInfo.getStatus());
+                Map<String, Object> param = alarmInfo.getParam();
+                String flag = param.get("flag").toString();
+                assertEquals(MeasureAOP.LESS,flag);
+                return null;
+            }).when(alarmService).save(any());
+            measureAOP.taskIncrementDelayAlarm(taskDto, taskId, 3, alarmRuleDto);
+        }
+        @DisplayName("test taskIncrementDelayAlarm method when equalsFlag is less and replicateLag less alarmRule ms and and info map is not null ,so taskReplicateLagCount is greater than alarmRule point and continueTime greater 1 MINUTE")
+        @Test
+        void test5(){
+            AlarmInfo listAlarmInfo = alarmInfos.get(0);
+            listAlarmInfo.setFirstOccurrenceTime(new Date(System.currentTimeMillis()-1000000000));
+            AlarmRuleDto alarmRuleDto = getAlarmRuleDto();
+            alarmRuleDto.setEqualsFlag(-1);
+            TaskDto taskDto = new TaskDto();
+            taskDto.setId(new ObjectId(taskId));
+            taskDto.setCurrentEventTimestamp(System.currentTimeMillis());
+            MeasureAOP measureAOP = new MeasureAOP(taskService, alarmService, userService);
+            ReflectionTestUtils.setField(measureAOP, "obsMap", obsMap);
+            doAnswer(invocationOnMock -> {
+                AlarmInfo alarmInfo = (AlarmInfo)invocationOnMock.getArgument(0);
+                assertEquals(Level.WARNING,alarmInfo.getLevel());
+                assertEquals(AlarmStatusEnum.ING,alarmInfo.getStatus());
+                Map<String, Object> param = alarmInfo.getParam();
+                String flag = param.get("flag").toString();
+                assertEquals(MeasureAOP.LESS,flag);
+                assertEquals("TASK_INCREMENT_DELAY_ALWAYS",alarmInfo.getSummary());
+                return null;
+            }).when(alarmService).save(any());
+            measureAOP.taskIncrementDelayAlarm(taskDto, taskId, 3, alarmRuleDto);
+        }
+    }
+    @Nested
+    class AddAgentMeasurementTest{
+        private TaskService taskService;
+        private AlarmService alarmService;
+        private UserService userService;
+        private MeasureAOP measureAOP;
+        private TaskDto taskDto;
+        UserDetail userDetail;
+
+        AlarmRuleDto alarmRuleDto;
+
+        public String taskId = "6682d405494d8d2a5c957f38";
+        public String userId = "testUserId";
+
+
+        @BeforeEach
+        void setUp() {
+            taskService = mock(TaskService.class);
+            alarmService = mock(AlarmService.class);
+            userService = mock(UserService.class);
+            measureAOP = mock(MeasureAOP.class);
+            ReflectionTestUtils.setField(measureAOP, "taskService", taskService);
+            ReflectionTestUtils.setField(measureAOP, "alarmService", alarmService);
+            ReflectionTestUtils.setField(measureAOP, "userService", userService);
+
+            taskDto = new TaskDto();
+            taskDto.setId(MongoUtils.toObjectId(taskId));
+            when(taskService.findByTaskId(any())).thenReturn(taskDto);
+
+            alarmRuleDto = getAlarmRuleDto();
+            List<AlarmRuleDto> alarmRuleDtoList=new ArrayList<>();
+            alarmRuleDtoList.add(alarmRuleDto);
+
+            Map<String, List<AlarmRuleDto>> ruleMap=new HashMap<>();
+            ruleMap.put(taskId,alarmRuleDtoList);
+            when(alarmService.getAlarmRuleDtos(any())).thenReturn(ruleMap);
+
+            userDetail = mock(UserDetail.class);
+            when(userService.loadUserById(any())).thenReturn(userDetail);
+
+
+        }
+        @DisplayName("test addAgentMeasurement when sample type is task and have alarmRule is TASK_INCREMENT_DELAY")
+        @Test
+        void test1(){
+            when(alarmService.checkOpen(taskDto,null, AlarmKeyEnum.TASK_INCREMENT_DELAY, null, userDetail)).thenReturn(true);
+            List<SampleRequest> list = new ArrayList<>();
+            SampleRequest sampleRequest=new SampleRequest();
+
+            Map<String, String> tags =new HashMap<>();
+            tags.put("type","task");
+            tags.put("taskId",taskId);
+            sampleRequest.setTags(tags);
+
+            Map<String,Number> vs=new HashMap<>();
+            vs.put("replicateLag",20000);
+            Sample sample=new Sample();
+            sample.setVs(vs);
+            sampleRequest.setSample(sample);
+
+            list.add(sampleRequest);
+
+            JoinPoint joinPoint = mock(JoinPoint.class);
+
+            Object[] objects = new Object[10];
+            objects[0]=list;
+            when(joinPoint.getArgs()).thenReturn(objects);
+            doCallRealMethod().when(measureAOP).addAgentMeasurement(joinPoint);
+            measureAOP.addAgentMeasurement(joinPoint);
+            verify(measureAOP,times(1)).taskIncrementDelayAlarm(taskDto,taskId, 20000, alarmRuleDto);
+        }
+    }
+
+    @NotNull
+    private static AlarmRuleDto getAlarmRuleDto() {
+        AlarmRuleDto alarmRuleDto = new AlarmRuleDto();
+        alarmRuleDto.setKey(AlarmKeyEnum.TASK_INCREMENT_DELAY);
+        alarmRuleDto.setPoint(24);
+        alarmRuleDto.setEqualsFlag(1);
+        alarmRuleDto.setMs(40000);
+        return alarmRuleDto;
+    }
+}
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/inspect/service/InspectCronJobTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/inspect/service/InspectCronJobTest.java
new file mode 100644
index 0000000000..f1d47e9235
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/inspect/service/InspectCronJobTest.java
@@ -0,0 +1,158 @@
+package com.tapdata.tm.inspect.service;
+
+import com.tapdata.tm.base.dto.Where;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.inspect.constant.InspectStatusEnum;
+import com.tapdata.tm.inspect.dto.InspectDto;
+import com.tapdata.tm.user.service.UserService;
+import com.tapdata.tm.utils.SpringContextHelper;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.quartz.JobDetail;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.JobKey;
+import org.slf4j.Logger;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class InspectCronJobTest {
+    Logger log;
+    UserDetail userDetail;
+    InspectCronJob job;
+
+    @BeforeEach
+    void init() {
+        job = mock(InspectCronJob.class);
+        userDetail = mock(UserDetail.class);
+        log = mock(Logger.class);
+        mockSlf4jLog(job, log);
+    }
+
+    @Nested
+    class ExecuteTest {
+        JobExecutionContext jobExecutionContext;
+        InspectService inspectService;
+        InspectTaskService inspectTaskService;
+        UserService userService;
+        JobKey jobKey;
+        JobDetail jobDetail;
+        InspectDto inspectDto;
+        @BeforeEach
+        void init() throws JobExecutionException {
+            jobExecutionContext = mock(JobExecutionContext.class);
+            inspectService = mock(InspectService.class);
+            inspectTaskService = mock(InspectTaskService.class);
+            userService = mock(UserService.class);
+            jobKey = mock(JobKey.class);
+            jobDetail = mock(JobDetail.class);
+            inspectDto = mock(InspectDto.class);
+
+
+
+            when(jobKey.getName()).thenReturn(new ObjectId().toHexString());
+            when(jobKey.getGroup()).thenReturn("group");
+            when(jobDetail.getKey()).thenReturn(jobKey);
+            when(jobExecutionContext.getJobDetail()).thenReturn(jobDetail);
+
+            when(inspectService.findById(any(ObjectId.class))).thenReturn(inspectDto);
+
+            when(inspectDto.getUserId()).thenReturn(new ObjectId().toHexString());
+            when(userService.loadUserById(any(ObjectId.class))).thenReturn(userDetail);
+
+            when(inspectTaskService.executeInspect(any(Where.class), any(InspectDto.class), any(UserDetail.class))).thenReturn(mock(InspectDto.class));
+
+            doNothing().when(log).info(anyString());
+            doNothing().when(log).info(anyString(), anyString(), anyString());
+            doCallRealMethod().when(job).execute(jobExecutionContext);
+        }
+
+        @Test
+        void testNormal() {
+            when(inspectDto.getStatus()).thenReturn(InspectStatusEnum.WAITING.getValue());
+            try (MockedStatic<SpringContextHelper> sch = mockStatic(SpringContextHelper.class)) {
+                sch.when(() -> SpringContextHelper.getBean(InspectService.class)).thenReturn(inspectService);
+                sch.when(() -> SpringContextHelper.getBean(InspectTaskService.class)).thenReturn(inspectTaskService);
+                sch.when(() -> SpringContextHelper.getBean(UserService.class)).thenReturn(userService);
+                Assertions.assertDoesNotThrow(() -> job.execute(jobExecutionContext));
+                verify(jobKey, times(1)).getName();
+                verify(jobKey, times(1)).getGroup();
+                verify(jobExecutionContext, times(1)).getJobDetail();
+                verify(jobDetail, times(1)).getKey();
+                verify(inspectService, times(1)).findById(any(ObjectId.class));
+                verify(inspectDto, times(1)).getStatus();
+                verify(inspectDto, times(1)).getUserId();
+                verify(userService, times(1)).loadUserById(any(ObjectId.class));
+                verify(inspectTaskService, times(1)).executeInspect(any(Where.class), any(InspectDto.class), any(UserDetail.class));
+            }
+        }
+
+        @Test
+        void testRUNNING() {
+            when(inspectDto.getStatus()).thenReturn(InspectStatusEnum.RUNNING.getValue());
+            try (MockedStatic<SpringContextHelper> sch = mockStatic(SpringContextHelper.class)) {
+                sch.when(() -> SpringContextHelper.getBean(InspectService.class)).thenReturn(inspectService);
+                sch.when(() -> SpringContextHelper.getBean(InspectTaskService.class)).thenReturn(inspectTaskService);
+                sch.when(() -> SpringContextHelper.getBean(UserService.class)).thenReturn(userService);
+                Assertions.assertDoesNotThrow(() -> job.execute(jobExecutionContext));
+                verify(jobKey, times(1)).getName();
+                verify(jobKey, times(1)).getGroup();
+                verify(jobExecutionContext, times(1)).getJobDetail();
+                verify(jobDetail, times(1)).getKey();
+                verify(inspectService, times(1)).findById(any(ObjectId.class));
+                verify(inspectDto, times(1)).getStatus();
+                verify(inspectDto, times(0)).getUserId();
+                verify(userService, times(0)).loadUserById(any(ObjectId.class));
+                verify(inspectTaskService, times(0)).executeInspect(any(Where.class), any(InspectDto.class), any(UserDetail.class));
+            }
+        }
+
+        @Test
+        void testSCHEDULING() {
+            when(inspectDto.getStatus()).thenReturn(InspectStatusEnum.SCHEDULING.getValue());
+            try (MockedStatic<SpringContextHelper> sch = mockStatic(SpringContextHelper.class)) {
+                sch.when(() -> SpringContextHelper.getBean(InspectService.class)).thenReturn(inspectService);
+                sch.when(() -> SpringContextHelper.getBean(InspectTaskService.class)).thenReturn(inspectTaskService);
+                sch.when(() -> SpringContextHelper.getBean(UserService.class)).thenReturn(userService);
+                Assertions.assertDoesNotThrow(() -> job.execute(jobExecutionContext));
+                verify(jobKey, times(1)).getName();
+                verify(jobKey, times(1)).getGroup();
+                verify(jobExecutionContext, times(1)).getJobDetail();
+                verify(jobDetail, times(1)).getKey();
+                verify(inspectService, times(1)).findById(any(ObjectId.class));
+                verify(inspectDto, times(1)).getStatus();
+                verify(inspectDto, times(0)).getUserId();
+                verify(userService, times(0)).loadUserById(any(ObjectId.class));
+                verify(inspectTaskService, times(0)).executeInspect(any(Where.class), any(InspectDto.class), any(UserDetail.class));
+            }
+        }
+    }
+
+    public static void mockSlf4jLog(Object mockTo, Logger log) {
+        try {
+            Field logF = mockTo.getClass().getDeclaredField("log");
+            Field modifiersField = Field.class.getDeclaredField("modifiers");
+            modifiersField.setAccessible(true);
+            modifiersField.setInt(logF, logF.getModifiers() & ~Modifier.FINAL);
+            logF.setAccessible(true);
+            logF.set(mockTo, log);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/inspect/service/InspectDetailsServiceTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/inspect/service/InspectDetailsServiceTest.java
new file mode 100644
index 0000000000..311929239a
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/inspect/service/InspectDetailsServiceTest.java
@@ -0,0 +1,116 @@
+package com.tapdata.tm.inspect.service;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.inspect.bean.Source;
+import com.tapdata.tm.inspect.bean.Stats;
+import com.tapdata.tm.inspect.dto.InspectDetailsDto;
+import com.tapdata.tm.inspect.dto.InspectResultDto;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.mongodb.core.query.Criteria;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipOutputStream;
+
+import static org.mockito.Mockito.*;
+
+public class InspectDetailsServiceTest {
+
+    @Nested
+    class ExportInspectDetails{
+
+        @Test
+        void testExport() throws IOException {
+            InspectDetailsService inspectDetailsService = mock(InspectDetailsService.class);
+            InspectDetailsDto inspectDetails = new InspectDetailsDto();
+            String inspectResultId = ObjectId.get().toHexString();
+            inspectDetails.setInspectResultId(inspectResultId);
+            ZipOutputStream zipOutputStream = mock(ZipOutputStream.class);
+            UserDetail userDetail = mock(UserDetail.class);
+            InspectResultService inspectResultService = mock(InspectResultService.class);
+            InspectResultDto inspectResultDto = new InspectResultDto();
+            List<Stats> stats = new ArrayList<>();
+            String taskId = ObjectId.get().toHexString();
+            Stats stats2 = new Stats();
+            stats2.setTaskId(ObjectId.get().toHexString());
+            stats.add(stats2);
+            Stats stats1= new Stats();
+            stats1.setTaskId(taskId);
+            stats1.setResult("failed");
+            Source source = new Source();
+            source.setConnectionName("mysql");
+            source.setTable("test");
+            Source target = new Source();
+            target.setConnectionName("mysql");
+            target.setTable("check_test");
+            stats1.setSource(source);
+            stats1.setTarget(target);
+            long targetTotal = 2l;
+            stats1.setTargetTotal(targetTotal);
+            long sourceTotal = 3l;
+            stats1.setSourceTotal(sourceTotal);
+            stats1.setSourceOnly(0l);
+            stats1.setTargetOnly(1l);
+            stats1.setRowFailed(0l);
+            stats.add(stats1);
+
+            inspectResultDto.setStats(stats);
+            when(inspectResultService.findById(new ObjectId(inspectResultId),userDetail)).thenReturn(inspectResultDto);
+            org.springframework.data.mongodb.core.query.Query query = org.springframework.data.mongodb.core.query.Query.query(Criteria.where("inspectResultId").is(inspectDetails.getInspectResultId()));
+            Sort sort = Sort.by("createTime").descending();
+            query.with(sort);
+            List<InspectDetailsDto> inspectDetailsDto = new ArrayList<>();
+            InspectDetailsDto inspectDetailsDto1 = new InspectDetailsDto();
+            inspectDetailsDto1.setTaskId(taskId);
+            inspectDetailsDto.add(inspectDetailsDto1);
+            inspectDetailsDto.add(inspectDetailsDto1);
+            when(inspectDetailsService.findAllDto(query,userDetail)).thenReturn(inspectDetailsDto);
+            doCallRealMethod().when(inspectDetailsService).export(inspectDetails, zipOutputStream, userDetail, inspectResultService);
+            inspectDetailsService.export(inspectDetails, zipOutputStream, userDetail, inspectResultService);
+
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("sourceTableName", source.getTable() + "/" + source.getConnectionName()
+                    + "(Row:" + sourceTotal + ")");
+            jsonObject.put("targetTableName", target.getTable() + "/" + target.getConnectionName()
+                    + "(Row:" + targetTotal + ")");
+            jsonObject.put("checkResult", "failed");
+            long count = targetTotal - sourceTotal;
+            if (count < 0) {
+                jsonObject.put("targetCountLess", Math.abs(count));
+            } else {
+                jsonObject.put("targetCountMore", count);
+            }
+
+            jsonObject.put("tableDiffCount", 1);
+            JSONArray jsonArrayTmp = new JSONArray();
+            JSONObject jsonObjectTmp = new JSONObject();
+            jsonObjectTmp.put("source", inspectDetailsDto1.getSource());
+            jsonObjectTmp.put("target", inspectDetailsDto1.getTarget());
+            jsonArrayTmp.add(jsonObjectTmp);
+            JSONObject jsonObjectTmp1 = new JSONObject();
+            jsonObjectTmp1.put("source", inspectDetailsDto1.getSource());
+            jsonObjectTmp1.put("target", inspectDetailsDto1.getTarget());
+            jsonArrayTmp.add(jsonObjectTmp1);
+            jsonObject.put("data", jsonArrayTmp);
+            JSONArray jsonArray = new JSONArray();
+            jsonArray.add(jsonObject);
+
+
+
+            verify(zipOutputStream,times(1)).write(jsonArray.toString().getBytes());
+
+        }
+
+
+
+
+
+    }
+
+}
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/roleMapping/service/RoleMappingServiceTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/roleMapping/service/RoleMappingServiceTest.java
new file mode 100644
index 0000000000..ed6983b790
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/roleMapping/service/RoleMappingServiceTest.java
@@ -0,0 +1,77 @@
+package com.tapdata.tm.roleMapping.service;
+
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.roleMapping.dto.RoleMappingDto;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.data.mongodb.core.query.Query;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class RoleMappingServiceTest {
+    RoleMappingService service;
+    @BeforeEach
+    void init() {
+        service = mock(RoleMappingService.class);
+    }
+
+    @Nested
+    class UpdateUserRoleMappingTest {
+        List<RoleMappingDto> roleDto;
+        UserDetail userDetail;
+        RoleMappingDto dto ;
+        @BeforeEach
+        void init() {
+            dto = mock(RoleMappingDto.class);
+            userDetail = mock(UserDetail.class);
+            roleDto = new ArrayList<>();
+            roleDto.add(null);
+            roleDto.add(dto);
+
+            when(dto.getRoleId()).thenReturn(new ObjectId());
+            when(dto.getPrincipalId()).thenReturn("id");
+            when(dto.getPrincipalType()).thenReturn("type");
+            when(service.upsert(any(Query.class), any(RoleMappingDto.class), any(UserDetail.class))).thenReturn(0L);
+            when(service.updateUserRoleMapping(roleDto, userDetail)).thenCallRealMethod();
+        }
+
+        @Test
+        void testNormal() {
+            when(service.findAll(any(Query.class))).thenReturn(new ArrayList<>());
+            Assertions.assertNotNull(service.updateUserRoleMapping(roleDto, userDetail));
+            verify(service).upsert(any(Query.class), any(RoleMappingDto.class), any(UserDetail.class));
+            verify(service).findAll(any(Query.class));
+        }
+
+        @Test
+        void testEmpty() {
+            roleDto.clear();
+            when(service.findAll(any(Query.class))).thenReturn(new ArrayList<>());
+            List<RoleMappingDto> roleMappingDto = service.updateUserRoleMapping(roleDto, userDetail);
+            Assertions.assertNotNull(roleMappingDto);
+            Assertions.assertEquals(0, roleMappingDto.size());
+            verify(service, times(0)).upsert(any(Query.class), any(RoleMappingDto.class), any(UserDetail.class));
+            verify(service, times(0)).findAll(any(Query.class));
+        }
+
+        @Test
+        void testResultIsEmpty() {
+            when(service.findAll(any(Query.class))).thenReturn(null);
+            List<RoleMappingDto> roleMappingDto = service.updateUserRoleMapping(roleDto, userDetail);
+            Assertions.assertNotNull(roleMappingDto);
+            Assertions.assertEquals(0, roleMappingDto.size());
+            verify(service).upsert(any(Query.class), any(RoleMappingDto.class), any(UserDetail.class));
+            verify(service).findAll(any(Query.class));
+        }
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/utils/MailUtilsTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/utils/MailUtilsTest.java
index ebf53b429b..4f087eaa08 100644
--- a/manager/tm-api/src/test/java/com/tapdata/tm/utils/MailUtilsTest.java
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/utils/MailUtilsTest.java
@@ -7,27 +7,48 @@
 import com.tapdata.tm.message.constant.MsgTypeEnum;
 import com.tapdata.tm.message.constant.SystemEnum;
 import com.tapdata.tm.message.service.BlacklistService;
-import com.tapdata.tm.utils.MailUtils;
-import com.tapdata.tm.utils.SendStatus;
+import org.jsoup.nodes.Document;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.MockedConstruction;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.slf4j.Logger;
+import org.springframework.test.util.ReflectionTestUtils;
 
-import javax.mail.*;
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Transport;
 import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeMessage;
+import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 import java.util.Properties;
 
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyList;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
 @ExtendWith(MockitoExtension.class)
 class MailUtilsTest {
     @Mock
@@ -40,7 +61,7 @@ class MailUtilsTest {
 
     @BeforeEach
     void setup(){
-
+        //doCallRealMethod().when(mailUtils).sendEmail(any(Document.class), any(SendStatus.class), anyList(), anyString(), anyString());
     }
 
     @Test
@@ -180,4 +201,209 @@ void testGetInternetAddress() throws UnsupportedEncodingException {
     }
 
 
+
+    @Nested
+    class SendValidateCodeForResetPWDTest {
+        private String host;
+        private Integer port;
+
+        private String user;
+        private String sendAddress;
+
+        private String password;
+        Transport transport;
+
+        MailUtils mu;
+        Logger log;
+        @BeforeEach
+        void init() throws MessagingException, UnsupportedEncodingException {
+            host = "127.0.0.1";
+            port = 9999;
+            user = "tapdata";
+            sendAddress = "127.0.0.1";
+            password = "123456";
+            log = mock(Logger.class);
+
+            mu = mock(MailUtils.class);
+            ReflectionTestUtils.setField(mu, "host", host);
+            ReflectionTestUtils.setField(mu, "port", port);
+            ReflectionTestUtils.setField(mu, "user", user);
+            ReflectionTestUtils.setField(mu, "sendAddress", sendAddress);
+            ReflectionTestUtils.setField(mu, "password", password);
+
+            transport = mock(Transport.class);
+            mockSlf4jLog(mu, log);
+
+            when(mu.emailSession()).thenCallRealMethod();
+            when(mu.message(any(Session.class), anyList(), anyString(), anyString())).thenCallRealMethod();
+            doCallRealMethod().when(mu).sendEmail(any(Document.class), any(SendStatus.class), anyList(), anyString(), anyString());
+            when(mu.sendValidateCodeForResetPWD(anyString(), anyString(), anyString())).thenCallRealMethod();
+        }
+
+        @Test
+        void testSendValidateCodeForResetPWD() throws MessagingException {
+            when(mu.connectSMTP(any(Session.class))).thenCallRealMethod();
+            doCallRealMethod().when(mu).closeTransport(any(Transport.class));
+            doNothing().when(transport).connect(anyString(), anyInt(), anyString(), anyString());
+            doNothing().when(mu).initMailConfig();
+            doNothing().when(transport).close();
+            doNothing().when(transport).sendMessage(any(MimeMessage.class), any(Address[].class));
+            Session session = mock(Session.class);
+            InternetAddress[] internetAddressList = new InternetAddress[0];
+            Address[] addresses = new Address[0];
+
+            doNothing().when(session).setDebug(true);
+            when(session.getTransport("smtp")).thenReturn(transport);
+            try(MockedStatic<Session> s = mockStatic(Session.class);
+                MockedConstruction<InternetAddress> i = mockConstruction(InternetAddress.class, (ic, c) -> {});
+                MockedConstruction<MimeMessage> m = mockConstruction(MimeMessage.class, (mk,c) -> {
+                    doNothing().when(mk).setFrom(any(InternetAddress.class));
+                    doNothing().when(mk).setRecipients(Message.RecipientType.TO, internetAddressList);
+                    doNothing().when(mk).setContent(anyString(), anyString());
+                    doNothing().when(mk).setSentDate(any(Date.class));
+                    doNothing().when(mk).saveChanges();
+                    when(mk.getAllRecipients()).thenReturn(addresses);
+                })) {
+                s.when(() -> Session.getDefaultInstance(any(Properties.class))).thenReturn(session);
+                SendStatus sendStatus = mu.sendValidateCodeForResetPWD("", "", "");
+                Assertions.assertNotNull(sendStatus);
+            }
+        }
+
+        @Test
+        void testSendValidateCodeForResetPWDException() throws MessagingException {
+            doNothing().when(log).error(anyString(), any(Exception.class));
+            when(mu.connectSMTP(any(Session.class))).thenCallRealMethod();
+            doCallRealMethod().when(mu).closeTransport(any(Transport.class));
+            doNothing().when(transport).connect(anyString(), anyInt(), anyString(), anyString());
+            doNothing().when(mu).initMailConfig();
+            doNothing().when(transport).close();
+            Session session = mock(Session.class);
+            InternetAddress[] internetAddressList = new InternetAddress[0];
+            Address[] addresses = new Address[0];
+
+            doAnswer(a -> {
+                throw new IOException("");
+            }).when(transport).sendMessage(any(MimeMessage.class), any(Address[].class));
+
+            doNothing().when(session).setDebug(true);
+            when(session.getTransport("smtp")).thenReturn(transport);
+            try(MockedStatic<Session> s = mockStatic(Session.class);
+                MockedConstruction<InternetAddress> i = mockConstruction(InternetAddress.class, (ic, c) -> {});
+                MockedConstruction<MimeMessage> m = mockConstruction(MimeMessage.class, (mk,c) -> {
+                    doNothing().when(mk).setFrom(any(InternetAddress.class));
+                    doNothing().when(mk).setRecipients(Message.RecipientType.TO, internetAddressList);
+                    doNothing().when(mk).setContent(anyString(), anyString());
+                    doNothing().when(mk).setSentDate(any(Date.class));
+                    doNothing().when(mk).saveChanges();
+                    when(mk.getAllRecipients()).thenReturn(addresses);
+                })) {
+                s.when(() -> Session.getDefaultInstance(any(Properties.class))).thenReturn(session);
+                SendStatus sendStatus = mu.sendValidateCodeForResetPWD("", "", "");
+                Assertions.assertNotNull(sendStatus);
+            }
+        }
+
+        @Test
+        void testSendValidateCodeForResetPWDCloseException() throws MessagingException {
+            when(mu.connectSMTP(any(Session.class))).thenCallRealMethod();
+            doCallRealMethod().when(mu).closeTransport(any(Transport.class));
+            doNothing().when(transport).connect(anyString(), anyInt(), anyString(), anyString());
+            doNothing().when(mu).initMailConfig();
+            doAnswer(a -> {
+                throw new MessagingException("");
+            }).when(transport).close();
+            doNothing().when(transport).sendMessage(any(MimeMessage.class), any(Address[].class));
+            Session session = mock(Session.class);
+            InternetAddress[] internetAddressList = new InternetAddress[0];
+            Address[] addresses = new Address[0];
+            doNothing().when(session).setDebug(true);
+            when(session.getTransport("smtp")).thenReturn(transport);
+            try(MockedStatic<Session> s = mockStatic(Session.class);
+                MockedConstruction<InternetAddress> i = mockConstruction(InternetAddress.class, (ic, c) -> {});
+                MockedConstruction<MimeMessage> m = mockConstruction(MimeMessage.class, (mk,c) -> {
+                    doNothing().when(mk).setFrom(any(InternetAddress.class));
+                    doNothing().when(mk).setRecipients(Message.RecipientType.TO, internetAddressList);
+                    doNothing().when(mk).setContent(anyString(), anyString());
+                    doNothing().when(mk).setSentDate(any(Date.class));
+                    doNothing().when(mk).saveChanges();
+                    when(mk.getAllRecipients()).thenReturn(addresses);
+                })) {
+                s.when(() -> Session.getDefaultInstance(any(Properties.class))).thenReturn(session);
+                SendStatus sendStatus = mu.sendValidateCodeForResetPWD("", "", "");
+                Assertions.assertNotNull(sendStatus);
+            }
+        }
+
+        @Test
+        void testSendValidateCodeForResetPWDTransportIsNull() throws MessagingException {
+            doNothing().when(log).error(anyString(), any(Exception.class));
+            doCallRealMethod().when(mu).closeTransport(null);
+            doNothing().when(mu).initMailConfig();
+            Session session = mock(Session.class);
+            InternetAddress[] internetAddressList = new InternetAddress[0];
+            Address[] addresses = new Address[0];
+            doNothing().when(session).setDebug(true);
+            try(MockedStatic<Session> s = mockStatic(Session.class);
+                MockedConstruction<InternetAddress> i = mockConstruction(InternetAddress.class, (ic, c) -> {});
+                MockedConstruction<MimeMessage> m = mockConstruction(MimeMessage.class, (mk,c) -> {
+                    doNothing().when(mk).setFrom(any(InternetAddress.class));
+                    doNothing().when(mk).setRecipients(Message.RecipientType.TO, internetAddressList);
+                    doNothing().when(mk).setContent(anyString(), anyString());
+                    doNothing().when(mk).setSentDate(any(Date.class));
+                    doAnswer(a -> {
+                        throw new MessagingException("");
+                    }).when(mk).saveChanges();
+                    when(mk.getAllRecipients()).thenReturn(addresses);
+                })) {
+                s.when(() -> Session.getDefaultInstance(any(Properties.class))).thenReturn(session);
+                SendStatus sendStatus = mu.sendValidateCodeForResetPWD("", "", "");
+                Assertions.assertNotNull(sendStatus);
+            }
+        }
+
+        @Test
+        void testNotPort() throws MessagingException {
+            when(mu.connectSMTP(any(Session.class))).thenCallRealMethod();
+            doCallRealMethod().when(mu).closeTransport(any(Transport.class));
+            ReflectionTestUtils.setField(mu, "port", null);
+            doNothing().when(transport).connect(anyString(), anyString(), anyString());
+            doNothing().when(mu).initMailConfig();
+            doNothing().when(transport).close();
+            doNothing().when(transport).sendMessage(any(MimeMessage.class), any(Address[].class));
+            Session session = mock(Session.class);
+            InternetAddress[] internetAddressList = new InternetAddress[0];
+            Address[] addresses = new Address[0];
+
+            doNothing().when(session).setDebug(true);
+            when(session.getTransport("smtp")).thenReturn(transport);
+            try(MockedStatic<Session> s = mockStatic(Session.class);
+                MockedConstruction<InternetAddress> i = mockConstruction(InternetAddress.class, (ic, c) -> {});
+                MockedConstruction<MimeMessage> m = mockConstruction(MimeMessage.class, (mk,c) -> {
+                    doNothing().when(mk).setFrom(any(InternetAddress.class));
+                    doNothing().when(mk).setRecipients(Message.RecipientType.TO, internetAddressList);
+                    doNothing().when(mk).setContent(anyString(), anyString());
+                    doNothing().when(mk).setSentDate(any(Date.class));
+                    doNothing().when(mk).saveChanges();
+                    when(mk.getAllRecipients()).thenReturn(addresses);
+                })) {
+                s.when(() -> Session.getDefaultInstance(any(Properties.class))).thenReturn(session);
+                SendStatus sendStatus = mu.sendValidateCodeForResetPWD("", "", "");
+                Assertions.assertNotNull(sendStatus);
+            }
+        }
+    }
+
+    public void mockSlf4jLog(Object mockTo, Logger log) {
+        try {
+            Field logF = mockTo.getClass().getDeclaredField("log");
+            Field modifiersField = Field.class.getDeclaredField("modifiers");
+            modifiersField.setAccessible(true);
+            modifiersField.setInt(logF, logF.getModifiers() & ~Modifier.FINAL);
+            logF.setAccessible(true);
+            logF.set(mockTo, log);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/utils/MetadataInstancesFilterUtilTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/utils/MetadataInstancesFilterUtilTest.java
new file mode 100644
index 0000000000..f288f2b163
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/utils/MetadataInstancesFilterUtilTest.java
@@ -0,0 +1,193 @@
+package com.tapdata.tm.utils;
+
+import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
+import com.tapdata.tm.commons.schema.Field;
+import com.tapdata.tm.commons.schema.MetadataInstancesDto;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+
+public class MetadataInstancesFilterUtilTest {
+    @Test
+    void test_getFilteredOriginalNames(){
+        DatabaseNode databaseNode = new DatabaseNode();
+        databaseNode.setMigrateTableSelectType("expression");
+        databaseNode.setTableExpression("test.*");
+        MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+        metadataInstancesDto1.setOriginalName("test1");
+        MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+        metadataInstancesDto2.setOriginalName("test2");
+        MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+        List<String> result = MetadataInstancesFilterUtil.getFilteredOriginalNames(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3),databaseNode);
+        Assertions.assertEquals(2,result.size());
+    }
+
+    @Test
+    void test_getFilteredOriginalNames_tableExpression_isNull(){
+        DatabaseNode databaseNode = new DatabaseNode();
+        databaseNode.setMigrateTableSelectType("expression");
+        MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+        metadataInstancesDto1.setOriginalName("test1");
+        MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+        metadataInstancesDto2.setOriginalName("test2");
+        MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+        List<String> result = MetadataInstancesFilterUtil.getFilteredOriginalNames(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3),databaseNode);
+        Assertions.assertEquals(0,result.size());
+    }
+
+    @Test
+    void test_getFilteredOriginalNames_NoPrimaryKeyTableSelectType_is_HasKeys(){
+        DatabaseNode databaseNode = new DatabaseNode();
+        databaseNode.setMigrateTableSelectType("expression");
+        databaseNode.setTableExpression("test.*");
+        databaseNode.setNoPrimaryKeyTableSelectType("HasKeys");
+        MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+        metadataInstancesDto1.setOriginalName("test1");
+        Field field1 = new Field();
+        field1.setPrimaryKey(true);
+        metadataInstancesDto1.setFields(Arrays.asList(field1));
+        MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+        Field field2 = new Field();
+        field2.setPrimaryKey(false);
+        metadataInstancesDto2.setOriginalName("test2");
+        metadataInstancesDto2.setFields(Arrays.asList(field2));
+        MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+        List<String> result = MetadataInstancesFilterUtil.getFilteredOriginalNames(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3),databaseNode);
+        Assertions.assertEquals(1,result.size());
+    }
+
+    @Test
+    void test_getFilteredOriginalNames_NoPrimaryKeyTableSelectType_is_NoKeys(){
+        DatabaseNode databaseNode = new DatabaseNode();
+        databaseNode.setMigrateTableSelectType("expression");
+        databaseNode.setTableExpression("test.*");
+        databaseNode.setNoPrimaryKeyTableSelectType("NoKeys");
+        MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+        metadataInstancesDto1.setOriginalName("test1");
+        Field field1 = new Field();
+        field1.setPrimaryKey(true);
+        metadataInstancesDto1.setFields(Arrays.asList(field1));
+        MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+        Field field2 = new Field();
+        field2.setPrimaryKey(false);
+        metadataInstancesDto2.setOriginalName("test2");
+        metadataInstancesDto2.setFields(Arrays.asList(field2));
+        MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+        List<String> result = MetadataInstancesFilterUtil.getFilteredOriginalNames(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3),databaseNode);
+        Assertions.assertEquals(1,result.size());
+    }
+
+    @Test
+    void test_getFilteredOriginalNames_NoPrimaryKeyTableSelectType_is_All(){
+        DatabaseNode databaseNode = new DatabaseNode();
+        databaseNode.setMigrateTableSelectType("expression");
+        databaseNode.setTableExpression("test.*");
+        databaseNode.setNoPrimaryKeyTableSelectType("All");
+        MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+        metadataInstancesDto1.setOriginalName("test1");
+        Field field1 = new Field();
+        field1.setPrimaryKey(true);
+        metadataInstancesDto1.setFields(Arrays.asList(field1));
+        MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+        Field field2 = new Field();
+        field2.setPrimaryKey(false);
+        metadataInstancesDto2.setOriginalName("test2");
+        metadataInstancesDto2.setFields(Arrays.asList(field2));
+        MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+        List<String> result = MetadataInstancesFilterUtil.getFilteredOriginalNames(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3),databaseNode);
+        Assertions.assertEquals(2,result.size());
+    }
+
+    @Test
+    void test_countFilteredOriginalNames(){
+        DatabaseNode databaseNode = new DatabaseNode();
+        databaseNode.setMigrateTableSelectType("expression");
+        databaseNode.setTableExpression("test.*");
+        MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+        metadataInstancesDto1.setOriginalName("test1");
+        MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+        metadataInstancesDto2.setOriginalName("test2");
+        MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+        Long result = MetadataInstancesFilterUtil.countFilteredOriginalNames(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3),databaseNode);
+        Assertions.assertEquals(2,result);
+    }
+
+    @Test
+    void test_countFilteredOriginalNames_tableExpression_isNull(){
+        DatabaseNode databaseNode = new DatabaseNode();
+        databaseNode.setMigrateTableSelectType("expression");
+        MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+        metadataInstancesDto1.setOriginalName("test1");
+        MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+        metadataInstancesDto2.setOriginalName("test2");
+        MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+        Long result = MetadataInstancesFilterUtil.countFilteredOriginalNames(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3),databaseNode);
+        Assertions.assertEquals(0,result);
+    }
+
+    @Test
+    void test_countFilteredOriginalNames_NoPrimaryKeyTableSelectType_is_HasKeys(){
+        DatabaseNode databaseNode = new DatabaseNode();
+        databaseNode.setMigrateTableSelectType("expression");
+        databaseNode.setTableExpression("test.*");
+        databaseNode.setNoPrimaryKeyTableSelectType("HasKeys");
+        MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+        metadataInstancesDto1.setOriginalName("test1");
+        Field field1 = new Field();
+        field1.setPrimaryKey(true);
+        metadataInstancesDto1.setFields(Arrays.asList(field1));
+        MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+        Field field2 = new Field();
+        field2.setPrimaryKey(false);
+        metadataInstancesDto2.setOriginalName("test2");
+        metadataInstancesDto2.setFields(Arrays.asList(field2));
+        MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+        Long result = MetadataInstancesFilterUtil.countFilteredOriginalNames(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3),databaseNode);
+        Assertions.assertEquals(1,result);
+    }
+
+    @Test
+    void test_countFilteredOriginalNames_NoPrimaryKeyTableSelectType_is_NoKeys(){
+        DatabaseNode databaseNode = new DatabaseNode();
+        databaseNode.setMigrateTableSelectType("expression");
+        databaseNode.setTableExpression("test.*");
+        databaseNode.setNoPrimaryKeyTableSelectType("NoKeys");
+        MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+        metadataInstancesDto1.setOriginalName("test1");
+        Field field1 = new Field();
+        field1.setPrimaryKey(true);
+        metadataInstancesDto1.setFields(Arrays.asList(field1));
+        MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+        Field field2 = new Field();
+        field2.setPrimaryKey(false);
+        metadataInstancesDto2.setOriginalName("test2");
+        metadataInstancesDto2.setFields(Arrays.asList(field2));
+        MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+        Long result = MetadataInstancesFilterUtil.countFilteredOriginalNames(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3),databaseNode);
+        Assertions.assertEquals(1,result);
+    }
+
+    @Test
+    void test_countFilteredOriginalNames_NoPrimaryKeyTableSelectType_is_All(){
+        DatabaseNode databaseNode = new DatabaseNode();
+        databaseNode.setMigrateTableSelectType("expression");
+        databaseNode.setTableExpression("test.*");
+        databaseNode.setNoPrimaryKeyTableSelectType("All");
+        MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+        metadataInstancesDto1.setOriginalName("test1");
+        Field field1 = new Field();
+        field1.setPrimaryKey(true);
+        metadataInstancesDto1.setFields(Arrays.asList(field1));
+        MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+        Field field2 = new Field();
+        field2.setPrimaryKey(false);
+        metadataInstancesDto2.setOriginalName("test2");
+        metadataInstancesDto2.setFields(Arrays.asList(field2));
+        MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+        Long result = MetadataInstancesFilterUtil.countFilteredOriginalNames(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3),databaseNode);
+        Assertions.assertEquals(2,result);
+    }
+}
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/utils/QueryUtilTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/utils/QueryUtilTest.java
index a3d194bab4..8f42ff3426 100644
--- a/manager/tm-api/src/test/java/com/tapdata/tm/utils/QueryUtilTest.java
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/utils/QueryUtilTest.java
@@ -46,4 +46,52 @@ void testNormal() {
             Assertions.assertEquals(3, sorts.size());
         }
     }
-}
\ No newline at end of file
+
+    @Nested
+    class ParseOrderTest {
+        String sortKey = "createTime";
+
+        @Test
+        void testNotParse() {
+            String order = null;
+            Assertions.assertNull(QueryUtil.parseOrder(order));
+
+            order = "";
+            Assertions.assertNull(QueryUtil.parseOrder(order));
+
+            order = " ";
+            Assertions.assertNull(QueryUtil.parseOrder(order));
+        }
+
+        @Test
+        void testNoDirection() {
+            String order = String.format("%s", sortKey);
+            Sort sort = QueryUtil.parseOrder(order);
+            Assertions.assertNotNull(sort);
+            Sort.Order orderFor = sort.getOrderFor(sortKey);
+            Assertions.assertNotNull(orderFor);
+            Assertions.assertEquals(Sort.Direction.DESC, orderFor.getDirection());
+        }
+
+        @Test
+        void testAsc() {
+            String order = String.format("%s  asc ", sortKey);
+            Sort sort = QueryUtil.parseOrder(order);
+            Assertions.assertNotNull(sort);
+            Sort.Order orderFor = sort.getOrderFor(sortKey);
+            Assertions.assertNotNull(orderFor);
+            Assertions.assertEquals(Sort.Direction.ASC, orderFor.getDirection());
+        }
+
+        @Test
+        void testDesc() {
+            String order = String.format("%s desc", sortKey);
+            Sort sort = QueryUtil.parseOrder(order);
+            Assertions.assertNotNull(sort);
+            Sort.Order orderFor = sort.getOrderFor(sortKey);
+            Assertions.assertNotNull(orderFor);
+            Assertions.assertEquals(Sort.Direction.DESC, orderFor.getDirection());
+
+        }
+    }
+}
diff --git a/manager/tm-api/src/test/resources/resetPasswordTemplate.html b/manager/tm-api/src/test/resources/resetPasswordTemplate.html
new file mode 100644
index 0000000000..79c3ba73dc
--- /dev/null
+++ b/manager/tm-api/src/test/resources/resetPasswordTemplate.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8" />
+    <title>Password Reset</title>
+</head>
+<body>
+<div style="margin: 50px auto 0; font-size: 12px; color: #000; max-width: 700px">
+    <div style="overflow: hidden; line-height: 30px">
+        <img src="https://cloud.tapdata.net/assets/img/logo2.png" alt="" style="float: left; height: 30px" />
+        <div style="float: right; color: #76808e">让数据随时可用</div>
+    </div>
+    <div style="margin-top: 17px; background: #fff; box-shadow: -2px 0px 8px 0px rgb(0 0 0 / 10%)">
+        <div style="padding: 24px">
+            <div>Hi，<font id="username" style="color: #2c65ff">Gavin</font>：</div>
+            <div style="margin-top: 24px">
+                <span id="sysName">您的验证码是：</span>
+                <span id="code" style="color: #2c65ff"> 234565 </span>
+            </div>
+
+            <div style="padding: 22px;color: #76808e">
+                <div>
+                    <div><font id="validateTimes"  style="font-weight: bolder;color: #2c65ff">5</font> 内分钟有效 </div>
+                </div>
+                <div style="margin-top: 24px" id="mailContent">将用于账号： <font id="account" style="font-weight: bolder">Gavin</font> 进行<font style="color: #2c65ff">修改密码</font>，请回到页面填写此验证码</div>
+            </div>
+        </div>
+        <div style="padding: 22px; text-align: center; border-top: 1px solid #cccccc; color: #76808e">
+            <div>如您对Tapdata的使用有任何疑问，请随时与我们联系。</div>
+            <div style="margin-top: 8px">
+                <a href="https://cloud.tapdata.net/" target="_blank" style="text-decoration: none; color: #76808e"
+                >访问官网</a
+                >
+                <span>｜</span>
+                <a
+                        href="https://cloud.tapdata.net/console/#/workbench"
+                        target="_blank"
+                        style="text-decoration: none; color: #76808e"
+                >访问控制台</a
+                >
+                <span>｜</span>
+                <a
+                        href="https://cloud.tapdata.net/contact.html"
+                        target="_blank"
+                        style="text-decoration: none; color: #76808e"
+                >联系我们</a
+                >
+            </div>
+        </div>
+    </div>
+</div>
+</body>
+</html>
diff --git a/manager/tm-common/src/test/java/com/tapdata/tm/commons/cdcdelay/CdcDelayTest.java b/manager/tm-common/src/test/java/com/tapdata/tm/commons/cdcdelay/CdcDelayTest.java
new file mode 100644
index 0000000000..7d1bfaebeb
--- /dev/null
+++ b/manager/tm-common/src/test/java/com/tapdata/tm/commons/cdcdelay/CdcDelayTest.java
@@ -0,0 +1,75 @@
+package com.tapdata.tm.commons.cdcdelay;
+
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.commons.util.ConnHeartbeatUtils;
+import io.tapdata.entity.event.TapEvent;
+import io.tapdata.entity.event.control.HeartbeatEvent;
+import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.LongConsumer;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class CdcDelayTest {
+    private CdcDelay cdcDelay;
+    @BeforeEach
+    void beforeSetUp(){
+        cdcDelay=new CdcDelay();
+    }
+    @DisplayName("test filterAndCalcDelay sync Task")
+    @Test
+    void test1(){
+        TapUpdateRecordEvent tapUpdateRecordEvent=new TapUpdateRecordEvent();
+        Map<String,Object> after=new HashMap<>();
+        after.put("ts",new Date());
+        after.put("id","recordId");
+        tapUpdateRecordEvent.setAfter(after);
+        tapUpdateRecordEvent.setTableId(ConnHeartbeatUtils.TABLE_NAME);
+        LongConsumer consumer=(time)->{
+        };
+        List<String> tables=new ArrayList<>();
+        tables.add("testTableId");
+        cdcDelay.addHeartbeatTable(tables);
+        TapEvent tapEvent = cdcDelay.filterAndCalcDelay(tapUpdateRecordEvent, consumer, TaskDto.SYNC_TYPE_SYNC);
+        assertEquals(true,tapEvent instanceof HeartbeatEvent);
+    }
+    @DisplayName("test filterAndCalcDelay LOG_COLLECTOR Task")
+    @Test
+    void test2(){
+        TapUpdateRecordEvent tapUpdateRecordEvent=new TapUpdateRecordEvent();
+        Map<String,Object> after=new HashMap<>();
+        after.put("ts",new Date());
+        after.put("id","recordId");
+        tapUpdateRecordEvent.setAfter(after);
+        tapUpdateRecordEvent.setTableId(ConnHeartbeatUtils.TABLE_NAME);
+        LongConsumer consumer=(time)->{
+        };
+        List<String> tables=new ArrayList<>();
+        tables.add("testTableId");
+        cdcDelay.addHeartbeatTable(tables);
+        TapEvent tapEvent = cdcDelay.filterAndCalcDelay(tapUpdateRecordEvent, consumer, TaskDto.SYNC_TYPE_LOG_COLLECTOR);
+        assertEquals(true,tapEvent instanceof TapUpdateRecordEvent);
+    }
+    @DisplayName("test filterAndCalcDelay not heartbeat event")
+    @Test
+    void test3(){
+        ReflectionTestUtils.setField(cdcDelay,"lastUpdated",System.currentTimeMillis()-5000L*1000);
+        TapUpdateRecordEvent tapUpdateRecordEvent=new TapUpdateRecordEvent();
+        Map<String,Object> after=new HashMap<>();
+        after.put("ts",new Date());
+        after.put("id","recordId");
+        tapUpdateRecordEvent.setReferenceTime(System.currentTimeMillis());
+        tapUpdateRecordEvent.setAfter(after);
+        tapUpdateRecordEvent.setTableId("testTable");
+        LongConsumer consumer=(time)->{
+        };
+        TapEvent tapEvent = cdcDelay.filterAndCalcDelay(tapUpdateRecordEvent, consumer, TaskDto.SYNC_TYPE_LOG_COLLECTOR);
+        assertEquals(tapUpdateRecordEvent,tapEvent);
+    }
+}
diff --git a/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/DAGDataServiceImplTest.java b/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/DAGDataServiceImplTest.java
index 3920bc4450..fcef2e6232 100644
--- a/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/DAGDataServiceImplTest.java
+++ b/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/DAGDataServiceImplTest.java
@@ -1,24 +1,38 @@
 package com.tapdata.tm.commons.dag;
 
 import com.tapdata.tm.commons.dag.deduction.rule.ChangeRuleStage;
-import com.tapdata.tm.commons.dag.vo.FieldChangeRule;
-import com.tapdata.tm.commons.dag.vo.FieldChangeRuleGroup;
-import com.tapdata.tm.commons.schema.*;
+import com.tapdata.tm.commons.dag.nodes.CacheNode;
+import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
+import com.tapdata.tm.commons.dag.process.MigrateProcessorNode;
+import com.tapdata.tm.commons.dag.process.ProcessorNode;
+import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
+import com.tapdata.tm.commons.schema.MetadataInstancesDto;
+import com.tapdata.tm.commons.schema.Schema;
+import com.tapdata.tm.commons.schema.TransformerWsMessageDto;
 import com.tapdata.tm.commons.schema.bean.SourceTypeEnum;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.commons.util.MetaDataBuilderUtils;
+import com.tapdata.tm.commons.util.MetaType;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
-import org.mockito.internal.stubbing.answers.CallsRealMethods;
 import org.springframework.test.util.ReflectionTestUtils;
 
-
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 
 class DAGDataServiceImplTest {
@@ -153,5 +167,136 @@ void testQualifiedNameIsNullOne(){
             Assertions.assertEquals(2,dagDataService.getLogCollectorMetadataInstancesDto().size());
         }
     }
+    @Nested
+    class GetSchemaByNodeAndTableNameTest {
+        TaskDto taskDto;
+        DAG dag;
+        com.tapdata.tm.commons.dag.Node node;
+        List<MetadataInstancesDto> batchInsertMetaDataList;
+        MetadataInstancesDto d1;
+        Map<String, DataSourceConnectionDto> dataSourceMap;
+        DataSourceConnectionDto t1;
+        @BeforeEach
+        void init() {
+            dataSourceMap = new HashMap<>();
+            t1 = mock(DataSourceConnectionDto.class);
+            dataSourceMap.put("id", t1);
+
+            batchInsertMetaDataList = new ArrayList<>();
+            d1 = mock(MetadataInstancesDto.class);
+            when(d1.getQualifiedName()).thenReturn("");
+            batchInsertMetaDataList.add(d1);
+
+            dag = mock(DAG.class);
+
+            taskDto = mock(TaskDto.class);
+            when(taskDto.getDag()).thenReturn(dag);
+
+
+            ReflectionTestUtils.setField(service, "dataSourceMap", dataSourceMap);
+            ReflectionTestUtils.setField(service, "batchInsertMetaDataList", batchInsertMetaDataList);
+            ReflectionTestUtils.setField(service, "taskId", "id");
+            when(service.getTaskById(anyString())).thenReturn(taskDto);
+            when(service.getMetadata(anyString())).thenReturn(mock(MetadataInstancesDto.class));
+            when(service.getSchemaByNodeAndTableName(anyString(), anyString())).thenCallRealMethod();
+        }
+
+        @Test
+        void testTableRenameNode() {
+            node = mock(com.tapdata.tm.commons.dag.process.ProcessorNode.class);
+            when(dag.getNode(anyString())).thenReturn(node);
+            try (MockedStatic<MetaDataBuilderUtils> m = mockStatic(MetaDataBuilderUtils.class)) {
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), anyString(), anyString())).thenReturn("qualifiedName");
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), any(DataSourceConnectionDto.class), anyString(), anyString())).thenReturn("qualifiedName");
+                Assertions.assertNotNull(service.getSchemaByNodeAndTableName("", ""));
+                verify(service).getMetadata(anyString());
+                verify(service).getTaskById(anyString());
+                verify(taskDto).getDag();
+            }
+        }
+
+        @Test
+        void testTaskDtoIsNull() {
+            when(service.getTaskById(anyString())).thenReturn(null);
+            node = mock(com.tapdata.tm.commons.dag.process.ProcessorNode.class);
+            when(dag.getNode(anyString())).thenReturn(node);
+            try (MockedStatic<MetaDataBuilderUtils> m = mockStatic(MetaDataBuilderUtils.class)) {
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), anyString(), anyString())).thenReturn("qualifiedName");
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), any(DataSourceConnectionDto.class), anyString(), anyString())).thenReturn("qualifiedName");
+                Assertions.assertNull(service.getSchemaByNodeAndTableName("", ""));
+                verify(service, times(0)).getMetadata(anyString());
+                verify(service).getTaskById(anyString());
+                verify(taskDto, times(0)).getDag();
+            }
+        }
 
+        @Test
+        void testDagIsNull() {
+            node = mock(com.tapdata.tm.commons.dag.process.ProcessorNode.class);
+            when(dag.getNode(anyString())).thenReturn(node);
+            when(taskDto.getDag()).thenReturn(null);
+            try (MockedStatic<MetaDataBuilderUtils> m = mockStatic(MetaDataBuilderUtils.class)) {
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), anyString(), anyString())).thenReturn("qualifiedName");
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), any(DataSourceConnectionDto.class), anyString(), anyString())).thenReturn("qualifiedName");
+                Assertions.assertNull(service.getSchemaByNodeAndTableName("", ""));
+                verify(service, times(0)).getMetadata(anyString());
+                verify(service).getTaskById(anyString());
+                verify(taskDto).getDag();
+            }
+        }
+        @Test
+        void testDatabaseNode() {
+            node = mock(DatabaseNode.class);
+            when(((DatabaseNode)node).getConnectionId()).thenReturn("id");
+            when(dag.getNode(anyString())).thenReturn(node);
+            try (MockedStatic<MetaDataBuilderUtils> m = mockStatic(MetaDataBuilderUtils.class)) {
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), anyString(), anyString())).thenReturn("qualifiedName");
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), any(DataSourceConnectionDto.class), anyString(), anyString())).thenReturn("qualifiedName");
+                Assertions.assertNotNull(service.getSchemaByNodeAndTableName("", ""));
+                verify(service).getMetadata(anyString());
+                verify(service).getTaskById(anyString());
+                verify(taskDto).getDag();
+            }
+        }
+        @Test
+        void testMigrateProcessorNode() {
+            node = mock(MigrateProcessorNode.class);
+            when(dag.getNode(anyString())).thenReturn(node);
+            try (MockedStatic<MetaDataBuilderUtils> m = mockStatic(MetaDataBuilderUtils.class)) {
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), anyString(), anyString())).thenReturn("qualifiedName");
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), any(DataSourceConnectionDto.class), anyString(), anyString())).thenReturn("qualifiedName");
+                Assertions.assertNotNull(service.getSchemaByNodeAndTableName("", ""));
+                verify(service).getMetadata(anyString());
+                verify(service).getTaskById(anyString());
+                verify(taskDto).getDag();
+            }
+        }
+        @Test
+        void testMigrateProcessorNode1() {
+            when(d1.getQualifiedName()).thenReturn("qualifiedName");
+            node = mock(MigrateProcessorNode.class);
+            when(dag.getNode(anyString())).thenReturn(node);
+            try (MockedStatic<MetaDataBuilderUtils> m = mockStatic(MetaDataBuilderUtils.class)) {
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), anyString(), anyString())).thenReturn("qualifiedName");
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), any(DataSourceConnectionDto.class), anyString(), anyString())).thenReturn("qualifiedName");
+                Assertions.assertNotNull(service.getSchemaByNodeAndTableName("", ""));
+                verify(service, times(0)).getMetadata(anyString());
+                verify(service).getTaskById(anyString());
+                verify(taskDto).getDag();
+            }
+        }
+        @Test
+        void testOtherNode() {
+            node = mock(CacheNode.class);
+            when(dag.getNode(anyString())).thenReturn(node);
+            try (MockedStatic<MetaDataBuilderUtils> m = mockStatic(MetaDataBuilderUtils.class)) {
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), anyString(), anyString())).thenReturn("qualifiedName");
+                m.when(() -> MetaDataBuilderUtils.generateQualifiedName(anyString(), any(DataSourceConnectionDto.class), anyString(), anyString())).thenReturn("qualifiedName");
+                Assertions.assertNull(service.getSchemaByNodeAndTableName("", ""));
+                verify(service, times(0)).getMetadata(anyString());
+                verify(service).getTaskById(anyString());
+                verify(taskDto).getDag();
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/nodes/DatabaseNodeTest.java b/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/nodes/DatabaseNodeTest.java
index f232f6337c..1c585c16fc 100644
--- a/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/nodes/DatabaseNodeTest.java
+++ b/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/nodes/DatabaseNodeTest.java
@@ -2,11 +2,14 @@
 
 import com.tapdata.tm.commons.dag.DAG;
 import com.tapdata.tm.commons.dag.DAGDataServiceImpl;
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.process.MigrateUnionProcessorNode;
 import io.github.openlg.graphlib.Graph;
 import org.junit.jupiter.api.*;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 
 import static org.mockito.Mockito.*;
@@ -61,4 +64,32 @@ void test1() {
 
         }
     }
+
+    @Nested
+    class LoadSchemaTest {
+        @Test
+        void test_hasMigrateUnionNode(){
+            DAGDataServiceImpl dagDataService = mock(DAGDataServiceImpl.class);
+            ReflectionTestUtils.setField(databaseNode, "service", dagDataService);
+            List<String> includes = new ArrayList<>();
+            includes.add("test");
+            when(databaseNode.getSyncType()).thenReturn("migrate");
+            LinkedList<Node<?>> nodes = new LinkedList<>();
+            nodes.add(new MigrateUnionProcessorNode());
+            when(databaseNode.getPreNodes(any())).thenReturn(nodes);
+            doCallRealMethod().when(databaseNode).loadSchema(includes);
+            Assertions.assertEquals(0,databaseNode.loadSchema(includes).size());
+        }
+        @Test
+        void test_main(){
+            DAGDataServiceImpl dagDataService = mock(DAGDataServiceImpl.class);
+            ReflectionTestUtils.setField(databaseNode, "service", dagDataService);
+            List<String> includes = new ArrayList<>();
+            includes.add("test");
+            when(databaseNode.getSyncType()).thenReturn("migrate");
+            when(dagDataService.loadSchema(any(),any(),any(),any())).thenReturn(new ArrayList<>());
+            doCallRealMethod().when(databaseNode).loadSchema(includes);
+            Assertions.assertEquals(0,databaseNode.loadSchema(includes).size());
+        }
+    }
 }
diff --git a/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/process/MigrateUnionProcessorNodeTest.java b/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/process/MigrateUnionProcessorNodeTest.java
new file mode 100644
index 0000000000..da1e9eef1b
--- /dev/null
+++ b/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/process/MigrateUnionProcessorNodeTest.java
@@ -0,0 +1,58 @@
+package com.tapdata.tm.commons.dag.process;
+
+import com.tapdata.tm.commons.schema.Field;
+import com.tapdata.tm.commons.schema.Schema;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MigrateUnionProcessorNodeTest {
+    @Nested
+    class MergeSchemaTest{
+        @Test
+        void test_main(){
+            MigrateUnionProcessorNode migrateUnionProcessorNode = new MigrateUnionProcessorNode();
+            List<List<Schema>> inputSchemas = new ArrayList<>();
+            List<Schema> schemas = new ArrayList<>();
+            Schema schema1 = new Schema();
+            schema1.setName("schema1");
+            schema1.setAncestorsName("schema1");
+            schema1.setOriginalName("schema1");
+            List<Field> fields = new ArrayList<>();
+            Field field1 = new Field();
+            field1.setId("1");
+            field1.setFieldName("name_1");
+            fields.add(field1);
+            schema1.setFields(fields);
+            Schema schema2 = new Schema();
+            schema2.setName("schema1");
+            schema2.setAncestorsName("schema1");
+            schema2.setOriginalName("schema1");
+            List<Field> field2s = new ArrayList<>();
+            Field field2 = new Field();
+            field2.setId("2");
+            field2.setFieldName("name_2");
+            field2s.add(field2);
+            schema1.setFields(fields);
+            schema2.setFields(field2s);
+            schemas.add(schema1);
+            schemas.add(schema2);
+            inputSchemas.add(schemas);
+            List<Schema> result = migrateUnionProcessorNode.mergeSchema(inputSchemas,null,null);
+            Assertions.assertEquals(2,result.get(0).getFields().size());
+            Assertions.assertTrue(result.get(0).getFields().contains(field1));
+            Assertions.assertTrue(result.get(0).getFields().contains(field2));
+        }
+
+        @Test
+        void test_inputSchemaIsNull(){
+            MigrateUnionProcessorNode migrateUnionProcessorNode = new MigrateUnionProcessorNode();
+            List<Schema> result = migrateUnionProcessorNode.mergeSchema(null,null,null);
+            Assertions.assertEquals(0,result.size());
+        }
+
+    }
+}
diff --git a/manager/tm-oss/src/test/java/com/tapdata/tm/roleMapping/service/RoleMappingServiceImplTest.java b/manager/tm-oss/src/test/java/com/tapdata/tm/roleMapping/service/RoleMappingServiceImplTest.java
new file mode 100644
index 0000000000..c6ebc9328f
--- /dev/null
+++ b/manager/tm-oss/src/test/java/com/tapdata/tm/roleMapping/service/RoleMappingServiceImplTest.java
@@ -0,0 +1,27 @@
+package com.tapdata.tm.roleMapping.service;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+
+class RoleMappingServiceImplTest {
+    RoleMappingServiceImpl service;
+    @BeforeEach
+    void init() {
+        service = mock(RoleMappingServiceImpl.class);
+    }
+
+    @Nested
+    class RemoveRoleFromUserTest {
+        @Test
+        void testNormal() {
+            doCallRealMethod().when(service).removeRoleFromUser(anyString());
+            Assertions.assertDoesNotThrow(() -> service.removeRoleFromUser("Id"));
+        }
+    }
+}
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImplTest.java
index d807e2d355..0153d9174b 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImplTest.java
@@ -2,6 +2,7 @@
 
 
 import com.tapdata.tm.Settings.constant.SettingUtil;
+import com.tapdata.tm.agent.service.AgentGroupService;
 import com.tapdata.tm.base.exception.BizException;
 import com.tapdata.tm.commons.dag.AccessNodeTypeEnum;
 import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
@@ -11,9 +12,12 @@
 import com.tapdata.tm.discovery.service.DefaultDataDirectoryService;
 import com.tapdata.tm.ds.entity.DataSourceEntity;
 import com.tapdata.tm.ds.repository.DataSourceRepository;
+import com.tapdata.tm.messagequeue.dto.MessageQueueDto;
+import com.tapdata.tm.messagequeue.service.MessageQueueService;
 import com.tapdata.tm.metadatainstance.service.MetadataInstancesService;
 import com.tapdata.tm.permissions.DataPermissionHelper;
 import com.tapdata.tm.report.service.UserDataReportService;
+import com.tapdata.tm.worker.entity.Worker;
 import com.tapdata.tm.worker.service.WorkerService;
 import org.bson.types.ObjectId;
 import org.junit.jupiter.api.*;
@@ -377,4 +381,89 @@ void testHiddenMqPasswd_WithMongoAtlasUriError() {
                     "MongoDB URI password should be masked");
         }
     }
+    @Nested
+    @DisplayName("Method sendTestConnection test")
+    class sendTestConnectionTest{
+        private DataSourceServiceImpl dataSourceService;
+        private DataSourceRepository dataSourceRepository;
+        private WorkerService workerService;
+        private AgentGroupService agentGroupService;
+        private MessageQueueService messageQueueService;
+
+        @BeforeEach
+        void setUp() {
+            dataSourceRepository = mock(DataSourceRepository.class);
+            workerService = mock(WorkerService.class);
+            agentGroupService = mock(AgentGroupService.class);
+            messageQueueService = mock(MessageQueueService.class);
+            dataSourceService = spy(new DataSourceServiceImpl(dataSourceRepository));
+            ReflectionTestUtils.setField(dataSourceService, "workerService", workerService);
+            ReflectionTestUtils.setField(dataSourceService, "agentGroupService", agentGroupService);
+            ReflectionTestUtils.setField(dataSourceService, "messageQueueService", messageQueueService);
+        }
+        @Test
+        void test_MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP(){
+            DataSourceConnectionDto connectionDto = new DataSourceConnectionDto();
+            connectionDto.setName("test");
+            connectionDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP");
+            connectionDto.setPriorityProcessId("work_2");
+            List<Worker> workers = new ArrayList<>();
+            Worker worker1 = new Worker();
+            worker1.setProcessId("work_1");
+            Worker worker2 = new Worker();
+            worker2.setProcessId("work_2");
+            workers.add(worker1);
+            workers.add(worker2);
+            when(workerService.findAvailableAgentByAccessNode(any(),any())).thenReturn(workers);
+            doAnswer(invocationOnMock -> {
+                MessageQueueDto queueDto = invocationOnMock.getArgument(0);
+                Assertions.assertEquals("work_2",queueDto.getReceiver());
+                return null;
+            }).when(messageQueueService).sendMessage(any());
+            dataSourceService.sendTestConnection(connectionDto,true,true,mock(UserDetail.class));
+        }
+
+        @Test
+        void test_MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP_PriorityProcessWorkIsInaction(){
+            DataSourceConnectionDto connectionDto = new DataSourceConnectionDto();
+            connectionDto.setName("test");
+            connectionDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP");
+            connectionDto.setPriorityProcessId("work_2");
+            List<Worker> workers = new ArrayList<>();
+            Worker worker1 = new Worker();
+            worker1.setProcessId("work_1");
+            workers.add(worker1);
+            when(workerService.findAvailableAgentByAccessNode(any(),any())).thenReturn(workers);
+            doAnswer(invocationOnMock -> {
+                MessageQueueDto queueDto = invocationOnMock.getArgument(0);
+                Assertions.assertEquals("work_1",queueDto.getReceiver());
+                return null;
+            }).when(messageQueueService).sendMessage(any());
+            dataSourceService.sendTestConnection(connectionDto,true,true,mock(UserDetail.class));
+        }
+
+
+
+        @Test
+        void test_MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP_PriorityProcessIdIsNull(){
+            DataSourceConnectionDto connectionDto = new DataSourceConnectionDto();
+            connectionDto.setName("test");
+            connectionDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP");
+            List<Worker> workers = new ArrayList<>();
+            Worker worker1 = new Worker();
+            worker1.setProcessId("work_1");
+            Worker worker2 = new Worker();
+            worker2.setProcessId("work_2");
+            workers.add(worker1);
+            workers.add(worker2);
+            when(workerService.findAvailableAgentByAccessNode(any(),any())).thenReturn(workers);
+            doAnswer(invocationOnMock -> {
+                MessageQueueDto queueDto = invocationOnMock.getArgument(0);
+                Assertions.assertEquals("work_1",queueDto.getReceiver());
+                return null;
+            }).when(messageQueueService).sendMessage(any());
+            dataSourceService.sendTestConnection(connectionDto,true,true,mock(UserDetail.class));
+        }
+
+    }
 }
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImplTest.java
index 6487dcf863..738a65a1a3 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImplTest.java
@@ -8,6 +8,7 @@
 import com.tapdata.tm.base.dto.Where;
 import com.tapdata.tm.base.exception.BizException;
 import com.tapdata.tm.commons.dag.DAG;
+import com.tapdata.tm.commons.dag.Edge;
 import com.tapdata.tm.commons.dag.Node;
 import com.tapdata.tm.commons.dag.logCollector.LogCollecotrConnConfig;
 import com.tapdata.tm.commons.dag.logCollector.LogCollectorNode;
@@ -24,6 +25,7 @@
 import com.tapdata.tm.commons.schema.bean.SourceDto;
 import com.tapdata.tm.commons.schema.bean.Table;
 import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.commons.util.ConnHeartbeatUtils;
 import com.tapdata.tm.commons.util.MetaDataBuilderUtils;
 import com.tapdata.tm.commons.util.MetaType;
 import com.tapdata.tm.commons.util.PdkSchemaConvert;
@@ -51,6 +53,7 @@
 import com.tapdata.tm.utils.MetadataUtil;
 import com.tapdata.tm.utils.MongoUtils;
 import com.tapdata.tm.utils.SchemaTransformUtils;
+import io.github.openlg.graphlib.Graph;
 import io.tapdata.entity.conversion.TableFieldTypesGenerator;
 import io.tapdata.entity.mapping.DefaultExpressionMatchingMap;
 import io.tapdata.entity.mapping.TypeExprResult;
@@ -58,6 +61,7 @@
 import io.tapdata.entity.schema.TapField;
 import io.tapdata.entity.schema.TapTable;
 import io.tapdata.entity.schema.type.TapType;
+import io.tapdata.entity.simplify.TapSimplify;
 import io.tapdata.entity.utils.DataMap;
 import io.tapdata.entity.utils.InstanceFactory;
 import io.tapdata.pdk.core.constants.SystemConstants;
@@ -66,6 +70,7 @@
 import org.apache.commons.lang3.tuple.Pair;
 import org.bson.Document;
 import org.bson.types.ObjectId;
+import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
@@ -2330,6 +2335,43 @@ class GetParentNodeTest{
 	}
 	@Nested
 	class LinkLogicTest{
+		@DisplayName("test linkLogic method normal update")
+		@Test
+		void test1() {
+			try (
+					MockedStatic<InstanceFactory> instanceFactoryMockedStatic = mockStatic(InstanceFactory.class);
+					MockedStatic<TapSimplify> tapSimplifyMockedStatic = mockStatic(TapSimplify.class);
+					MockedStatic<SchemaUtils> schemaUtilsMockedStatic = mockStatic(SchemaUtils.class)
+			) {
+				tapSimplifyMockedStatic.when(() -> TapSimplify.toJson(any())).thenReturn("test");
+				com.tapdata.tm.commons.schema.Schema schema = new com.tapdata.tm.commons.schema.Schema();
+				schema.setDatabase("test");
+				schemaUtilsMockedStatic.when(() -> {
+					SchemaUtils.mergeSchema(any(List.class), any(com.tapdata.tm.commons.schema.Schema.class), anyBoolean());
+				}).thenReturn(schema);
+				MetadataInstancesService metadataInstancesService = mock(MetadataInstancesServiceImpl.class);
+				TaskService taskService = mock(TaskService.class);
+				ReflectionTestUtils.setField(metadataInstancesService, "taskService", taskService);
+				List<TaskDto> taskDtos = new ArrayList<>();
+				TaskDto taskDto = new TaskDto();
+				taskDto.setId(new ObjectId("65d31d426a7c0d7571db82a7"));
+				taskDtos.add(taskDto);
+				doReturn(taskDtos).when(taskService).findAllDto(any(Query.class), any(UserDetail.class));
+				doCallRealMethod().when(metadataInstancesService).linkLogic(any(), any(), any());
+				List<MetadataInstancesDto> metadataInstancesDtos = new ArrayList<>();
+				MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+				metadataInstancesDto.setMetaType("source");
+				metadataInstancesDto.setOriginalName("testOriginalName");
+				metadataInstancesDto.setQualifiedName("testQualifiedName");
+				SourceDto sourceDto = new SourceDto();
+				sourceDto.set_id("test_Id");
+				metadataInstancesDto.setSource(sourceDto);
+				metadataInstancesDtos.add(metadataInstancesDto);
+				doReturn(metadataInstancesDtos).when(metadataInstancesService).findAllDto(any(Query.class), any(UserDetail.class));
+				metadataInstancesService.linkLogic(metadataInstancesDtos, userDetail, "65d31d426a7c0d7571db82a7");
+				verify(metadataInstancesService,times(1)).bulkUpsetByWhere(any(List.class),any(UserDetail.class));
+			}
+		}
 	}
 	@Nested
 	class DeleteTaskMetadataTest{
@@ -2729,5 +2771,108 @@ void test3(){
 				assertEquals(false,actual);
 			}
 		}
+
+		@Nested
+		class FindHeartbeatQualifiedNameByNodeIdTest {
+			private MetadataInstancesServiceImpl metadataInstancesService;
+			private TaskService taskService;
+			private DataSourceService dataSourceService;
+
+			@BeforeEach
+			void beforeEach() {
+				taskService = mock(TaskService.class);
+				metadataInstancesService = mock(MetadataInstancesServiceImpl.class);
+				dataSourceService = mock(DataSourceServiceImpl.class);
+				ReflectionTestUtils.setField(metadataInstancesService, "taskService", taskService);
+				ReflectionTestUtils.setField(metadataInstancesService, "dataSourceService", dataSourceService);
+			}
+
+			@DisplayName("test findHeartbeatQualifiedNameByNodeId method normal")
+			@Test
+			void test1() {
+				try (MockedStatic<MetaDataBuilderUtils> metaDataBuilderUtilsMockedStatic = mockStatic(MetaDataBuilderUtils.class);) {
+					TaskDto taskDto = new TaskDto();
+					taskDto.setId(new ObjectId("65ae2d427f580c320ec3bc65"));
+					taskDto.setName("testParentTask");
+					UserDetail userDetail = mock(UserDetail.class);
+					Where where = new Where();
+					where.put("nodeId", "testNodeId");
+					Filter filter = new Filter();
+					filter.setWhere(where);
+					Query query = new Query(Criteria.where("dag.nodes.id").is("testNodeId"));
+					query.fields().include("_id");
+					when(taskService.findOne(query, userDetail)).thenReturn(taskDto);
+
+					//mock HeartBeat task
+					TaskDto heartbeatTaskDto = new TaskDto();
+					heartbeatTaskDto.setId(new ObjectId("65ae40b097480b60d7d7f097"));
+					HashSet<String> heartbeatTasks = new HashSet<>();
+					heartbeatTasks.add("65ae2d427f580c320ec3bc65");
+					taskDto.setHeartbeatTasks(heartbeatTasks);
+
+					//build dag
+					DAG dag = getDag();
+
+
+					Criteria criteria = Criteria.where(ConnHeartbeatUtils.TASK_RELATION_FIELD).is("65ae2d427f580c320ec3bc65");
+					criteria.and("status").ne("deleting");
+					criteria.and("is_deleted").ne(true);
+					Query queryHeartBeat = new Query(criteria);
+					queryHeartBeat.fields().include("_id", "dag");
+					heartbeatTaskDto.setDag(dag);
+					when(taskService.findOne(queryHeartBeat, userDetail)).thenReturn(heartbeatTaskDto);
+					DataSourceConnectionDto dataSourceConnectionDto = new DataSourceConnectionDto();
+					dataSourceConnectionDto.setDefinitionPdkId("pdkId");
+					dataSourceConnectionDto.setDefinitionGroup("definitionGroup");
+					dataSourceConnectionDto.setDefinitionVersion("version");
+
+					when(dataSourceService.findById(any())).thenReturn(dataSourceConnectionDto);
+					metaDataBuilderUtilsMockedStatic.when(() -> {
+						MetaDataBuilderUtils.generatePdkQualifiedName(anyString(), anyString(), eq(ConnHeartbeatUtils.TABLE_NAME), anyString(), anyString(), anyString(), any());
+					}).thenReturn("heartBeatQualifiedName");
+					doCallRealMethod().when(metadataInstancesService).findHeartbeatQualifiedNameByNodeId(any(), any());
+					String heartbeatQualifiedNameByNodeId = metadataInstancesService.findHeartbeatQualifiedNameByNodeId(filter, userDetail);
+					assertEquals("heartBeatQualifiedName", heartbeatQualifiedNameByNodeId);
+				}
+			}
+
+			@DisplayName("test findHeartbeatQualifiedNameByNodeId method when where is null")
+			@Test
+			void test2() {
+				UserDetail userDetail = mock(UserDetail.class);
+				Filter filter = new Filter();
+				filter.setWhere(null);
+				String heartbeatQualifiedNameByNodeId = metadataInstancesService.findHeartbeatQualifiedNameByNodeId(filter, userDetail);
+				assertEquals(null, heartbeatQualifiedNameByNodeId);
+			}
+		}
+	}
+
+	public static DAG getDag() {
+		Graph<Node, Edge> graph = new Graph<>();
+		Node sourceNode = new TableNode();
+		sourceNode.setName("sourceNode");
+		sourceNode.setId("sourceId");
+		sourceNode.setGraph(graph);
+
+		TableNode targetNode = new TableNode();
+		targetNode.setName("targetNode");
+		targetNode.setId("targetId");
+		targetNode.setConnectionId("testConnectionId");
+		targetNode.setGraph(graph);
+		targetNode.setType("testType");
+
+		Edge edge = new Edge();
+		edge.setSource(sourceNode.getId());
+		edge.setTarget(targetNode.getId());
+
+		edge.setGraph(graph);
+
+		graph.setNode(sourceNode.getId(), sourceNode);
+		graph.setNode(targetNode.getId(), targetNode);
+		graph.setEdge(sourceNode.getId(), targetNode.getId(), edge);
+
+		DAG dag = new DAG(graph);
+		return dag;
 	}
 }
diff --git a/manager/tm/src/test/java/com/tapdata/tm/modules/service/ModulesServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/modules/service/ModulesServiceTest.java
new file mode 100644
index 0000000000..d202ab04d6
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/modules/service/ModulesServiceTest.java
@@ -0,0 +1,108 @@
+package com.tapdata.tm.modules.service;
+
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.commons.schema.Field;
+import com.tapdata.tm.commons.schema.Tag;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.modules.dto.ModulesDto;
+import com.tapdata.tm.modules.dto.ModulesPermissionsDto;
+import com.tapdata.tm.modules.dto.ModulesTagsDto;
+import com.tapdata.tm.modules.entity.Path;
+import com.tapdata.tm.modules.repository.ModulesRepository;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.mockito.Mockito.*;
+
+public class ModulesServiceTest {
+    ModulesService modulesService;
+    ModulesRepository modulesRepository;
+    @BeforeEach
+    void init(){
+        modulesRepository = mock(ModulesRepository.class);
+        modulesService = new ModulesService(modulesRepository);
+    }
+    @Nested
+    class UpdatePermissionsTest{
+        @Test
+        void test_main(){
+            ModulesPermissionsDto modulesPermissionsDto = new ModulesPermissionsDto();
+            modulesPermissionsDto.setModuleId("test");
+            modulesPermissionsDto.setAcl(Arrays.asList("admin"));
+            modulesService.updatePermissions(modulesPermissionsDto,mock(UserDetail.class));
+            verify(modulesRepository,times(1)).updateFirst(any(),any(),any());
+        }
+
+        @Test
+        void test_aclIsNull(){
+            ModulesPermissionsDto modulesPermissionsDto = new ModulesPermissionsDto();
+            modulesPermissionsDto.setModuleId("test");
+            Assertions.assertThrows(BizException.class,()->modulesService.updatePermissions(modulesPermissionsDto,mock(UserDetail.class)));
+        }
+
+    }
+
+    @Nested
+    class UpdateTagsTest{
+        @Test
+        void test_main(){
+            ModulesTagsDto modulesTagsDto = new ModulesTagsDto();
+            modulesTagsDto.setModuleId("test");
+            modulesTagsDto.setListtags(Arrays.asList(new Tag("id","app")));
+            modulesService.updateTags(modulesTagsDto,mock(UserDetail.class));
+            verify(modulesRepository,times(1)).updateFirst(any(),any(),any());
+        }
+
+        @Test
+        void test_tagsIsNull(){
+            ModulesTagsDto modulesTagsDto = new ModulesTagsDto();
+            modulesTagsDto.setModuleId("test");
+            Assertions.assertThrows(BizException.class,()->modulesService.updateTags(modulesTagsDto,mock(UserDetail.class)));
+        }
+
+    }
+    @Nested
+    class BeforeSaveTest{
+        @Test
+        void test_main(){
+            ModulesDto modules = new ModulesDto();
+            List<Path> paths = new ArrayList<>();
+            Path path = new Path();
+            List<Field> fields = new ArrayList<>();
+            fields.add(new Field());
+            fields.add(null);
+            fields.add(null);
+            path.setFields(fields);
+            paths.add(path);
+            modules.setPaths(paths);
+            modulesService.beforeSave(modules,mock(UserDetail.class));
+            Assertions.assertEquals(1,modules.getPaths().get(0).getFields().size());
+        }
+        @Test
+        void test_paths_isNull(){
+            ModulesDto modules = new ModulesDto();
+            List<Path> paths = new ArrayList<>();
+            modules.setPaths(paths);
+            modulesService.beforeSave(modules,mock(UserDetail.class));
+            Assertions.assertEquals(0,modules.getPaths().size());
+        }
+        @Test
+        void test_paths_fields_isNull(){
+            ModulesDto modules = new ModulesDto();
+            List<Path> paths = new ArrayList<>();
+            Path path = new Path();
+            List<Field> fields = new ArrayList<>();
+            path.setFields(fields);
+            paths.add(path);
+            modules.setPaths(paths);
+            modulesService.beforeSave(modules,mock(UserDetail.class));
+            Assertions.assertEquals(0,modules.getPaths().get(0).getFields().size());
+        }
+    }
+}
diff --git a/manager/tm/src/test/java/com/tapdata/tm/schedule/TaskResetScheduleTest.java b/manager/tm/src/test/java/com/tapdata/tm/schedule/TaskResetScheduleTest.java
new file mode 100644
index 0000000000..6141a2c18b
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/schedule/TaskResetScheduleTest.java
@@ -0,0 +1,83 @@
+package com.tapdata.tm.schedule;
+
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.task.service.TaskService;
+import com.tapdata.tm.task.service.TaskServiceImpl;
+import com.tapdata.tm.user.service.UserService;
+import com.tapdata.tm.user.service.UserServiceImpl;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.*;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+public class TaskResetScheduleTest {
+    @Nested
+    class CheckMoreThanResetTimesTest{
+        TaskResetSchedule taskResetSchedule;
+        TaskService taskService;
+        UserService userService;
+        @BeforeEach
+        void init(){
+            taskResetSchedule = new TaskResetSchedule();
+            taskService = mock(TaskServiceImpl.class);
+            userService = mock(UserServiceImpl.class);
+            ReflectionTestUtils.setField(taskResetSchedule, "resetAllTimes", 3);
+            ReflectionTestUtils.setField(taskResetSchedule, "taskService", taskService);
+            ReflectionTestUtils.setField(taskResetSchedule, "userService", userService);
+        }
+        @Test
+        void test_main(){
+            List<TaskDto> taskDtos = new ArrayList<>();
+            TaskDto taskDto1 = new TaskDto();
+            taskDto1.setUserId("user1");
+            taskDto1.setId(new ObjectId());
+            TaskDto taskDto2 = new TaskDto();
+            taskDto2.setUserId("user2");
+            taskDto2.setId(new ObjectId());
+            taskDtos.add(taskDto1);
+            taskDtos.add(taskDto2);
+            when(taskService.findAll(any(Query.class))).thenReturn(taskDtos);
+            Map<String, UserDetail> userDetailMap = new HashMap<>();
+            userDetailMap.put("user1",mock(UserDetail.class));
+            userDetailMap.put("user2",mock(UserDetail.class));
+            when(userService.getUserMapByIdList(Arrays.asList("user1", "user2"))).thenReturn(userDetailMap);
+            taskResetSchedule.checkMoreThanResetTimes();
+            verify(taskService,times(2)).update(any(Query.class),any(Update.class));
+        }
+        @Test
+        void test_TaskDtosIsEmpty(){
+            List<TaskDto> taskDtos = new ArrayList<>();
+            when(taskService.findAll(any(Query.class))).thenReturn(taskDtos);
+            taskResetSchedule.checkMoreThanResetTimes();
+            verify(taskService,times(0)).update(any(Query.class),any(Update.class));
+        }
+        @Test
+        void test_UserIsNull(){
+            List<TaskDto> taskDtos = new ArrayList<>();
+            TaskDto taskDto1 = new TaskDto();
+            taskDto1.setUserId("user1");
+            taskDto1.setId(new ObjectId());
+            TaskDto taskDto2 = new TaskDto();
+            taskDto2.setUserId("user2");
+            taskDto2.setId(new ObjectId());
+            taskDtos.add(taskDto1);
+            taskDtos.add(taskDto2);
+            when(taskService.findAll(any(Query.class))).thenReturn(taskDtos);
+            Map<String, UserDetail> userDetailMap = new HashMap<>();
+            userDetailMap.put("user1",mock(UserDetail.class));
+            userDetailMap.put("user2",null);
+            when(userService.getUserMapByIdList(Arrays.asList("user1", "user2"))).thenReturn(userDetailMap);
+            taskResetSchedule.checkMoreThanResetTimes();
+            verify(taskService,times(1)).update(any(Query.class),any(Update.class));
+        }
+    }
+}
diff --git a/manager/tm/src/test/java/com/tapdata/tm/schedule/service/impl/TaskScheduleServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/schedule/service/impl/TaskScheduleServiceImplTest.java
index e7ed0d5d7a..87e95d54b4 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/schedule/service/impl/TaskScheduleServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/schedule/service/impl/TaskScheduleServiceImplTest.java
@@ -1,5 +1,6 @@
 package com.tapdata.tm.schedule.service.impl;
 
+import com.tapdata.tm.agent.service.AgentGroupService;
 import com.tapdata.tm.base.exception.BizException;
 import com.tapdata.tm.commons.task.dto.TaskDto;
 import com.tapdata.tm.config.security.SimpleGrantedAuthority;
@@ -12,16 +13,21 @@
 import com.tapdata.tm.user.service.UserService;
 import com.tapdata.tm.utils.MongoUtils;
 import com.tapdata.tm.worker.dto.WorkerDto;
+import com.tapdata.tm.worker.entity.Worker;
 import com.tapdata.tm.worker.service.WorkerService;
 import com.tapdata.tm.worker.vo.CalculationEngineVo;
 import org.bson.types.ObjectId;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -41,6 +47,7 @@ class testCloudTaskLimitNum {
         StateMachineService stateMachineService;
         TaskDto taskDto;
         WorkerDto workerDto;
+        AgentGroupService agentGroupService;
 
         @BeforeEach
         void beforeEach() {
@@ -48,6 +55,7 @@ void beforeEach() {
             userService = mock(UserService.class);
             workerService = mock(WorkerService.class);
             stateMachineService = mock(StateMachineService.class);
+            agentGroupService = mock(AgentGroupService.class);
             taskScheduleService.setTaskService(taskService);
             taskScheduleService.setUserService(userService);
             taskScheduleService.setWorkerService(workerService);
@@ -65,12 +73,13 @@ void beforeEach() {
             workerDto.setUserId(user.getUserId());
             when(taskService.findByTaskId(taskDto.getId(), "user_id")).thenReturn(taskDto);
             when(userService.loadUserById(new ObjectId(taskDto.getUserId()))).thenReturn(user);
-            when(workerService.findByProcessId(processId, user, "user_id", "agentTags", "process_id")).thenReturn(workerDto);
-            when(workerService.getLimitTaskNum(workerDto, user)).thenReturn(2);
+            ReflectionTestUtils.setField(taskScheduleService, "agentGroupService", agentGroupService);
         }
 
         @Test
         void testSpecifiedByTheUserExceedCloudTaskLimitNum() {
+            when(workerService.findByProcessId(processId, user, "user_id", "agentTags", "process_id")).thenReturn(workerDto);
+            when(workerService.getLimitTaskNum(workerDto, user)).thenReturn(2);
             when(taskService.runningTaskNum(processId, user)).thenReturn(4);
             when(taskService.subCronOrPlanNum(taskDto,4)).thenReturn(4);
             when(stateMachineService.executeAboutTask(taskDto, DataFlowEvent.SCHEDULE_FAILED, user)).thenReturn(StateMachineResult.ok());
@@ -79,6 +88,8 @@ void testSpecifiedByTheUserExceedCloudTaskLimitNum() {
 
         @Test
         void testSpecifiedByTheUserNoExceedCloudTaskLimitNum() {
+            when(workerService.findByProcessId(processId, user, "user_id", "agentTags", "process_id")).thenReturn(workerDto);
+            when(workerService.getLimitTaskNum(workerDto, user)).thenReturn(2);
             when(taskService.runningTaskNum(processId, user)).thenReturn(3);
             when(taskService.subCronOrPlanNum(taskDto,3)).thenReturn(2);
             when(workerService.scheduleTaskToEngine(taskDto, user, "task", taskDto.getName())).thenReturn(new CalculationEngineVo());
@@ -88,6 +99,8 @@ void testSpecifiedByTheUserNoExceedCloudTaskLimitNum() {
 
         @Test
         void testNoExceedCloudTaskLimitNum() {
+            when(workerService.findByProcessId(processId, user, "user_id", "agentTags", "process_id")).thenReturn(workerDto);
+            when(workerService.getLimitTaskNum(workerDto, user)).thenReturn(2);
             CalculationEngineVo calculationEngineVo = new CalculationEngineVo();
             calculationEngineVo.setRunningNum(1);
             calculationEngineVo.setTaskLimit(2);
@@ -101,6 +114,8 @@ void testNoExceedCloudTaskLimitNum() {
 
         @Test
         void testExceedCloudTaskLimitNum() {
+            when(workerService.findByProcessId(processId, user, "user_id", "agentTags", "process_id")).thenReturn(workerDto);
+            when(workerService.getLimitTaskNum(workerDto, user)).thenReturn(2);
             CalculationEngineVo calculationEngineVo = new CalculationEngineVo();
             calculationEngineVo.setRunningNum(4);
             calculationEngineVo.setTaskLimit(2);
@@ -111,5 +126,57 @@ void testExceedCloudTaskLimitNum() {
             when(taskService.subCronOrPlanNum(taskDto,4)).thenReturn(4);
             assertThrows(BizException.class, () -> taskScheduleService.cloudTaskLimitNum(taskDto, user, false));
         }
+        @Test
+        void testTaskIsGroupManually_main(){
+            taskDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP");
+            taskDto.setPriorityProcessId("worker_test2");
+            CalculationEngineVo calculationEngineVo = new CalculationEngineVo();
+            List<Worker> availableAgent = new ArrayList<>();
+            Worker worker1 = new Worker();
+            worker1.setProcessId("worker_test1");
+            Worker worker2 = new Worker();
+            worker2.setProcessId("worker_test2");
+            availableAgent.add(worker1);
+            availableAgent.add(worker2);
+            when(agentGroupService.getProcessNodeListWithGroup(taskDto, user)).thenReturn(Arrays.asList("worker_test1","worker_test2"));
+            when(workerService.findAvailableAgentByAccessNode(any(),anyList())).thenReturn(availableAgent);
+            when(workerService.scheduleTaskToEngine(taskDto, user, "task", taskDto.getName())).thenAnswer(invocationOnMock -> {
+                TaskDto result = invocationOnMock.getArgument(0);
+                Assertions.assertEquals("worker_test2",result.getAgentId());
+                return calculationEngineVo;
+            });
+            taskScheduleService.cloudTaskLimitNum(taskDto, user, false);
+        }
+        @Test
+        void testTaskIsGroupManually_PriorityProcessId_is_null(){
+            taskDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP");
+            CalculationEngineVo calculationEngineVo = new CalculationEngineVo();
+            List<Worker> availableAgent = new ArrayList<>();
+            Worker worker1 = new Worker();
+            worker1.setProcessId("worker_test1");
+            Worker worker2 = new Worker();
+            worker2.setProcessId("worker_test2");
+            availableAgent.add(worker1);
+            availableAgent.add(worker2);
+            when(agentGroupService.getProcessNodeListWithGroup(taskDto, user)).thenReturn(Arrays.asList("worker_test1","worker_test2"));
+            when(workerService.findAvailableAgentByAccessNode(any(),anyList())).thenReturn(availableAgent);
+            when(workerService.scheduleTaskToEngine(taskDto, user, "task", taskDto.getName())).thenAnswer(invocationOnMock -> {
+                TaskDto result = invocationOnMock.getArgument(0);
+                Assertions.assertEquals("worker_test1",result.getAgentId());
+                return calculationEngineVo;
+            });
+            taskScheduleService.cloudTaskLimitNum(taskDto, user, false);
+        }
+        @Test
+        void testTaskIsGroupManually_accessNodeProcessIdList_is_Empty(){
+            taskDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP");
+            CalculationEngineVo calculationEngineVo = new CalculationEngineVo();
+            when(workerService.scheduleTaskToEngine(taskDto, user, "task", taskDto.getName())).thenAnswer(invocationOnMock -> {
+                TaskDto result = invocationOnMock.getArgument(0);
+                Assertions.assertNull(result.getAgentId());
+                return calculationEngineVo;
+            });
+            taskScheduleService.cloudTaskLimitNum(taskDto, user, false);
+        }
     }
 }
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java
index ae3f7eb29c..4db7ffde9f 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java
@@ -14,12 +14,16 @@
 import com.tapdata.tm.base.exception.BizException;
 import com.tapdata.tm.base.handler.ExceptionHandler;
 import com.tapdata.tm.commons.dag.*;
+import com.tapdata.tm.commons.dag.logCollector.LogCollecotrConnConfig;
+import com.tapdata.tm.commons.dag.logCollector.LogCollectorNode;
 import com.tapdata.tm.commons.dag.nodes.*;
 import com.tapdata.tm.commons.dag.process.*;
 import com.tapdata.tm.commons.dag.vo.SyncObjects;
 import com.tapdata.tm.commons.externalStorage.ExternalStorageDto;
 import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
 import com.tapdata.tm.commons.schema.MetadataTransformerDto;
+import com.tapdata.tm.commons.schema.bean.ResponseBody;
+import com.tapdata.tm.commons.schema.bean.ValidateDetail;
 import com.tapdata.tm.commons.task.constant.NotifyEnum;
 import com.tapdata.tm.commons.task.dto.*;
 import com.tapdata.tm.commons.task.dto.alarm.AlarmSettingVO;
@@ -30,6 +34,7 @@
 import com.tapdata.tm.dataflowinsight.dto.DataFlowInsightStatisticsDto;
 import com.tapdata.tm.disruptor.constants.DisruptorTopicEnum;
 import com.tapdata.tm.disruptor.service.DisruptorService;
+import com.tapdata.tm.ds.service.impl.DataSourceService;
 import com.tapdata.tm.ds.service.impl.DataSourceServiceImpl;
 import com.tapdata.tm.externalStorage.service.ExternalStorageService;
 import com.tapdata.tm.inspect.bean.Source;
@@ -60,6 +65,7 @@
 import com.tapdata.tm.permissions.service.DataPermissionService;
 import com.tapdata.tm.report.service.UserDataReportService;
 import com.tapdata.tm.schedule.service.ScheduleService;
+import com.tapdata.tm.shareCdcTableMapping.service.ShareCdcTableMappingService;
 import com.tapdata.tm.statemachine.enums.DataFlowEvent;
 import com.tapdata.tm.statemachine.model.StateMachineResult;
 import com.tapdata.tm.statemachine.service.StateMachineService;
@@ -86,6 +92,8 @@
 import com.tapdata.tm.worker.vo.CalculationEngineVo;
 import io.tapdata.common.sample.request.Sample;
 import io.tapdata.exception.TapCodeException;
+import io.tapdata.pdk.apis.entity.Capability;
+import io.tapdata.pdk.apis.entity.TestItem;
 import lombok.SneakyThrows;
 import org.apache.commons.lang3.StringUtils;
 import org.bson.BsonValue;
@@ -1163,6 +1171,7 @@ class AfterRemove{
         private MetadataInstancesServiceImpl metadataInstancesService;
         private MetaDataHistoryService historyService;
         private TaskCollectionObjService taskCollectionObjService;
+        private TaskRecordService taskRecordService;
         @BeforeEach
         void setUp(){
             taskAutoInspectResultsService = mock(TaskAutoInspectResultsService.class);
@@ -1170,11 +1179,13 @@ void setUp(){
             metadataInstancesService = mock(MetadataInstancesServiceImpl.class);
             historyService = mock(MetaDataHistoryService.class);
             taskCollectionObjService = mock(TaskCollectionObjService.class);
+            taskRecordService = mock(TaskRecordService.class);
             ReflectionTestUtils.setField(taskService,"taskAutoInspectResultsService",taskAutoInspectResultsService);
             ReflectionTestUtils.setField(taskService,"messageService",messageService);
             ReflectionTestUtils.setField(taskService,"metadataInstancesService",metadataInstancesService);
             ReflectionTestUtils.setField(taskService,"historyService",historyService);
             ReflectionTestUtils.setField(taskService,"taskCollectionObjService",taskCollectionObjService);
+            ReflectionTestUtils.setField(taskService,"taskRecordService",taskRecordService);
         }
         @Test
         @DisplayName("test afterRemove method for migrate")
@@ -2391,6 +2402,69 @@ void test45(){
             assertEquals(true,actual);
             assertEquals(null,taskDto.getAgentId());
         }
+        @Test
+        @DisplayName("test findAgent MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP")
+        void test6(){
+            List<Worker> list = new ArrayList<>();
+            Worker worker1 = new Worker();
+            worker1.setProcessId("work_1");
+            Worker worker2 = new Worker();
+            worker2.setProcessId("work_2");
+            list.add(worker1);
+            list.add(worker2);
+            when(workerService.findAvailableAgent(user)).thenReturn(list);
+            taskDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP");
+            taskDto.setPriorityProcessId("work_2");
+            doAnswer(invocationOnMock -> {
+                List<String> accessNodeProcessIdList = invocationOnMock.getArgument(1);
+                accessNodeProcessIdList.addAll(Arrays.asList("work_1","work_2"));
+                return false;
+            }).when(taskService).findProcessNodeListWithGroup(any(),anyList(),any());
+            doCallRealMethod().when(taskService).findAgent(taskDto,user);
+            taskService.findAgent(taskDto, user);
+            assertEquals("work_2",taskDto.getAgentId());
+        }
+        @Test
+        @DisplayName("test findAgent MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP,PriorityProcess Work lose effectiveness")
+        void test7(){
+            List<Worker> list = new ArrayList<>();
+            Worker worker1 = new Worker();
+            worker1.setProcessId("work_1");
+            list.add(worker1);
+            when(workerService.findAvailableAgent(user)).thenReturn(list);
+            taskDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP");
+            taskDto.setPriorityProcessId("work_2");
+            doAnswer(invocationOnMock -> {
+                List<String> accessNodeProcessIdList = invocationOnMock.getArgument(1);
+                accessNodeProcessIdList.addAll(Arrays.asList("work_1","work_2"));
+                return false;
+            }).when(taskService).findProcessNodeListWithGroup(any(),anyList(),any());
+            doCallRealMethod().when(taskService).findAgent(taskDto,user);
+            taskService.findAgent(taskDto, user);
+            assertEquals("work_1",taskDto.getAgentId());
+        }
+        @Test
+        @DisplayName("test findAgent MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP,PriorityProcessID is Null")
+        void test8(){
+            List<Worker> list = new ArrayList<>();
+            Worker worker1 = new Worker();
+            worker1.setProcessId("work_1");
+            Worker worker2 = new Worker();
+            worker2.setProcessId("work_2");
+            list.add(worker1);
+            list.add(worker2);
+            when(workerService.findAvailableAgent(user)).thenReturn(list);
+            taskDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP");
+            taskDto.setPriorityProcessId(null);
+            doAnswer(invocationOnMock -> {
+                List<String> accessNodeProcessIdList = invocationOnMock.getArgument(1);
+                accessNodeProcessIdList.addAll(Arrays.asList("work_1","work_2"));
+                return false;
+            }).when(taskService).findProcessNodeListWithGroup(any(),anyList(),any());
+            doCallRealMethod().when(taskService).findAgent(taskDto,user);
+            taskService.findAgent(taskDto, user);
+            assertEquals("work_1",taskDto.getAgentId());
+        }
     }
     @Nested
     class StartTest{
@@ -4818,6 +4892,7 @@ void test1(){
             Query query = Query.query(Criteria.where("dag.nodes.connectionId").is(connectionId)
                     .and("syncType").is(TaskDto.SYNC_TYPE_CONN_HEARTBEAT)
                     .and("is_deleted").is(false)
+                    .and("status").nin(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED)
             );
             doCallRealMethod().when(taskService).findHeartbeatByConnectionId(connectionId);
             taskService.findHeartbeatByConnectionId(connectionId);
@@ -4831,6 +4906,7 @@ void test2(){
             Query query = Query.query(Criteria.where("dag.nodes.connectionId").is(connectionId)
                     .and("syncType").is(TaskDto.SYNC_TYPE_CONN_HEARTBEAT)
                     .and("is_deleted").is(false)
+                    .and("status").nin(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED)
             );
             query.fields().include(includeFields);
             doCallRealMethod().when(taskService).findHeartbeatByConnectionId(connectionId,includeFields);
@@ -4904,4 +4980,254 @@ void test2(){
             verify(taskService,new Times(2)).findByTaskId(any(ObjectId.class),anyString());
         }
     }
+
+    @Nested
+    class checkSourceCdcSupportTest{
+        TaskDto taskDto;
+        DataSourceServiceImpl dataSourceService;
+        DAG dag;
+        @BeforeEach
+        void init(){
+            taskDto =mock(TaskDto.class);
+            dataSourceService = mock(DataSourceServiceImpl.class);
+            doCallRealMethod().when(taskService).checkSourceCdcSupport(taskDto);
+            dag =mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(dag);
+            ReflectionTestUtils.setField(taskService,"dataSourceService",dataSourceService);
+
+        }
+
+        @Test
+        void testSourceNodeIsNull(){
+            when(taskDto.getType()).thenReturn("cdc");
+            when(dag.getSourceNode()).thenReturn(new LinkedList<>());
+            taskService.checkSourceCdcSupport(taskDto);
+            verify(dataSourceService,times(0)).findByIdByCheck(any());
+
+        }
+
+        @Test
+        void testSourceTypeNotCdc(){
+            when(taskDto.getType()).thenReturn("initial_sync");
+            taskService.checkSourceCdcSupport(taskDto);
+            verify(dataSourceService,times(0)).findByIdByCheck(any());
+
+        }
+
+
+        @Test
+        void testSourceDataTypeIsDummy(){
+            when(taskDto.getType()).thenReturn("cdc");
+            LinkedList<Node> list = new LinkedList<>();
+            DatabaseNode databaseNode = new DatabaseNode();
+            databaseNode.setDatabaseType("Dummy");
+            list.add(databaseNode);
+            when(dag.getSourceNodes()).thenReturn(list);
+            DataSourceConnectionDto dataSourceConnectionDto = new DataSourceConnectionDto();
+            dataSourceConnectionDto.setDatabase_type("Dummy");
+            when(dataSourceService.findByIdByCheck(any())).thenReturn(dataSourceConnectionDto);
+            taskService.checkSourceCdcSupport(taskDto);
+            verify(dataSourceService,times(1)).findByIdByCheck(any());
+        }
+
+        @Test
+        void testSourceNotSupport(){
+            when(taskDto.getType()).thenReturn("cdc");
+            LinkedList<Node> list = new LinkedList<>();
+            DatabaseNode databaseNode = new DatabaseNode();
+            list.add(databaseNode);
+            when(dag.getSourceNodes()).thenReturn(list);
+            DataSourceConnectionDto dataSourceConnectionDto = new DataSourceConnectionDto();
+            dataSourceConnectionDto.setDatabase_type("Mysql");
+            List<Capability> capabilities = new ArrayList<>();
+            Capability capability = new Capability();
+            capability.setId("stream_read_function");
+            capability.setType(11);
+            capabilities.add(capability);
+            dataSourceConnectionDto.setCapabilities(capabilities);
+            ResponseBody responseBody = new ResponseBody();
+            List<ValidateDetail> validateDetails = new ArrayList<>();
+            ValidateDetail validateDetail = new ValidateDetail();
+            validateDetail.setShowMsg("Binlog is close");
+            validateDetail.setStatus("failed");
+            validateDetails.add(validateDetail);
+            responseBody.setValidateDetails(validateDetails);
+            dataSourceConnectionDto.setResponse_body(responseBody);
+            when(dataSourceService.findByIdByCheck(any())).thenReturn(dataSourceConnectionDto);
+            try {
+                taskService.checkSourceCdcSupport(taskDto);
+            }catch (Exception e){
+                Assertions.assertTrue(((BizException)e).getErrorCode().equals("source.setting.check.cdc"));
+            }
+        }
+
+        @Test
+        void testSourceSupport() {
+            when(taskDto.getType()).thenReturn("cdc");
+            LinkedList<Node> list = new LinkedList<>();
+            DatabaseNode databaseNode = new DatabaseNode();
+            list.add(databaseNode);
+            when(dag.getSourceNodes()).thenReturn(list);
+            DataSourceConnectionDto dataSourceConnectionDto = new DataSourceConnectionDto();
+            dataSourceConnectionDto.setDatabase_type("Mysql");
+            List<Capability> capabilities = new ArrayList<>();
+            Capability capability = new Capability();
+            capability.setId("stream_read_function");
+            capability.setType(11);
+            capabilities.add(capability);
+            dataSourceConnectionDto.setCapabilities(capabilities);
+            ResponseBody responseBody = new ResponseBody();
+            List<ValidateDetail> validateDetails = new ArrayList<>();
+            ValidateDetail validateDetail = new ValidateDetail();
+            validateDetail.setShowMsg(TestItem.ITEM_READ_LOG);
+            validateDetail.setStatus("passed");
+            validateDetails.add(validateDetail);
+            responseBody.setValidateDetails(validateDetails);
+            dataSourceConnectionDto.setResponse_body(responseBody);
+            when(dataSourceService.findByIdByCheck(any())).thenReturn(dataSourceConnectionDto);
+            taskService.checkSourceCdcSupport(taskDto);
+            verify(dataSourceService, times(1)).findByIdByCheck(any());
+
+        }
+
+    }
+    @Nested
+    class checkHeartTableInLogCollectorTaskTableClass{
+        TaskDto taskDto;
+        UserDetail userDetail;
+        DAG dag;
+        DataSourceService dataSourceService;
+
+        ShareCdcTableMappingService shareCdcTableMappingService;
+        LogCollectorNode logCollectorNode;
+        List<Node> nodes=new ArrayList<>();
+        List<String> connectionIds=new ArrayList<>();
+        List<String> tableNames=new ArrayList<>();
+
+        @BeforeEach
+        void init() {
+            taskDto = mock(TaskDto.class);
+            dag = mock(DAG.class);
+            when(taskDto.getSyncType()).thenReturn(TaskDto.SYNC_TYPE_LOG_COLLECTOR);
+            when(taskDto.getDag()).thenReturn(dag);
+            taskDto.setSyncType(TaskDto.SYNC_TYPE_LOG_COLLECTOR);
+            userDetail = mock(UserDetail.class);
+            dataSourceService = mock(DataSourceServiceImpl.class);
+            shareCdcTableMappingService = mock(ShareCdcTableMappingService.class);
+            ReflectionTestUtils.setField(taskService, "shareCdcTableMappingService", shareCdcTableMappingService);
+            ReflectionTestUtils.setField(taskService, "dataSourceService", dataSourceService);
+
+            logCollectorNode = new LogCollectorNode();
+
+            nodes.add(logCollectorNode);
+
+
+            connectionIds.add("665eb04d9e541e4fee4f87c5");
+            logCollectorNode.setConnectionIds(connectionIds);
+
+
+            tableNames.add("testTableName");
+            logCollectorNode.setTableNames(tableNames);
+
+            Map<String, LogCollecotrConnConfig> logCollectorConnConfigs=new HashMap<>();
+            logCollectorNode.setLogCollectorConnConfigs(logCollectorConnConfigs);
+        }
+        @DisplayName("test update task config when tableConfig update")
+        @Test
+        void test1(){
+            when(dag.getSourceNodes()).thenReturn(nodes);
+            doCallRealMethod().when(taskService).checkHeartTableInLogCollectorTaskTable(taskDto, userDetail);
+            when(taskService.addHeartBeatTable2LogCollector(any(),any(),any())).thenReturn(false);
+            taskService.checkHeartTableInLogCollectorTaskTable(taskDto, userDetail);
+            verify(taskService,times(0)).updateById(taskDto,userDetail);
+        }
+        @DisplayName("test update task config when tableConfig not update")
+        @Test
+        void test2(){
+            LogCollectorNode logCollectorNode=new LogCollectorNode();
+            List<Node> nodes=new ArrayList<>();
+            nodes.add(logCollectorNode);
+
+            List<String> connectionIds=new ArrayList<>();
+            connectionIds.add("665eb04d9e541e4fee4f87c5");
+            logCollectorNode.setConnectionIds(connectionIds);
+
+            List<String> tableNames=new ArrayList<>();
+            tableNames.add("testTableName");
+            logCollectorNode.setTableNames(tableNames);
+
+            Map<String, LogCollecotrConnConfig> logCollectorConnConfigs=new HashMap<>();
+            logCollectorNode.setLogCollectorConnConfigs(logCollectorConnConfigs);
+
+            when(dag.getSourceNodes()).thenReturn(nodes);
+            doCallRealMethod().when(taskService).checkHeartTableInLogCollectorTaskTable(taskDto, userDetail);
+            when(taskService.addHeartBeatTable2LogCollector(any(),any(),any())).thenReturn(true);
+            taskService.checkHeartTableInLogCollectorTaskTable(taskDto, userDetail);
+            verify(taskService,times(1)).updateById(taskDto,userDetail);
+        }
+    }
+    @Nested
+    class testUpdateTableNameClass{
+        Set<String> tableNames;
+
+        @BeforeEach
+        void init() {
+            tableNames = new HashSet<>();
+            tableNames.add("testTable");
+        }
+        @DisplayName("test addHeartBeatTable2LogCollector method if connection enableHeartBeat")
+        @Test
+        void test1(){
+            List<DataSourceConnectionDto> dataSourceConnectionList=new ArrayList<>();
+            DataSourceConnectionDto dataSourceConnectionDto=new DataSourceConnectionDto();
+            dataSourceConnectionDto.setId(MongoUtils.toObjectId("665eb04d9e541e4fee4f87c5"));
+            dataSourceConnectionDto.setHeartbeatEnable(true);
+            dataSourceConnectionList.add(dataSourceConnectionDto);
+
+            Map<String, LogCollecotrConnConfig> logCollectorConnConfigs =new HashMap<>();
+            doCallRealMethod().when(taskService).addHeartBeatTable2LogCollector(dataSourceConnectionList,logCollectorConnConfigs,tableNames);
+            boolean updateConfig = taskService.addHeartBeatTable2LogCollector(dataSourceConnectionList, logCollectorConnConfigs, tableNames);
+            assertEquals(true,updateConfig);
+            assertEquals(2,tableNames.size());
+        }
+        @DisplayName("test addHeartBeatTable2LogCollector method if connection disableHeartBeat")
+        @Test
+        void test2(){
+            List<DataSourceConnectionDto> dataSourceConnectionList=new ArrayList<>();
+            DataSourceConnectionDto dataSourceConnectionDto=new DataSourceConnectionDto();
+            dataSourceConnectionDto.setId(MongoUtils.toObjectId("665eb04d9e541e4fee4f87c5"));
+            dataSourceConnectionDto.setHeartbeatEnable(false);
+            dataSourceConnectionList.add(dataSourceConnectionDto);
+
+            Map<String, LogCollecotrConnConfig> logCollectorConnConfigs =new HashMap<>();
+            doCallRealMethod().when(taskService).addHeartBeatTable2LogCollector(dataSourceConnectionList,logCollectorConnConfigs,tableNames);
+            boolean updateConfig = taskService.addHeartBeatTable2LogCollector(dataSourceConnectionList, logCollectorConnConfigs, tableNames);
+
+            assertEquals(false,updateConfig);
+            assertEquals(1,tableNames.size());
+        }
+        @Test
+        void test3(){
+            String connectionId="665eb04d9e541e4fee4f87c5";
+            List<DataSourceConnectionDto> dataSourceConnectionList=new ArrayList<>();
+            DataSourceConnectionDto dataSourceConnectionDto=new DataSourceConnectionDto();
+            dataSourceConnectionDto.setId(MongoUtils.toObjectId(connectionId));
+            dataSourceConnectionDto.setHeartbeatEnable(true);
+            dataSourceConnectionList.add(dataSourceConnectionDto);
+
+            Map<String, LogCollecotrConnConfig> logCollectorConnConfigs =new HashMap<>();
+
+            List<String> tableNamesList=new ArrayList<>();
+            tableNamesList.add("testTable");
+            LogCollecotrConnConfig logCollecotrConnConfig=new LogCollecotrConnConfig(connectionId, tableNamesList);
+            logCollectorConnConfigs.put(connectionId, logCollecotrConnConfig);
+
+            doCallRealMethod().when(taskService).addHeartBeatTable2LogCollector(dataSourceConnectionList,logCollectorConnConfigs,tableNames);
+            boolean updateConfig = taskService.addHeartBeatTable2LogCollector(dataSourceConnectionList, logCollectorConnConfigs, tableNames);
+            assertEquals(true,updateConfig);
+            assertEquals(2,tableNames.size());
+            assertEquals(2,logCollecotrConnConfig.getTableNames().size());
+        }
+
+    }
 }
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceTest.java
index c1a2ccfa43..5db2509f98 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceTest.java
@@ -59,6 +59,7 @@
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.doNothing;
 import static org.springframework.beans.BeanUtils.*;
 
 @ExtendWith(MockitoExtension.class)
@@ -1661,6 +1662,7 @@ void test() {
             doNothing().when(taskService).checkTaskInspectFlag(any());
             doNothing().when(taskService).checkDagAgentConflict(taskDto,userDetail,true);
             doNothing().when(taskService).checkDDLConflict(any());
+            doNothing().when(taskService).checkSourceCdcSupport(taskDto);
             doAnswer(invocationOnMock -> {
                 Update set = invocationOnMock.getArgument(1);
                 Document result = (Document) set.getUpdateObject().get("$unset");
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/TransformSchemaServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/TransformSchemaServiceTest.java
index b9841c2cda..2be60450e3 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/task/service/TransformSchemaServiceTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/TransformSchemaServiceTest.java
@@ -503,5 +503,44 @@ void testConnConfigMapIsEmpty(){
             Assertions.assertEquals(0,result.size());
         }
     }
+    @Nested
+    class GetTableNodeSameMetadataInstancesTest{
+        @Test
+        void test_main(){
+            TaskDto taskDto = new TaskDto();
+            taskDto.setId(new ObjectId());
+            DAG dag = mock(DAG.class);
+            List<Node> nodes = new ArrayList<>();
+            TableNode tableNode1 = new TableNode();
+            tableNode1.setId("tableNode1");
+            TableNode tableNode2 = new TableNode();
+            tableNode2.setId("tableNode2");
+            nodes.add(tableNode1);
+            nodes.add(tableNode2);
+            when(dag.getSourceNodes()).thenReturn(nodes);
+            when(metadataInstancesService.getQualifiedNameByNodeId(any(),any(),any(),any(),any())).thenReturn("test");
+            taskDto.setDag(dag);
+            doCallRealMethod().when(transformSchemaService).getTableNodeSameMetadataInstances(any(),any());
+            Map<String,List<String>> result = transformSchemaService.getTableNodeSameMetadataInstances(taskDto,mock(UserDetail.class));
+            result.get("test").forEach(s -> Assertions.assertTrue(s.equals("tableNode1") || s.equals("tableNode2")));
+        }
+
+        @Test
+        void test_tableNodeIsEmpty(){
+            TaskDto taskDto = new TaskDto();
+            taskDto.setId(new ObjectId());
+            DAG dag = mock(DAG.class);
+            List<Node> nodes = new ArrayList<>();
+            DatabaseNode databaseNode = new DatabaseNode();
+            nodes.add(databaseNode);
+            when(dag.getSourceNodes()).thenReturn(nodes);
+            when(metadataInstancesService.getQualifiedNameByNodeId(any(),any(),any(),any(),any())).thenReturn("test");
+            taskDto.setDag(dag);
+            doCallRealMethod().when(transformSchemaService).getTableNodeSameMetadataInstances(any(),any());
+            Map<String,List<String>> result = transformSchemaService.getTableNodeSameMetadataInstances(taskDto,mock(UserDetail.class));
+            Assertions.assertEquals(0,result.size());
+        }
+
+    }
 
 }
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/batchin/ParseRelMigFileTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/batchin/ParseRelMigFileTest.java
index cd09025cd0..b5a03e1aa5 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/task/service/batchin/ParseRelMigFileTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/batchin/ParseRelMigFileTest.java
@@ -1,21 +1,48 @@
 package com.tapdata.tm.task.service.batchin;
 
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.task.service.batchin.constant.KeyWords;
+import com.tapdata.tm.task.service.batchin.dto.TablePathInfo;
+import com.tapdata.tm.task.service.batchin.entity.GenericPropertiesParam;
 import com.tapdata.tm.task.service.batchin.entity.ParseParam;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
+import org.springframework.mock.web.MockMultipartFile;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.io.FileInputStream;
+import java.net.URL;
+import java.util.*;
 
-import static org.mockito.Mockito.mock;
+import static com.tapdata.tm.task.service.batchin.ParseRelMig.RM_ID;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.*;
 
 class ParseRelMigFileTest {
+    ParseRelMigFile parseRelMigFile;
+    FileInputStream fileInputStream;
+    MockMultipartFile mockMultipartFile;
+    String rmJson;
+    @BeforeEach
+    void beforeEach() throws Exception {
+        URL resource = this.getClass().getClassLoader().getResource("test.relmig");
+        fileInputStream = new FileInputStream(resource.getFile());
+        mockMultipartFile = new MockMultipartFile("test.relmig", fileInputStream);
+        rmJson = new String(mockMultipartFile.getBytes());
+
+        HashMap<String, Object> rmProject = new ObjectMapper().readValue(rmJson, HashMap.class);
+        ParseParam param = new ParseParam()
+                .withMultipartFile(mockMultipartFile);
+        param.setRelMigStr(rmJson);
+        param.setRelMigInfo(rmProject);
+        parseRelMigFile = spy(new ParseRelMigFile(param) {
+            @Override
+            public List<TaskDto> parse() {
+                return null;
+            }
+        });
+    }
     @Test
     void testNew() {
         Assertions.assertDoesNotThrow(() -> new ParseRelMigFile(new ParseParam()) {
@@ -55,4 +82,56 @@ void init() {
 
         }
     }
+    @Nested
+    class scanOneChildrenTest{
+        String parentId = "1334091b-5ca0-4ad1-bd0b-c461d30b80ee";
+        String childId = "559c3fe5-d721-4218-8bcc-0ee6a0545e02";
+        GenericPropertiesParam propertiesParam;
+        @BeforeEach
+        void beforeEach(){
+            Map<String, Object> full = (Map<String, Object>) parseRelMigFile.schema.get("full");
+            Map<String, Object> contentMapping = (Map<String, Object>) parseRelMigFile.content.get("mappings");
+            Map<String, Object> relationships = (Map<String, Object>) parseRelMigFile.content.get("relationships");
+            Map<String, Object> relationshipsMapping = (Map<String, Object>) relationships.get("mappings");
+            propertiesParam = GenericPropertiesParam.of()
+                    .withContentMapping(contentMapping)
+                    .withRelationshipsMapping(relationshipsMapping)
+                    .withFull(full)
+                    .withSourceToJS(new HashMap<>())
+                    .withRenameFields(new HashMap<>());
+        }
+        @Test
+        @DisplayName("test for embedded document array when parent target path is empty")
+        void test1(){
+            Map<String, Object> rootProperties = new HashMap<>();
+            rootProperties.put(KeyWords.TARGET_PATH, KeyWords.EMPTY);
+            rootProperties.put(RM_ID, parentId);
+            propertiesParam.withParent(rootProperties);
+            doNothing().when(parseRelMigFile).scanAllFieldKeys(any(),anyMap(),anyMap());
+            Map<String, Object> actual = parseRelMigFile.scanOneChildren(propertiesParam, childId);
+            assertEquals("direct",actual.get("targetPath"));
+        }
+        @Test
+        @DisplayName("test for embedded document array when parent target path is not empty")
+        void test2(){Map<String, Object> rootProperties = new HashMap<>();
+            rootProperties.put(KeyWords.TARGET_PATH, "parent");
+            rootProperties.put(RM_ID, parentId);
+            propertiesParam.withParent(rootProperties);
+            doNothing().when(parseRelMigFile).scanAllFieldKeys(any(),anyMap(),anyMap());
+            Map<String, Object> actual = parseRelMigFile.scanOneChildren(propertiesParam, childId);
+            assertEquals("parent.direct",actual.get("targetPath"));
+        }
+    }
+    @Nested
+    class getTableSchemaTest{
+        @Test
+        void testGetTableSchemaSimple(){
+            Map<String, Object> full = (Map<String, Object>) parseRelMigFile.schema.get("full");
+            Map<String, Object> mappings = (Map<String, Object>) parseRelMigFile.content.get("mappings");
+            Map<String, Object> contentMapping = (Map<String, Object>) mappings.get("1334091b-5ca0-4ad1-bd0b-c461d30b80ee");
+            TablePathInfo tableInfo = parseRelMigFile.getTablePathInfo(contentMapping);
+            Map<String, Object> actual = parseRelMigFile.getTableSchema(full, tableInfo);
+            assertNotNull(actual);
+        }
+    }
 }
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/impl/LdpServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/impl/LdpServiceImplTest.java
index 588585111d..3aba8b48da 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/task/service/impl/LdpServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/impl/LdpServiceImplTest.java
@@ -4,22 +4,28 @@
 import com.tapdata.tm.agent.service.AgentGroupService;
 import com.tapdata.tm.commons.dag.AccessNodeTypeEnum;
 import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
+import com.tapdata.tm.commons.schema.MetadataInstancesDto;
+import com.tapdata.tm.commons.schema.Tag;
+import com.tapdata.tm.commons.schema.bean.SourceDto;
 import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.metadatainstance.service.MetadataInstancesService;
+import com.tapdata.tm.metadatainstance.service.MetadataInstancesServiceImpl;
 import com.tapdata.tm.worker.entity.Worker;
 import com.tapdata.tm.worker.service.WorkerService;
+import org.bson.types.ObjectId;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.test.util.ReflectionTestUtils;
 
+import java.lang.reflect.Array;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
 
 class LdpServiceImplTest {
 
@@ -28,6 +34,7 @@ class LdpServiceImplTest {
     WorkerService workerService;
 
     UserDetail user;
+    MetadataInstancesService metadataInstancesService;
 
     @BeforeEach
     void init() {
@@ -36,7 +43,8 @@ void init() {
         ReflectionTestUtils.setField(ldpService, "agentGroupService", agentGroupService);
         workerService = mock(WorkerService.class);
         ReflectionTestUtils.setField(ldpService, "workerService", workerService);
-
+        metadataInstancesService = mock(MetadataInstancesServiceImpl.class);
+        ReflectionTestUtils.setField(ldpService, "metadataInstancesService", metadataInstancesService);
         user = mock(UserDetail.class);
     }
 
@@ -119,4 +127,62 @@ void testProcessNodeListWithGroupNotContainsId() {
             Assertions.assertNull(nodeId);
         }
     }
+    @Nested
+    class CleanLdpMeta{
+        @Test
+        void test_main(){
+            List<MetadataInstancesDto> metadataInstancesDtos = new ArrayList<>();
+            MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+            metadataInstancesDto1.setListtags(Arrays.asList(new Tag("id","test")));
+            metadataInstancesDto1.setAncestorsName("test");
+            SourceDto sourceDto = new SourceDto();
+            sourceDto.setId(new ObjectId());
+            metadataInstancesDto1.setSource(sourceDto);
+            metadataInstancesDtos.add(metadataInstancesDto1);
+            doCallRealMethod().when(ldpService).cleanLdpMeta(any(),any());
+            ldpService.cleanLdpMeta(metadataInstancesDtos,mock(UserDetail.class));
+            verify(metadataInstancesService,times(1)).deleteAll(any(),any());
+        }
+
+        @Test
+        void test_tagIdIsNull(){
+            List<MetadataInstancesDto> metadataInstancesDtos = new ArrayList<>();
+            MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+            metadataInstancesDto1.setAncestorsName("test");
+            SourceDto sourceDto = new SourceDto();
+            sourceDto.setId(new ObjectId());
+            metadataInstancesDto1.setSource(sourceDto);
+            metadataInstancesDtos.add(metadataInstancesDto1);
+            doCallRealMethod().when(ldpService).cleanLdpMeta(any(),any());
+            ldpService.cleanLdpMeta(metadataInstancesDtos,mock(UserDetail.class));
+            verify(metadataInstancesService,times(0)).deleteAll(any(),any());
+        }
+
+        @Test
+        void test_SourceIdIsNull(){
+            List<MetadataInstancesDto> metadataInstancesDtos = new ArrayList<>();
+            MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+            metadataInstancesDto1.setListtags(Arrays.asList(new Tag("id","test")));
+            metadataInstancesDto1.setAncestorsName("test");
+            metadataInstancesDtos.add(metadataInstancesDto1);
+            doCallRealMethod().when(ldpService).cleanLdpMeta(any(),any());
+            ldpService.cleanLdpMeta(metadataInstancesDtos,mock(UserDetail.class));
+            verify(metadataInstancesService,times(0)).deleteAll(any(),any());
+        }
+
+        @Test
+        void test_AncestorsNameIsNull(){
+            List<MetadataInstancesDto> metadataInstancesDtos = new ArrayList<>();
+            MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+            metadataInstancesDto1.setListtags(Arrays.asList(new Tag("id","test")));
+            SourceDto sourceDto = new SourceDto();
+            sourceDto.setId(new ObjectId());
+            metadataInstancesDto1.setSource(sourceDto);
+            metadataInstancesDtos.add(metadataInstancesDto1);
+            doCallRealMethod().when(ldpService).cleanLdpMeta(any(),any());
+            ldpService.cleanLdpMeta(metadataInstancesDtos,mock(UserDetail.class));
+            verify(metadataInstancesService,times(0)).deleteAll(any(),any());
+        }
+
+    }
 }
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/impl/TaskNodeServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/impl/TaskNodeServiceImplTest.java
index 3fd2a0bcca..880ec5709c 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/task/service/impl/TaskNodeServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/impl/TaskNodeServiceImplTest.java
@@ -2,11 +2,26 @@
 
 
 import com.tapdata.tm.agent.service.AgentGroupService;
+import com.tapdata.tm.base.dto.Page;
 import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.commons.dag.DAG;
+import com.tapdata.tm.commons.dag.DAGDataServiceImpl;
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.NodeEnum;
+import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
+import com.tapdata.tm.commons.dag.process.MigrateDateProcessorNode;
+import com.tapdata.tm.commons.dag.process.MigrateJsProcessorNode;
+import com.tapdata.tm.commons.dag.process.MigrateUnionProcessorNode;
+import com.tapdata.tm.commons.dag.process.StandardMigrateJsProcessorNode;
+import com.tapdata.tm.commons.schema.Field;
+import com.tapdata.tm.commons.schema.MetadataInstancesDto;
 import com.tapdata.tm.commons.task.dto.TaskDto;
 import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.ds.service.impl.DataSourceService;
 import com.tapdata.tm.messagequeue.dto.MessageQueueDto;
 import com.tapdata.tm.messagequeue.service.MessageQueueService;
+import com.tapdata.tm.metadatainstance.service.MetadataInstancesService;
+import com.tapdata.tm.metadatainstance.service.MetadataInstancesServiceImpl;
 import com.tapdata.tm.worker.entity.Worker;
 import com.tapdata.tm.worker.service.WorkerService;
 import org.junit.jupiter.api.Assertions;
@@ -15,10 +30,7 @@
 import org.junit.jupiter.api.Test;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doCallRealMethod;
@@ -35,6 +47,7 @@ class TaskNodeServiceImplTest {
     MessageQueueService messageQueueService;
 
     UserDetail userDetail;
+    MetadataInstancesService metadataInstancesService;
 
     @BeforeEach
     void init() {
@@ -45,6 +58,9 @@ void init() {
         ReflectionTestUtils.setField(taskNodeService, "workerService", workerService);
         messageQueueService = mock(MessageQueueService.class);
         ReflectionTestUtils.setField(taskNodeService, "messageQueueService", messageQueueService);
+        ReflectionTestUtils.setField(taskNodeService, "dataSourceService", mock(DataSourceService.class));
+        metadataInstancesService = mock(MetadataInstancesService.class);
+        ReflectionTestUtils.setField(taskNodeService, "metadataInstancesService", metadataInstancesService);
 
         userDetail = mock(UserDetail.class);
     }
@@ -106,4 +122,236 @@ void testNormal() {
             verify(taskNodeService, times(1)).sendMessageAfterFindAgent(any(TaskDto.class), any(TaskDto.class), any(UserDetail.class));
         }
     }
+    @Nested
+    class CheckMigrateTableSelectType{
+        @Test
+        void test_type_all(){
+            doCallRealMethod().when(taskNodeService).getMigrateTableNames(any(),any());
+            DatabaseNode databaseNode = new DatabaseNode();
+            databaseNode.setMigrateTableSelectType("all");
+            databaseNode.setTableNames(Arrays.asList("table1","table2"));
+            List<String> result = taskNodeService.getMigrateTableNames(databaseNode,mock(UserDetail.class));
+            Assertions.assertEquals(2,result.size());
+        }
+
+        @Test
+        void test_type_expression_main(){
+            doCallRealMethod().when(taskNodeService).getMigrateTableNames(any(),any());
+            DatabaseNode databaseNode = new DatabaseNode();
+            databaseNode.setMigrateTableSelectType("expression");
+            databaseNode.setTableExpression("test.*");
+            taskNodeService.getMigrateTableNames(databaseNode,mock(UserDetail.class));
+            MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+            metadataInstancesDto1.setOriginalName("test1");
+            MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+            metadataInstancesDto2.setOriginalName("test2");
+            MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+            when(metadataInstancesService.findSourceSchemaBySourceId(any(),any(),any(),any())).thenReturn(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3));
+            List<String> result = taskNodeService.getMigrateTableNames(databaseNode,mock(UserDetail.class));
+            Assertions.assertEquals(2,result.size());
+        }
+
+        @Test
+        void test_type_expression_tableExpression_isNull(){
+            doCallRealMethod().when(taskNodeService).getMigrateTableNames(any(),any());
+            DatabaseNode databaseNode = new DatabaseNode();
+            databaseNode.setMigrateTableSelectType("expression");
+            taskNodeService.getMigrateTableNames(databaseNode,mock(UserDetail.class));
+            MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+            metadataInstancesDto1.setOriginalName("test1");
+            MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+            metadataInstancesDto2.setOriginalName("test2");
+            MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+            when(metadataInstancesService.findSourceSchemaBySourceId(any(),any(),any(),any())).thenReturn(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3));
+            List<String> result = taskNodeService.getMigrateTableNames(databaseNode,mock(UserDetail.class));
+            Assertions.assertEquals(0,result.size());
+        }
+
+        @Test
+        void test_type_expression_NoPrimaryKeyTableSelectType_is_HasKeys(){
+            doCallRealMethod().when(taskNodeService).getMigrateTableNames(any(),any());
+            DatabaseNode databaseNode = new DatabaseNode();
+            databaseNode.setMigrateTableSelectType("expression");
+            databaseNode.setTableExpression("test.*");
+            databaseNode.setNoPrimaryKeyTableSelectType("HasKeys");
+            taskNodeService.getMigrateTableNames(databaseNode,mock(UserDetail.class));
+            MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+            metadataInstancesDto1.setOriginalName("test1");
+            Field field1 = new Field();
+            field1.setPrimaryKey(true);
+            metadataInstancesDto1.setFields(Arrays.asList(field1));
+            MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+            Field field2 = new Field();
+            field2.setPrimaryKey(false);
+            metadataInstancesDto2.setOriginalName("test2");
+            metadataInstancesDto2.setFields(Arrays.asList(field2));
+            MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+            when(metadataInstancesService.findSourceSchemaBySourceId(any(),any(),any(),any())).thenReturn(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3));
+            List<String> result = taskNodeService.getMigrateTableNames(databaseNode,mock(UserDetail.class));
+            Assertions.assertEquals(1,result.size());
+        }
+
+        @Test
+        void test_type_expression_NoPrimaryKeyTableSelectType_is_NoKeys(){
+            doCallRealMethod().when(taskNodeService).getMigrateTableNames(any(),any());
+            DatabaseNode databaseNode = new DatabaseNode();
+            databaseNode.setMigrateTableSelectType("expression");
+            databaseNode.setTableExpression("test.*");
+            databaseNode.setNoPrimaryKeyTableSelectType("NoKeys");
+            taskNodeService.getMigrateTableNames(databaseNode,mock(UserDetail.class));
+            MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+            metadataInstancesDto1.setOriginalName("test1");
+            Field field1 = new Field();
+            field1.setPrimaryKey(true);
+            metadataInstancesDto1.setFields(Arrays.asList(field1));
+            MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+            Field field2 = new Field();
+            field2.setPrimaryKey(false);
+            metadataInstancesDto2.setOriginalName("test2");
+            metadataInstancesDto2.setFields(Arrays.asList(field2));
+            MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+            when(metadataInstancesService.findSourceSchemaBySourceId(any(),any(),any(),any())).thenReturn(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3));
+            List<String> result = taskNodeService.getMigrateTableNames(databaseNode,mock(UserDetail.class));
+            Assertions.assertEquals(1,result.size());
+        }
+        @Test
+        void test_type_expression_NoPrimaryKeyTableSelectType_is_All(){
+            doCallRealMethod().when(taskNodeService).getMigrateTableNames(any(),any());
+            DatabaseNode databaseNode = new DatabaseNode();
+            databaseNode.setMigrateTableSelectType("expression");
+            databaseNode.setTableExpression("test.*");
+            databaseNode.setNoPrimaryKeyTableSelectType("All");
+            taskNodeService.getMigrateTableNames(databaseNode,mock(UserDetail.class));
+            MetadataInstancesDto metadataInstancesDto1 = new MetadataInstancesDto();
+            metadataInstancesDto1.setOriginalName("test1");
+            Field field1 = new Field();
+            field1.setPrimaryKey(true);
+            metadataInstancesDto1.setFields(Arrays.asList(field1));
+            MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
+            Field field2 = new Field();
+            field2.setPrimaryKey(false);
+            metadataInstancesDto2.setOriginalName("test2");
+            metadataInstancesDto2.setFields(Arrays.asList(field2));
+            MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
+            when(metadataInstancesService.findSourceSchemaBySourceId(any(),any(),any(),any())).thenReturn(Arrays.asList(metadataInstancesDto1,metadataInstancesDto2,metadataInstancesDto3));
+            List<String> result = taskNodeService.getMigrateTableNames(databaseNode,mock(UserDetail.class));
+            Assertions.assertEquals(2,result.size());
+        }
+
+    }
+    @Nested
+    class CheckUnionProcess{
+        @Test
+        void test_main(){
+            DAG dag = mock(DAG.class);
+            LinkedList<Node<?>> preNodes = new LinkedList<>();
+            preNodes.add(new MigrateDateProcessorNode());
+            when(dag.getPreNodes(any())).thenReturn(preNodes);
+            doCallRealMethod().when(taskNodeService).checkUnionProcess(any(),any(),any());
+            List<String> exceptTableNames = new ArrayList<>();
+            taskNodeService.checkUnionProcess(dag,"nodeId",exceptTableNames);
+            Assertions.assertEquals(0,exceptTableNames.size());
+        }
+        @Test
+        void test_hashUnionNode(){
+            DAG dag = mock(DAG.class);
+            LinkedList<Node<?>> preNodes = new LinkedList<>();
+            MigrateUnionProcessorNode migrateUnionProcessorNode = new MigrateUnionProcessorNode();
+            migrateUnionProcessorNode.setTableName("union_test");
+            preNodes.add(migrateUnionProcessorNode);
+            when(dag.getPreNodes(any())).thenReturn(preNodes);
+            doCallRealMethod().when(taskNodeService).checkUnionProcess(any(),any(),any());
+            List<String> exceptTableNames = new ArrayList<>();
+            taskNodeService.checkUnionProcess(dag,"nodeId",exceptTableNames);
+            Assertions.assertEquals("union_test",exceptTableNames.get(0));
+        }
+        @Test
+        void test_jsNode(){
+            DAG dag = mock(DAG.class);
+            LinkedList<Node<?>> preNodes = new LinkedList<>();
+            preNodes.add(new MigrateUnionProcessorNode());
+            when(dag.getPreNodes(any())).thenReturn(preNodes);
+            when(dag.getNode(any())).thenReturn((Node)new StandardMigrateJsProcessorNode());
+            doCallRealMethod().when(taskNodeService).checkUnionProcess(any(),any(),any());
+            List<String> exceptTableNames = new ArrayList<>();
+            taskNodeService.checkUnionProcess(dag,"nodeId",exceptTableNames);
+            Assertions.assertEquals(0,exceptTableNames.size());
+        }
+    }
+    @Nested
+    class GetNodeIdIfContainsMigrateUnionNodeTest {
+        DatabaseNode sourceNode;
+        DAG dag;
+        List<Node> nodes;
+        Node node1;
+        Node node2;
+        @BeforeEach
+        void init() {
+            sourceNode = mock(DatabaseNode.class);
+            dag = mock(DAG.class);
+            nodes = new ArrayList<>();
+            node1 = mock(Node.class);
+            node2 = mock(Node.class);
+            nodes.add(node1);
+            nodes.add(null);
+            nodes.add(node2);
+            when(node1.getType()).thenReturn("type");
+            when(node2.getType()).thenReturn(NodeEnum.migrate_union_processor.name());
+            when(sourceNode.getDag()).thenReturn(dag);
+            when(sourceNode.getId()).thenReturn("id");
+            when(dag.getNodes()).thenReturn(nodes);
+            when(node1.getId()).thenReturn("id");
+            when(node2.getId()).thenReturn("id");
+            when(taskNodeService.getNodeIdIfContainsMigrateUnionNode(any(DatabaseNode.class))).thenCallRealMethod();
+        }
+
+        @Test
+        void testNormal() {
+            Assertions.assertNotNull(taskNodeService.getNodeIdIfContainsMigrateUnionNode(sourceNode));
+            verify(node1, times(1)).getType();
+            verify(node2, times(1)).getType();
+            verify(sourceNode, times(1)).getDag();
+            verify(sourceNode, times(0)).getId();
+            verify(node1, times(0)).getId();
+            verify(node2, times(1)).getId();
+            verify(dag, times(1)).getNodes();
+        }
+        @Test
+        void testDAGIsNull() {
+            when(sourceNode.getDag()).thenReturn(null);
+            Assertions.assertNotNull(taskNodeService.getNodeIdIfContainsMigrateUnionNode(sourceNode));
+            verify(node1, times(0)).getType();
+            verify(node2, times(0)).getType();
+            verify(sourceNode, times(1)).getDag();
+            verify(sourceNode, times(1)).getId();
+            verify(node1, times(0)).getId();
+            verify(node2, times(0)).getId();
+            verify(dag, times(0)).getNodes();
+        }
+        @Test
+        void testNodesIsEmpty() {
+            nodes.clear();
+            Assertions.assertNotNull(taskNodeService.getNodeIdIfContainsMigrateUnionNode(sourceNode));
+            verify(node1, times(0)).getType();
+            verify(node2, times(0)).getType();
+            verify(sourceNode, times(1)).getDag();
+            verify(sourceNode, times(1)).getId();
+            verify(node1, times(0)).getId();
+            verify(node2, times(0)).getId();
+            verify(dag, times(1)).getNodes();
+        }
+        @Test
+        void testNotContainsUnionNode() {
+            nodes.clear();
+            nodes.add(node1);
+            Assertions.assertNotNull(taskNodeService.getNodeIdIfContainsMigrateUnionNode(sourceNode));
+            verify(node1, times(1)).getType();
+            verify(node2, times(0)).getType();
+            verify(sourceNode, times(1)).getDag();
+            verify(sourceNode, times(1)).getId();
+            verify(node1, times(0)).getId();
+            verify(node2, times(0)).getId();
+            verify(dag, times(1)).getNodes();
+        }
+    }
 }
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/impl/TaskRecordServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/impl/TaskRecordServiceImplTest.java
new file mode 100644
index 0000000000..114c251cd8
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/impl/TaskRecordServiceImplTest.java
@@ -0,0 +1,33 @@
+package com.tapdata.tm.task.service.impl;
+
+import com.tapdata.tm.task.entity.TaskRecord;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.mockito.Mockito.*;
+
+public class TaskRecordServiceImplTest {
+    @Nested
+    class CleanTaskRecordTest{
+        TaskRecordServiceImpl taskRecordService;
+        MongoTemplate mongoTemplate;
+        @BeforeEach
+        void init(){
+            mongoTemplate = mock(MongoTemplate.class);
+            taskRecordService = new TaskRecordServiceImpl();
+            ReflectionTestUtils.setField(taskRecordService,"mongoTemplate",mongoTemplate);
+        }
+
+        @Test
+        void test_main(){
+            taskRecordService.cleanTaskRecord("test");
+            Query query = new Query(Criteria.where("taskId").is("test"));
+            verify(mongoTemplate,times(1)).remove(eq(query), eq(TaskRecord.class));
+        }
+    }
+}
diff --git a/manager/tm/src/test/java/com/tapdata/tm/ws/handler/TestConnectionHandlerTest.java b/manager/tm/src/test/java/com/tapdata/tm/ws/handler/TestConnectionHandlerTest.java
index 8975aaf26c..8ba451a0d3 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/ws/handler/TestConnectionHandlerTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/ws/handler/TestConnectionHandlerTest.java
@@ -1,5 +1,6 @@
 package com.tapdata.tm.ws.handler;
 
+import com.tapdata.tm.agent.service.AgentGroupService;
 import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
 import com.tapdata.tm.commons.schema.DataSourceDefinitionDto;
 import com.tapdata.tm.config.security.UserDetail;
@@ -7,23 +8,20 @@
 import com.tapdata.tm.ds.service.impl.DataSourceService;
 import com.tapdata.tm.messagequeue.service.MessageQueueService;
 import com.tapdata.tm.user.service.UserService;
+import com.tapdata.tm.worker.entity.Worker;
+import com.tapdata.tm.worker.service.WorkerService;
 import com.tapdata.tm.ws.dto.MessageInfo;
 import com.tapdata.tm.ws.dto.WebSocketContext;
 import com.tapdata.tm.ws.dto.WebSocketResult;
 import com.tapdata.tm.ws.endpoint.WebSocketManager;
 import lombok.SneakyThrows;
 import org.apache.commons.collections.map.HashedMap;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.*;
 
 import static com.tapdata.tm.utils.MongoUtils.toObjectId;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -37,6 +35,8 @@ public class TestConnectionHandlerTest {
     private DataSourceService dataSourceService;
     private MessageQueueService messageQueueService;
     private DataSourceDefinitionService dataSourceDefinitionService;
+    private AgentGroupService agentGroupService;
+    private WorkerService workerService;
     @BeforeEach
     void buildTestConnectionHandler(){
         testConnectionHandler = mock(TestConnectionHandler.class);
@@ -52,6 +52,10 @@ void buildTestConnectionHandler(){
         ReflectionTestUtils.setField(testConnectionHandler,"messageQueueService",messageQueueService);
         dataSourceDefinitionService = mock(DataSourceDefinitionService.class);
         ReflectionTestUtils.setField(testConnectionHandler,"dataSourceDefinitionService",dataSourceDefinitionService);
+        agentGroupService = mock(AgentGroupService.class);
+        ReflectionTestUtils.setField(testConnectionHandler,"agentGroupService",agentGroupService);
+        workerService = mock(WorkerService.class);
+        ReflectionTestUtils.setField(testConnectionHandler,"workerService",workerService);
     }
     @Nested
     class HandleMessageTest{
@@ -181,4 +185,49 @@ void test5(){
             assertEquals(dataSourceConfig.get("password"),config1.get("password"));
         }
     }
+    @Nested
+    class CheckPriorityEngineTest{
+        @Test
+        void test_main(){
+            List<Worker> workers = new ArrayList<>();
+            Worker worker1 = new Worker();
+            worker1.setProcessId("work1");
+            Worker worker2 = new Worker();
+            worker2.setProcessId("work2");
+            workers.add(worker1);
+            workers.add(worker2);
+            doCallRealMethod().when(testConnectionHandler).checkPriorityEngine(workers,"work2");
+            List<Worker> result = testConnectionHandler.checkPriorityEngine(workers,"work2");
+            Assertions.assertTrue(result.contains(worker2));
+        }
+        @Test
+        void test_priorityProcessIsNull(){
+            List<Worker> workers = new ArrayList<>();
+            Worker worker1 = new Worker();
+            worker1.setProcessId("work1");
+            Worker worker2 = new Worker();
+            worker2.setProcessId("work2");
+            workers.add(worker1);
+            workers.add(worker2);
+            doCallRealMethod().when(testConnectionHandler).checkPriorityEngine(workers,null);
+            List<Worker> result = testConnectionHandler.checkPriorityEngine(workers,null);
+            Assertions.assertTrue(result.contains(worker1));
+            Assertions.assertTrue(result.contains(worker2));
+        }
+        @Test
+        void test_priorityProcessIsNotExist(){
+            List<Worker> workers = new ArrayList<>();
+            Worker worker1 = new Worker();
+            worker1.setProcessId("work1");
+            Worker worker2 = new Worker();
+            worker2.setProcessId("work2");
+            workers.add(worker1);
+            workers.add(worker2);
+            doCallRealMethod().when(testConnectionHandler).checkPriorityEngine(workers,"work3");
+            List<Worker> result = testConnectionHandler.checkPriorityEngine(workers,"work3");
+            Assertions.assertTrue(result.contains(worker1));
+            Assertions.assertTrue(result.contains(worker2));
+        }
+
+    }
 }