This is an automated email from the ASF dual-hosted git repository.

Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new 6981c44718 [ISSUE #7468] Detect duplicate configs within an import 
archive. (#7556)
6981c44718 is described below

commit 6981c44718166fc5b74f28a0ebe2478f69f19410
Author: redyyang <[email protected]>
AuthorDate: Mon Oct 12 13:14:11 2026 +0800

    [ISSUE #7468] Detect duplicate configs within an import archive. (#7556)
    
    * fix(admin): detect duplicate configs within an import archive.
    
    * test(admin): retain dict import rollback and deduplication coverage.
    
    ---------
    
    Co-authored-by: redyyang <[email protected]>
---
 .../admin/service/impl/AppAuthServiceImpl.java     |   1 +
 .../admin/service/impl/DiscoveryServiceImpl.java   |  49 ++-
 .../service/impl/DiscoveryUpstreamServiceImpl.java |  21 +-
 .../admin/service/impl/MetaDataServiceImpl.java    |  19 +-
 .../service/impl/ProxySelectorServiceImpl.java     |  11 +-
 .../shenyu/admin/service/impl/RuleServiceImpl.java |  12 +-
 .../admin/service/impl/SelectorServiceImpl.java    |  20 +-
 .../admin/service/impl/ShenyuDictServiceImpl.java  |  19 +-
 .../admin/service/ConfigsImportDuplicateTest.java  | 451 +++++++++++++++++++++
 .../admin/service/DictImportIntegrationTest.java   |  33 +-
 .../admin/service/ShenyuDictServiceTest.java       |   1 +
 11 files changed, 568 insertions(+), 69 deletions(-)

diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/AppAuthServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/AppAuthServiceImpl.java
index a33a6d359f..21ea38efb1 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/AppAuthServiceImpl.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/AppAuthServiceImpl.java
@@ -380,6 +380,7 @@ public class AppAuthServiceImpl implements AppAuthService {
             appAuthDO.setNamespaceId(namespace);
             int inserted = appAuthMapper.insertSelective(appAuthDO);
             if (inserted > 0) {
+                existAppKeySet.add(appKey);
                 successCount++;
                 // auth path
                 List<AuthPathDTO> authPathDTOList = appAuth.getAuthPathList();
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryServiceImpl.java
index 3d1a2e9b62..27f5ce4954 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryServiceImpl.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryServiceImpl.java
@@ -62,6 +62,7 @@ import 
org.springframework.transaction.support.TransactionSynchronizationManager
 import org.springframework.transaction.support.TransactionTemplate;
 
 import java.sql.Timestamp;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -487,43 +488,42 @@ public class DiscoveryServiceImpl implements 
DiscoveryService {
             return ConfigImportResult.success();
         }
         Map<String, String> discoveryHandlerIdMapping = 
context.getDiscoveryHandlerIdMapping();
-        Map<String, List<DiscoveryDO>> pluginDiscoveryMap = discoveryMapper
+        Map<String, Set<String>> pluginDiscoveryMap = discoveryMapper
                 .selectAllByNamespaceId(namespace)
                 .stream()
-                .collect(Collectors.groupingBy(DiscoveryDO::getPluginName));
+                .collect(Collectors.groupingBy(DiscoveryDO::getPluginName,
+                        Collectors.mapping(DiscoveryDO::getDiscoveryName, 
Collectors.toSet())));
+        Map<String, Map<String, String>> importedHandlerIdMap = 
Maps.newHashMap();
         int successCount = 0;
         StringBuilder errorMsgBuilder = new StringBuilder();
         for (DiscoveryDTO discoveryDTO : discoveryList) {
             String pluginName = discoveryDTO.getPluginName();
             String discoveryName = discoveryDTO.getName();
-            Set<String> existDiscoveryNameSet = pluginDiscoveryMap
-                    .getOrDefault(pluginName, Lists.newArrayList())
-                    .stream()
-                    .map(DiscoveryDO::getDiscoveryName)
-                    .collect(Collectors.toSet());
+            Set<String> existDiscoveryNameSet = 
pluginDiscoveryMap.computeIfAbsent(pluginName, key -> new HashSet<>());
+            Map<String, String> importedHandlerIds = 
importedHandlerIdMap.computeIfAbsent(pluginName, key -> Maps.newHashMap());
             if (existDiscoveryNameSet.contains(discoveryName)) {
                 errorMsgBuilder
                         .append(discoveryName)
                         .append(",");
+                String importedHandlerId = 
importedHandlerIds.get(discoveryName);
+                if (Objects.nonNull(importedHandlerId) && 
Objects.nonNull(discoveryDTO.getDiscoveryHandler())) {
+                    
discoveryHandlerIdMapping.put(discoveryDTO.getDiscoveryHandler().getId(), 
importedHandlerId);
+                }
                 continue;
             }
             String discoveryId = UUIDUtils.getInstance().generateShortUuid();
             discoveryDTO.setId(discoveryId);
             discoveryDTO.setNamespaceId(namespace);
-            create(discoveryDTO);
+            if (Objects.isNull(create(discoveryDTO))) {
+                continue;
+            }
+            existDiscoveryNameSet.add(discoveryName);
             successCount++;
             
             // import discovery handler data
-            String discoveryHandlerId = null;
-            if (Objects.nonNull(discoveryDTO.getDiscoveryHandler())) {
-                DiscoveryHandlerDO discoveryHandlerDO = DiscoveryTransfer
-                        .INSTANCE
-                        .mapToDO(discoveryDTO.getDiscoveryHandler());
-                discoveryHandlerDO.setDiscoveryId(discoveryId);
-                discoveryHandlerId = 
UUIDUtils.getInstance().generateShortUuid();
-                discoveryHandlerIdMapping.put(discoveryHandlerDO.getId(), 
discoveryHandlerId);
-                discoveryHandlerDO.setId(discoveryHandlerId);
-                discoveryHandlerMapper.insertSelective(discoveryHandlerDO);
+            String discoveryHandlerId = importDiscoveryHandler(discoveryDTO, 
context);
+            if (Objects.nonNull(discoveryHandlerId)) {
+                importedHandlerIds.put(discoveryName, discoveryHandlerId);
             }
             
             // import discovery rel data
@@ -561,4 +561,17 @@ public class DiscoveryServiceImpl implements 
DiscoveryService {
         }
         return ConfigImportResult.success(successCount);
     }
+
+    private String importDiscoveryHandler(final DiscoveryDTO discoveryDTO, 
final ConfigsImportContext context) {
+        if (Objects.isNull(discoveryDTO.getDiscoveryHandler())) {
+            return null;
+        }
+        DiscoveryHandlerDO discoveryHandlerDO = 
DiscoveryTransfer.INSTANCE.mapToDO(discoveryDTO.getDiscoveryHandler());
+        discoveryHandlerDO.setDiscoveryId(discoveryDTO.getId());
+        String discoveryHandlerId = 
UUIDUtils.getInstance().generateShortUuid();
+        context.getDiscoveryHandlerIdMapping().put(discoveryHandlerDO.getId(), 
discoveryHandlerId);
+        discoveryHandlerDO.setId(discoveryHandlerId);
+        discoveryHandlerMapper.insertSelective(discoveryHandlerDO);
+        return discoveryHandlerId;
+    }
 }
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java
index 943ba7386d..e33a523ce9 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java
@@ -55,6 +55,7 @@ import 
org.springframework.transaction.support.TransactionSynchronizationManager
 import org.springframework.util.StringUtils;
 
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -358,18 +359,16 @@ public class DiscoveryUpstreamServiceImpl implements 
DiscoveryUpstreamService {
         Map<String, String> discoveryHandlerIdMapping = 
context.getDiscoveryHandlerIdMapping();
         int successCount = 0;
         StringBuilder errorMsgBuilder = new StringBuilder();
-        Map<String, List<DiscoveryUpstreamDO>> discoveryHandlerUpstreamMap = 
discoveryUpstreamMapper
+        Map<String, Set<String>> discoveryHandlerUpstreamMap = 
discoveryUpstreamMapper
                 .selectByNamespaceId(namespace)
                 .stream()
-                
.collect(Collectors.groupingBy(DiscoveryUpstreamDO::getDiscoveryHandlerId));
+                
.collect(Collectors.groupingBy(DiscoveryUpstreamDO::getDiscoveryHandlerId,
+                        
Collectors.mapping(DiscoveryUpstreamDO::getUpstreamUrl, Collectors.toSet())));
         for (DiscoveryUpstreamDTO discoveryUpstreamDTO : 
discoveryUpstreamList) {
             String discoveryHandlerId = 
discoveryUpstreamDTO.getDiscoveryHandlerId();
             String url = discoveryUpstreamDTO.getUrl();
-            Set<String> existsUpstreamUrlSet = discoveryHandlerUpstreamMap
-                    
.getOrDefault(discoveryHandlerIdMapping.getOrDefault(discoveryHandlerId, 
discoveryHandlerId), Lists.newArrayList())
-                    .stream()
-                    .map(DiscoveryUpstreamDO::getUpstreamUrl)
-                    .collect(Collectors.toSet());
+            String newDiscoveryHandlerId = 
discoveryHandlerIdMapping.getOrDefault(discoveryHandlerId, discoveryHandlerId);
+            Set<String> existsUpstreamUrlSet = 
discoveryHandlerUpstreamMap.computeIfAbsent(newDiscoveryHandlerId, key -> new 
HashSet<>());
             if (existsUpstreamUrlSet.contains(url)) {
                 errorMsgBuilder
                         .append(url)
@@ -378,10 +377,12 @@ public class DiscoveryUpstreamServiceImpl implements 
DiscoveryUpstreamService {
             }
             discoveryUpstreamDTO.setNamespaceId(namespace);
             discoveryUpstreamDTO.setId(null);
-            
discoveryUpstreamDTO.setDiscoveryHandlerId(discoveryHandlerIdMapping.getOrDefault(discoveryUpstreamDTO.getDiscoveryHandlerId(),
 discoveryUpstreamDTO.getDiscoveryHandlerId()));
+            discoveryUpstreamDTO.setDiscoveryHandlerId(newDiscoveryHandlerId);
             DiscoveryUpstreamDO discoveryUpstreamDO = 
DiscoveryUpstreamDO.buildDiscoveryUpstreamDO(discoveryUpstreamDTO);
-            discoveryUpstreamMapper.insert(discoveryUpstreamDO);
-            successCount++;
+            if (discoveryUpstreamMapper.insert(discoveryUpstreamDO) > 0) {
+                existsUpstreamUrlSet.add(url);
+                successCount++;
+            }
         }
         if (StringUtils.hasLength(errorMsgBuilder)) {
             errorMsgBuilder.setLength(errorMsgBuilder.length() - 1);
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/MetaDataServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/MetaDataServiceImpl.java
index 4105874139..e118bc1c4d 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/MetaDataServiceImpl.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/MetaDataServiceImpl.java
@@ -104,7 +104,11 @@ public class MetaDataServiceImpl implements 
MetaDataService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public String createOrUpdate(final MetaDataDTO metaDataDTO) {
-        return StringUtils.isBlank(metaDataDTO.getId()) ? 
this.create(metaDataDTO) : this.update(metaDataDTO);
+        if (StringUtils.isBlank(metaDataDTO.getId())) {
+            create(metaDataDTO);
+            return ShenyuResultMessage.CREATE_SUCCESS;
+        }
+        return update(metaDataDTO);
     }
 
     @Override
@@ -276,8 +280,10 @@ public class MetaDataServiceImpl implements 
MetaDataService {
                 continue;
             }
             metaDataDTO.setNamespaceId(namespace);
-            create(metaDataDTO);
-            successCount++;
+            if (create(metaDataDTO) > 0) {
+                existMetadataPathSet.add(metaDataPath);
+                successCount++;
+            }
         }
         if (StringUtils.isNotEmpty(errorMsgBuilder)) {
             errorMsgBuilder.setLength(errorMsgBuilder.length() - 1);
@@ -286,7 +292,7 @@ public class MetaDataServiceImpl implements MetaDataService 
{
         return ConfigImportResult.success(successCount);
     }
     
-    private String create(final MetaDataDTO metaDataDTO) {
+    private int create(final MetaDataDTO metaDataDTO) {
         Assert.isNull(metaDataMapper.pathExisted(metaDataDTO.getPath(), 
metaDataDTO.getNamespaceId()), AdminConstants.DATA_PATH_IS_EXIST);
         MetaDataDO metaDataDO = 
MetaDataTransfer.INSTANCE.mapToEntity(metaDataDTO);
         metaDataDO.setId(UUIDUtils.getInstance().generateShortUuid());
@@ -294,14 +300,15 @@ public class MetaDataServiceImpl implements 
MetaDataService {
         Timestamp currentTime = new Timestamp(System.currentTimeMillis());
         metaDataDO.setDateCreated(currentTime);
         metaDataDO.setDateUpdated(currentTime);
-        if (metaDataMapper.insert(metaDataDO) > 0) {
+        final int inserted = metaDataMapper.insert(metaDataDO);
+        if (inserted > 0) {
             publisher.onCreated(metaDataDO);
         }
 
         // publish MetaData's create event
         eventPublisher.publishEvent(new 
DataChangedEvent(ConfigGroupEnum.META_DATA, DataEventTypeEnum.CREATE,
                 
Collections.singletonList(MetaDataTransfer.INSTANCE.mapToData(metaDataDO))));
-        return ShenyuResultMessage.CREATE_SUCCESS;
+        return inserted;
     }
 
     private String update(final MetaDataDTO metaDataDTO) {
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ProxySelectorServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ProxySelectorServiceImpl.java
index 9e47973beb..9a8e88ebc4 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ProxySelectorServiceImpl.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ProxySelectorServiceImpl.java
@@ -581,19 +581,17 @@ public class ProxySelectorServiceImpl implements 
ProxySelectorService {
             return ConfigImportResult.success();
         }
         Map<String, String> proxySelectorIdMapping = new HashMap<>();
-        Map<String, List<ProxySelectorDO>> pluginProxySelectorMap = 
proxySelectorMapper
+        Map<String, Map<String, String>> pluginProxySelectorMap = 
proxySelectorMapper
                 .selectByNamespaceId(namespace)
                 .stream()
-                
.collect(Collectors.groupingBy(ProxySelectorDO::getPluginName));
+                .collect(Collectors.groupingBy(ProxySelectorDO::getPluginName,
+                        Collectors.toMap(ProxySelectorDO::getName, 
ProxySelectorDO::getId, (first, second) -> first)));
         int successCount = 0;
         StringBuilder errorMsgBuilder = new StringBuilder();
         for (ProxySelectorData selectorData : proxySelectorList) {
             String pluginName = selectorData.getPluginName();
             String proxySelectorName = selectorData.getName();
-            Map<String, String> existProxySelectorNameSet = 
pluginProxySelectorMap
-                    .getOrDefault(pluginName, Lists.newArrayList())
-                    .stream()
-                    .collect(Collectors.toMap(ProxySelectorDO::getName, 
ProxySelectorDO::getId));
+            Map<String, String> existProxySelectorNameSet = 
pluginProxySelectorMap.computeIfAbsent(pluginName, key -> new HashMap<>());
             
             if (existProxySelectorNameSet.containsKey(proxySelectorName)) {
                 errorMsgBuilder
@@ -608,6 +606,7 @@ public class ProxySelectorServiceImpl implements 
ProxySelectorService {
             proxySelectorDO.setId(newProxySelectorId);
             proxySelectorDO.setNamespaceId(namespace);
             if (proxySelectorMapper.insert(proxySelectorDO) > 0) {
+                existProxySelectorNameSet.put(proxySelectorName, 
newProxySelectorId);
                 proxySelectorIdMapping.put(oldProxySelectorId, 
newProxySelectorId);
                 successCount++;
             }
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/RuleServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/RuleServiceImpl.java
index 5e835604bd..40e8125b92 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/RuleServiceImpl.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/RuleServiceImpl.java
@@ -347,10 +347,11 @@ public class RuleServiceImpl implements RuleService {
             return ConfigImportResult.success();
         }
         Map<String, String> selectorIdMapping = context.getSelectorIdMapping();
-        Map<String, List<RuleDO>> selectorRuleMap = ruleMapper
+        Map<String, Set<String>> selectorRuleMap = ruleMapper
                 .selectAllByNamespaceId(namespace)
                 .stream()
-                .collect(Collectors.groupingBy(RuleDO::getSelectorId));
+                .collect(Collectors.groupingBy(RuleDO::getSelectorId,
+                        Collectors.mapping(RuleDO::getRuleName, 
Collectors.toSet())));
 
         int successCount = 0;
         StringBuilder errorMsgBuilder = new StringBuilder();
@@ -365,11 +366,7 @@ public class RuleServiceImpl implements RuleService {
                         .append(",");
                 continue;
             }
-            Set<String> existRuleNameSet = selectorRuleMap
-                    .getOrDefault(newSelectorId, Lists.newArrayList())
-                    .stream()
-                    .map(RuleDO::getRuleName)
-                    .collect(Collectors.toSet());
+            Set<String> existRuleNameSet = 
selectorRuleMap.computeIfAbsent(newSelectorId, key -> new HashSet<>());
 
             if (existRuleNameSet.contains(ruleName)) {
                 errorMsgBuilder
@@ -391,6 +388,7 @@ public class RuleServiceImpl implements RuleService {
                     });
             addCondition(ruleDO, ruleDTO.getRuleConditions());
             if (ruleCount > 0) {
+                existRuleNameSet.add(ruleName);
                 successCount++;
             }
         }
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java
index 466cd4314f..5c08df0284 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java
@@ -567,9 +567,10 @@ public class SelectorServiceImpl implements 
SelectorService {
         Map<String, String> selectorIdMapping = context.getSelectorIdMapping();
         StringBuilder errorMsgBuilder = new StringBuilder();
         int successCount = 0;
-        Map<String, List<SelectorDO>> pluginSelectorMap = 
selectorMapper.selectAllByNamespaceId(namespace).stream()
+        Map<String, Map<String, String>> pluginSelectorMap = 
selectorMapper.selectAllByNamespaceId(namespace).stream()
                 .filter(Objects::nonNull)
-                .collect(Collectors.groupingBy(SelectorDO::getPluginId));
+                .collect(Collectors.groupingBy(SelectorDO::getPluginId,
+                        Collectors.toMap(SelectorDO::getSelectorName, 
SelectorDO::getId, (first, second) -> first)));
 
         Map<String, List<SelectorDTO>> importSelectorMap = 
selectorList.stream()
                 .collect(Collectors.groupingBy(SelectorDTO::getPluginId));
@@ -580,11 +581,7 @@ public class SelectorServiceImpl implements 
SelectorService {
             List<SelectorDTO> selectorDTOList = selectorEntry.getValue();
             if (CollectionUtils.isNotEmpty(selectorDTOList)) {
 
-                Map<String, String> existSelectorSet = Optional
-                        .ofNullable(pluginSelectorMap.get(pluginId))
-                        .orElseGet(Lists::newArrayList)
-                        .stream()
-                        .collect(Collectors.toMap(SelectorDO::getSelectorName, 
SelectorDO::getId));
+                Map<String, String> existSelectorSet = 
pluginSelectorMap.computeIfAbsent(pluginId, key -> new HashMap<>());
 
                 for (SelectorDTO selectorDTO : selectorDTOList) {
                     // filter by selectorName
@@ -598,7 +595,7 @@ public class SelectorServiceImpl implements SelectorService 
{
                     } else {
                         // gen new id
                         String selectorId = 
UUIDUtils.getInstance().generateShortUuid();
-                        selectorIdMapping.put(selectorDTO.getId(), selectorId);
+                        final String oldSelectorId = selectorDTO.getId();
                         selectorDTO.setId(selectorId);
                         selectorDTO.setNamespaceId(namespace);
                         selectorDTO.setPluginId(pluginId);
@@ -607,8 +604,11 @@ public class SelectorServiceImpl implements 
SelectorService {
                                             c.setSelectorId(selectorId);
                                             c.setId(null);
                                         });
-                        create(selectorDTO);
-                        successCount++;
+                        if (create(selectorDTO) > 0) {
+                            existSelectorSet.put(selectorName, selectorId);
+                            selectorIdMapping.put(oldSelectorId, selectorId);
+                            successCount++;
+                        }
                     }
                 }
             }
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ShenyuDictServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ShenyuDictServiceImpl.java
index d856deb9a6..8ff727aa0e 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ShenyuDictServiceImpl.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ShenyuDictServiceImpl.java
@@ -17,7 +17,6 @@
 
 package org.apache.shenyu.admin.service.impl;
 
-import com.google.common.collect.Lists;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.shenyu.admin.aspect.annotation.Pageable;
@@ -35,6 +34,7 @@ import org.apache.shenyu.admin.utils.Assert;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -112,19 +112,16 @@ public class ShenyuDictServiceImpl implements 
ShenyuDictService {
         if (CollectionUtils.isEmpty(dictList)) {
             return ConfigImportResult.success();
         }
-        Map<String, List<ShenyuDictVO>> dictTypeMap = listAllData()
+        Map<String, Set<String>> dictTypeMap = listAllData()
                 .stream()
-                .collect(Collectors.groupingBy(ShenyuDictVO::getType));
+                .collect(Collectors.groupingBy(ShenyuDictVO::getType,
+                        Collectors.mapping(ShenyuDictVO::getDictName, 
Collectors.toSet())));
         int successCount = 0;
         StringBuilder errorMsgBuilder = new StringBuilder();
         for (ShenyuDictDTO dictDTO : dictList) {
             String type = dictDTO.getType();
             String dictName = dictDTO.getDictName();
-            Set<String> existDictNameSet = dictTypeMap
-                    .getOrDefault(type, Lists.newArrayList())
-                    .stream()
-                    .map(ShenyuDictVO::getDictName)
-                    .collect(Collectors.toSet());
+            Set<String> existDictNameSet = dictTypeMap.computeIfAbsent(type, 
key -> new HashSet<>());
             // check if dictName exists for this type
             if (existDictNameSet.contains(dictName)) {
                 errorMsgBuilder
@@ -132,8 +129,10 @@ public class ShenyuDictServiceImpl implements 
ShenyuDictService {
                         .append(",");
                 continue;
             }
-            create(dictDTO);
-            successCount++;
+            if (create(dictDTO) > 0) {
+                existDictNameSet.add(dictName);
+                successCount++;
+            }
         }
         if (StringUtils.isNotEmpty(errorMsgBuilder)) {
             errorMsgBuilder.setLength(errorMsgBuilder.length() - 1);
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ConfigsImportDuplicateTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ConfigsImportDuplicateTest.java
new file mode 100644
index 0000000000..7442bffcc9
--- /dev/null
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ConfigsImportDuplicateTest.java
@@ -0,0 +1,451 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.service;
+
+import org.apache.shenyu.admin.discovery.DiscoveryProcessor;
+import org.apache.shenyu.admin.discovery.DiscoveryProcessorHolder;
+import org.apache.shenyu.admin.mapper.AppAuthMapper;
+import org.apache.shenyu.admin.mapper.AuthParamMapper;
+import org.apache.shenyu.admin.mapper.AuthPathMapper;
+import org.apache.shenyu.admin.mapper.DiscoveryHandlerMapper;
+import org.apache.shenyu.admin.mapper.DiscoveryMapper;
+import org.apache.shenyu.admin.mapper.DiscoveryRelMapper;
+import org.apache.shenyu.admin.mapper.DiscoveryUpstreamMapper;
+import org.apache.shenyu.admin.mapper.MetaDataMapper;
+import org.apache.shenyu.admin.mapper.PluginMapper;
+import org.apache.shenyu.admin.mapper.ProxySelectorMapper;
+import org.apache.shenyu.admin.mapper.RuleConditionMapper;
+import org.apache.shenyu.admin.mapper.RuleMapper;
+import org.apache.shenyu.admin.mapper.SelectorConditionMapper;
+import org.apache.shenyu.admin.mapper.SelectorMapper;
+import org.apache.shenyu.admin.mapper.ShenyuDictMapper;
+import org.apache.shenyu.admin.model.dto.AppAuthDTO;
+import org.apache.shenyu.admin.model.dto.AuthPathDTO;
+import org.apache.shenyu.admin.model.dto.DiscoveryDTO;
+import org.apache.shenyu.admin.model.dto.DiscoveryHandlerDTO;
+import org.apache.shenyu.admin.model.dto.DiscoveryUpstreamDTO;
+import org.apache.shenyu.admin.model.dto.MetaDataDTO;
+import org.apache.shenyu.admin.model.dto.RuleDTO;
+import org.apache.shenyu.admin.model.dto.SelectorDTO;
+import org.apache.shenyu.admin.model.dto.ShenyuDictDTO;
+import org.apache.shenyu.admin.model.entity.AppAuthDO;
+import org.apache.shenyu.admin.model.entity.DiscoveryDO;
+import org.apache.shenyu.admin.model.entity.DiscoveryUpstreamDO;
+import org.apache.shenyu.admin.model.entity.MetaDataDO;
+import org.apache.shenyu.admin.model.entity.PluginDO;
+import org.apache.shenyu.admin.model.entity.ProxySelectorDO;
+import org.apache.shenyu.admin.model.entity.RuleDO;
+import org.apache.shenyu.admin.model.entity.SelectorDO;
+import org.apache.shenyu.admin.model.entity.ShenyuDictDO;
+import org.apache.shenyu.admin.model.result.ConfigImportResult;
+import org.apache.shenyu.admin.service.configs.ConfigsImportContext;
+import org.apache.shenyu.admin.service.impl.AppAuthServiceImpl;
+import org.apache.shenyu.admin.service.impl.DiscoveryServiceImpl;
+import org.apache.shenyu.admin.service.impl.DiscoveryUpstreamServiceImpl;
+import org.apache.shenyu.admin.service.impl.MetaDataServiceImpl;
+import org.apache.shenyu.admin.service.impl.ProxySelectorServiceImpl;
+import org.apache.shenyu.admin.service.impl.RuleServiceImpl;
+import org.apache.shenyu.admin.service.impl.SelectorServiceImpl;
+import org.apache.shenyu.admin.service.impl.ShenyuDictServiceImpl;
+import org.apache.shenyu.admin.service.publish.DictEventPublisher;
+import org.apache.shenyu.admin.service.publish.MetaDataEventPublisher;
+import org.apache.shenyu.admin.service.publish.RuleEventPublisher;
+import org.apache.shenyu.admin.service.publish.SelectorEventPublisher;
+import org.apache.shenyu.common.dto.ProxySelectorData;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.transaction.PlatformTransactionManager;
+
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Regression tests for duplicate business keys in namespace config imports.
+ */
+@ExtendWith(MockitoExtension.class)
+public final class ConfigsImportDuplicateTest {
+
+    private static final String NAMESPACE = "target-namespace";
+
+    @Mock
+    private SelectorMapper selectorMapper;
+
+    @Mock
+    private RuleMapper ruleMapper;
+
+    @Mock
+    private PluginMapper pluginMapper;
+
+    @Mock
+    private MetaDataMapper metaDataMapper;
+
+    @Mock
+    private AppAuthMapper appAuthMapper;
+
+    @Mock
+    private ShenyuDictMapper dictMapper;
+
+    @Mock
+    private ProxySelectorMapper proxySelectorMapper;
+
+    @Mock
+    private DiscoveryMapper discoveryMapper;
+
+    @Mock
+    private DiscoveryUpstreamMapper upstreamMapper;
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testSelectorsDeduplicateAfterPluginRemapping(final boolean 
existing) {
+        final SelectorServiceImpl service = new 
SelectorServiceImpl(selectorMapper, mock(SelectorConditionMapper.class), 
pluginMapper,
+                mock(ApplicationEventPublisher.class), discoveryMapper, 
mock(DiscoveryHandlerMapper.class), mock(DiscoveryRelMapper.class),
+                upstreamMapper, mock(DiscoveryProcessorHolder.class), 
mock(SelectorEventPublisher.class));
+        final ConfigsImportContext context = new ConfigsImportContext();
+        context.getPluginTemplateIdMapping().put("source", "target");
+        context.getPluginTemplateIdMapping().put("alias", "target");
+        context.getPluginTemplateIdMapping().put("other", "other-target");
+        
when(selectorMapper.selectAllByNamespaceId(NAMESPACE)).thenReturn(existing
+                ? 
Arrays.asList(SelectorDO.builder().id("existing").pluginId("target").selectorName("same").build(),
+                        
SelectorDO.builder().id("legacy-duplicate").pluginId("target").selectorName("same").build())
 : Collections.emptyList());
+        when(selectorMapper.insertSelective(any())).thenReturn(1);
+        
when(pluginMapper.selectById(anyString())).thenReturn(PluginDO.builder().name("divide").build());
+
+        final ConfigImportResult result = service.importData(NAMESPACE, 
Arrays.asList(selector("first", "source", "same"),
+                selector("duplicate", "alias", "same"), 
selector("other-plugin", "other", "same"), selector("other-name", "source", 
"different")), context);
+
+        assertResult(result, existing ? 2 : 3, "import fail selector: ", 
existing ? "same,same" : "same");
+        assertNotNull(context.getSelectorIdMapping().get("first"));
+        assertEquals(context.getSelectorIdMapping().get("first"), 
context.getSelectorIdMapping().get("duplicate"));
+        assertNotEquals(context.getSelectorIdMapping().get("first"), 
context.getSelectorIdMapping().get("other-plugin"));
+        final ArgumentCaptor<SelectorDO> captor = 
ArgumentCaptor.forClass(SelectorDO.class);
+        verify(selectorMapper, 
times(result.getSuccessCount())).insertSelective(captor.capture());
+        captor.getAllValues().forEach(row -> assertEquals(NAMESPACE, 
row.getNamespaceId()));
+        if (existing) {
+            assertEquals("existing", 
context.getSelectorIdMapping().get("first"));
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testRulesDeduplicateAfterSelectorRemapping(final boolean 
existing) {
+        final RuleServiceImpl service = new RuleServiceImpl(ruleMapper, 
mock(RuleConditionMapper.class), selectorMapper, pluginMapper, 
mock(RuleEventPublisher.class));
+        final ConfigsImportContext context = new ConfigsImportContext();
+        context.getSelectorIdMapping().put("source", "target");
+        context.getSelectorIdMapping().put("alias", "target");
+        context.getSelectorIdMapping().put("other", "other-target");
+        when(ruleMapper.selectAllByNamespaceId(NAMESPACE)).thenReturn(existing
+                ? 
Collections.singletonList(RuleDO.builder().selectorId("target").ruleName("same").build())
 : Collections.emptyList());
+        when(ruleMapper.insertSelective(any())).thenReturn(1);
+        
when(selectorMapper.selectById(anyString())).thenReturn(SelectorDO.builder().pluginId("plugin").build());
+        
when(pluginMapper.selectById("plugin")).thenReturn(PluginDO.builder().name("divide").build());
+
+        final ConfigImportResult result = service.importData(NAMESPACE, 
Arrays.asList(rule("source", "same"), rule("alias", "same"),
+                rule("other", "same"), rule("source", "different")), context);
+
+        assertResult(result, existing ? 2 : 3, "import fail rule: ", existing 
? "same,same" : "same");
+        final ArgumentCaptor<RuleDO> captor = 
ArgumentCaptor.forClass(RuleDO.class);
+        verify(ruleMapper, 
times(result.getSuccessCount())).insertSelective(captor.capture());
+        captor.getAllValues().forEach(row -> assertEquals(NAMESPACE, 
row.getNamespaceId()));
+        assertEquals(existing ? 0 : 1, captor.getAllValues().stream()
+                .filter(row -> "target".equals(row.getSelectorId()) && 
"same".equals(row.getRuleName())).count());
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testMetadataDeduplicatesPaths(final boolean existing) {
+        final MetaDataServiceImpl service = new 
MetaDataServiceImpl(metaDataMapper, mock(ApplicationEventPublisher.class), 
mock(MetaDataEventPublisher.class));
+        
when(metaDataMapper.findAllByNamespaceId(NAMESPACE)).thenReturn(existing
+                ? 
Collections.singletonList(MetaDataDO.builder().path("/same").build()) : 
Collections.emptyList());
+        when(metaDataMapper.insert(any())).thenReturn(1);
+        when(metaDataMapper.pathExisted(anyString(), 
anyString())).thenReturn(null);
+
+        final ConfigImportResult result = service.importData(NAMESPACE, 
Arrays.asList(metadata("/same"), metadata("/same"), metadata("/different")));
+
+        assertResult(result, existing ? 1 : 2, "import fail meta: ", existing 
? "/same,/same" : "/same");
+        final ArgumentCaptor<MetaDataDO> captor = 
ArgumentCaptor.forClass(MetaDataDO.class);
+        verify(metaDataMapper, 
times(result.getSuccessCount())).insert(captor.capture());
+        captor.getAllValues().forEach(row -> assertEquals(NAMESPACE, 
row.getNamespaceId()));
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testAuthDeduplicatesAppKeysAndChildRows(final boolean 
existing) {
+        final AuthPathMapper pathMapper = mock(AuthPathMapper.class);
+        final AppAuthServiceImpl service = new 
AppAuthServiceImpl(appAuthMapper, mock(ApplicationEventPublisher.class), 
mock(AuthParamMapper.class), pathMapper);
+        
when(appAuthMapper.selectAllByNamespaceId(NAMESPACE)).thenReturn(existing
+                ? 
Collections.singletonList(AppAuthDO.builder().appKey("same").build()) : 
Collections.emptyList());
+        when(appAuthMapper.insertSelective(any())).thenReturn(1);
+
+        final ConfigImportResult result = service.importData(NAMESPACE, 
Arrays.asList(auth("same"), auth("same"), auth("different")));
+
+        assertResult(result, existing ? 1 : 2, "import fail appKey: ", 
existing ? "same,same," : "same,");
+        final ArgumentCaptor<AppAuthDO> captor = 
ArgumentCaptor.forClass(AppAuthDO.class);
+        verify(appAuthMapper, 
times(result.getSuccessCount())).insertSelective(captor.capture());
+        verify(pathMapper, times(result.getSuccessCount())).batchSave(any());
+        captor.getAllValues().forEach(row -> assertEquals(NAMESPACE, 
row.getNamespaceId()));
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testDictsDeduplicateWithinType(final boolean existing) {
+        final ShenyuDictServiceImpl service = new 
ShenyuDictServiceImpl(dictMapper, mock(DictEventPublisher.class));
+        final Timestamp now = new Timestamp(System.currentTimeMillis());
+        when(dictMapper.selectByQuery(any())).thenReturn(existing ? 
Collections.singletonList(ShenyuDictDO.builder().id("existing")
+                
.type("type").dictCode("code").dictName("same").dateCreated(now).dateUpdated(now).build())
 : Collections.emptyList());
+        when(dictMapper.insertSelective(any())).thenReturn(1);
+
+        final ConfigImportResult result = 
service.importData(Arrays.asList(dict("type", "same"), dict("type", "same"),
+                dict("other-type", "same"), dict("type", "different")));
+
+        assertResult(result, existing ? 2 : 3, "import fail dict: ", existing 
? "same,same" : "same");
+        verify(dictMapper, 
times(result.getSuccessCount())).insertSelective(any());
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testProxySelectorsDeduplicateAndMapIds(final boolean existing) 
{
+        final ProxySelectorServiceImpl service = new 
ProxySelectorServiceImpl(proxySelectorMapper, discoveryMapper, upstreamMapper,
+                mock(DiscoveryHandlerMapper.class), 
mock(DiscoveryRelMapper.class), selectorMapper, 
mock(DiscoveryProcessorHolder.class), mock(ApplicationEventPublisher.class));
+        final ConfigsImportContext context = new ConfigsImportContext();
+        
when(proxySelectorMapper.selectByNamespaceId(NAMESPACE)).thenReturn(existing
+                ? 
Arrays.asList(ProxySelectorDO.builder().id("existing").pluginName("tcp").name("same").build(),
+                        
ProxySelectorDO.builder().id("legacy-duplicate").pluginName("tcp").name("same").build())
 : Collections.emptyList());
+        when(proxySelectorMapper.insert(any())).thenReturn(1);
+
+        final ConfigImportResult result = service.importData(NAMESPACE, 
Arrays.asList(proxy("first", "tcp", "same"), proxy("duplicate", "tcp", "same"),
+                proxy("other-plugin", "mqtt", "same"), proxy("other-name", 
"tcp", "different")), context);
+
+        assertResult(result, existing ? 2 : 3, "import fail proxy selector: ", 
existing ? "same,same" : "same");
+        assertNotNull(context.getProxySelectorIdMapping().get("first"));
+        assertEquals(context.getProxySelectorIdMapping().get("first"), 
context.getProxySelectorIdMapping().get("duplicate"));
+        assertNotEquals(context.getProxySelectorIdMapping().get("first"), 
context.getProxySelectorIdMapping().get("other-plugin"));
+        verify(proxySelectorMapper, 
times(result.getSuccessCount())).insert(any());
+        if (existing) {
+            assertEquals("existing", 
context.getProxySelectorIdMapping().get("first"));
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testDiscoveriesDeduplicateBeforeCreatingHandlers(final boolean 
existing) {
+        final DiscoveryProcessorHolder holder = 
mock(DiscoveryProcessorHolder.class);
+        final DiscoveryProcessor processor = mock(DiscoveryProcessor.class);
+        final DiscoveryHandlerMapper handlerMapper = 
mock(DiscoveryHandlerMapper.class);
+        final DiscoveryServiceImpl service = new 
DiscoveryServiceImpl(discoveryMapper, proxySelectorMapper, 
mock(DiscoveryRelMapper.class),
+                handlerMapper, mock(SelectorService.class), selectorMapper, 
holder, mock(PlatformTransactionManager.class));
+        when(holder.chooseProcessor("local")).thenReturn(processor);
+        
when(discoveryMapper.selectAllByNamespaceId(NAMESPACE)).thenReturn(existing
+                ? 
Collections.singletonList(DiscoveryDO.builder().pluginName("divide").discoveryName("same").build())
 : Collections.emptyList());
+        when(discoveryMapper.insert(any())).thenReturn(1);
+
+        final ConfigImportResult result = service.importData(NAMESPACE, 
Arrays.asList(discovery("divide", "same"), discovery("divide", "same"),
+                discovery("tcp", "same"), discovery("divide", "different")), 
new ConfigsImportContext());
+
+        assertResult(result, existing ? 2 : 3, "import fail discovery: ", 
existing ? "same,same" : "same");
+        verify(discoveryMapper, times(result.getSuccessCount())).insert(any());
+        verify(handlerMapper, 
times(result.getSuccessCount())).insertSelective(any());
+        verify(processor, 
times(result.getSuccessCount())).createDiscovery(any());
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testUpstreamsDeduplicateAfterHandlerRemapping(final boolean 
existing) {
+        final DiscoveryUpstreamServiceImpl service = new 
DiscoveryUpstreamServiceImpl(upstreamMapper, 
mock(DiscoveryHandlerMapper.class), proxySelectorMapper,
+                discoveryMapper, mock(DiscoveryRelMapper.class), 
selectorMapper, pluginMapper, mock(DiscoveryProcessorHolder.class));
+        final ConfigsImportContext context = new ConfigsImportContext();
+        context.getDiscoveryHandlerIdMapping().put("source", "target");
+        context.getDiscoveryHandlerIdMapping().put("alias", "target");
+        context.getDiscoveryHandlerIdMapping().put("other", "other-target");
+        when(upstreamMapper.selectByNamespaceId(NAMESPACE)).thenReturn(existing
+                ? 
Collections.singletonList(DiscoveryUpstreamDO.buildDiscoveryUpstreamDO(upstream("target",
 "same"))) : Collections.emptyList());
+        when(upstreamMapper.insert(any())).thenReturn(1);
+
+        final ConfigImportResult result = service.importData(NAMESPACE, 
Arrays.asList(upstream("source", "same"), upstream("alias", "same"),
+                upstream("other", "same"), upstream("source", "different")), 
context);
+
+        assertResult(result, existing ? 2 : 3, "import fail discovery 
upstream: ", existing ? "same,same" : "same");
+        final ArgumentCaptor<DiscoveryUpstreamDO> captor = 
ArgumentCaptor.forClass(DiscoveryUpstreamDO.class);
+        verify(upstreamMapper, 
times(result.getSuccessCount())).insert(captor.capture());
+        captor.getAllValues().forEach(row -> assertEquals(NAMESPACE, 
row.getNamespaceId()));
+        assertEquals(existing ? 0 : 1, captor.getAllValues().stream()
+                .filter(row -> "target".equals(row.getDiscoveryHandlerId()) && 
"same".equals(row.getUpstreamUrl())).count());
+    }
+
+    private void assertResult(final ConfigImportResult result, final int 
count, final String prefix, final String names) {
+        assertEquals(count, result.getSuccessCount());
+        assertEquals(prefix + names, result.getFailMessage());
+    }
+
+    @Test
+    public void testMetadataOnlyReservesPathsAfterSuccessfulInsert() {
+        final MetaDataServiceImpl service = new 
MetaDataServiceImpl(metaDataMapper, mock(ApplicationEventPublisher.class), 
mock(MetaDataEventPublisher.class));
+        when(metaDataMapper.insert(any())).thenReturn(0, 1);
+        when(metaDataMapper.pathExisted(anyString(), 
anyString())).thenReturn(null);
+
+        final ConfigImportResult result = service.importData(NAMESPACE, 
Arrays.asList(metadata("/same"), metadata("/same"), metadata("/same")));
+
+        assertResult(result, 1, "import fail meta: ", "/same");
+        verify(metaDataMapper, times(2)).insert(any());
+    }
+
+    @Test
+    public void 
testDuplicateDiscoveryHandlersRemapUpstreamsToFirstInsertedHandler() {
+        final DiscoveryProcessorHolder holder = 
mock(DiscoveryProcessorHolder.class);
+        final DiscoveryProcessor processor = mock(DiscoveryProcessor.class);
+        final DiscoveryHandlerMapper handlerMapper = 
mock(DiscoveryHandlerMapper.class);
+        final DiscoveryRelMapper relMapper = mock(DiscoveryRelMapper.class);
+        final DiscoveryServiceImpl discoveries = new 
DiscoveryServiceImpl(discoveryMapper, proxySelectorMapper, relMapper,
+                handlerMapper, mock(SelectorService.class), selectorMapper, 
holder, mock(PlatformTransactionManager.class));
+        final DiscoveryUpstreamServiceImpl upstreams = new 
DiscoveryUpstreamServiceImpl(upstreamMapper, handlerMapper, proxySelectorMapper,
+                discoveryMapper, relMapper, selectorMapper, pluginMapper, 
holder);
+        final ConfigsImportContext context = new ConfigsImportContext();
+        final DiscoveryDTO first = discovery("divide", "same");
+        first.getDiscoveryHandler().setId("first-handler");
+        final DiscoveryDTO duplicate = discovery("divide", "same");
+        duplicate.getDiscoveryHandler().setId("duplicate-handler");
+        when(holder.chooseProcessor("local")).thenReturn(processor);
+        when(discoveryMapper.insert(any())).thenReturn(1);
+        when(handlerMapper.insertSelective(any())).thenReturn(1);
+        when(upstreamMapper.insert(any())).thenReturn(1);
+
+        assertResult(discoveries.importData(NAMESPACE, Arrays.asList(first, 
duplicate), context), 1, "import fail discovery: ", "same");
+        final String targetHandler = 
context.getDiscoveryHandlerIdMapping().get("first-handler");
+        assertNotNull(targetHandler);
+        assertEquals(targetHandler, 
context.getDiscoveryHandlerIdMapping().get("duplicate-handler"));
+        assertResult(upstreams.importData(NAMESPACE, 
Arrays.asList(upstream("first-handler", "same-url"),
+                upstream("duplicate-handler", "same-url")), context), 1, 
"import fail discovery upstream: ", "same-url");
+        verify(handlerMapper).insertSelective(any());
+        final ArgumentCaptor<DiscoveryUpstreamDO> captor = 
ArgumentCaptor.forClass(DiscoveryUpstreamDO.class);
+        verify(upstreamMapper).insert(captor.capture());
+        assertEquals(targetHandler, captor.getValue().getDiscoveryHandlerId());
+    }
+
+    @Test
+    public void testDictOnlyReservesNamesAfterSuccessfulInsert() {
+        final ShenyuDictServiceImpl service = new 
ShenyuDictServiceImpl(dictMapper, mock(DictEventPublisher.class));
+        when(dictMapper.insertSelective(any())).thenReturn(0, 1);
+
+        final ConfigImportResult result = 
service.importData(Arrays.asList(dict("type", "same"), dict("type", "same"), 
dict("type", "same")));
+
+        assertResult(result, 1, "import fail dict: ", "same");
+        verify(dictMapper, times(2)).insertSelective(any());
+    }
+
+    @Test
+    public void testProxySelectorOnlyMapsIdsAfterSuccessfulInsert() {
+        final ProxySelectorServiceImpl service = new 
ProxySelectorServiceImpl(proxySelectorMapper, discoveryMapper, upstreamMapper,
+                mock(DiscoveryHandlerMapper.class), 
mock(DiscoveryRelMapper.class), selectorMapper, 
mock(DiscoveryProcessorHolder.class), mock(ApplicationEventPublisher.class));
+        final ConfigsImportContext context = new ConfigsImportContext();
+        when(proxySelectorMapper.insert(any())).thenReturn(0, 1);
+
+        final ConfigImportResult result = service.importData(NAMESPACE, 
Arrays.asList(proxy("failed", "tcp", "same"),
+                proxy("success", "tcp", "same"), proxy("duplicate", "tcp", 
"same")), context);
+
+        assertResult(result, 1, "import fail proxy selector: ", "same");
+        assertEquals(2, context.getProxySelectorIdMapping().size());
+        assertNotNull(context.getProxySelectorIdMapping().get("success"));
+        assertEquals(context.getProxySelectorIdMapping().get("success"), 
context.getProxySelectorIdMapping().get("duplicate"));
+        verify(proxySelectorMapper, times(2)).insert(any());
+    }
+
+    private SelectorDTO selector(final String id, final String pluginId, final 
String name) {
+        final SelectorDTO dto = new SelectorDTO();
+        dto.setId(id);
+        dto.setPluginId(pluginId);
+        dto.setName(name);
+        dto.setType(0);
+        dto.setSelectorConditions(Collections.emptyList());
+        return dto;
+    }
+
+    private RuleDTO rule(final String selectorId, final String name) {
+        final RuleDTO dto = new RuleDTO();
+        dto.setSelectorId(selectorId);
+        dto.setName(name);
+        dto.setRuleConditions(Collections.emptyList());
+        return dto;
+    }
+
+    private MetaDataDTO metadata(final String path) {
+        final MetaDataDTO dto = new MetaDataDTO();
+        dto.setPath(path);
+        return dto;
+    }
+
+    private AppAuthDTO auth(final String key) {
+        final AppAuthDTO dto = new AppAuthDTO();
+        dto.setAppKey(key);
+        final AuthPathDTO path = new AuthPathDTO();
+        path.setPath("/" + key);
+        dto.setAuthPathList(Collections.singletonList(path));
+        return dto;
+    }
+
+    private ShenyuDictDTO dict(final String type, final String name) {
+        final ShenyuDictDTO dto = new ShenyuDictDTO();
+        dto.setType(type);
+        dto.setDictCode("code");
+        dto.setDictName(name);
+        return dto;
+    }
+
+    private ProxySelectorData proxy(final String id, final String plugin, 
final String name) {
+        final ProxySelectorData data = new ProxySelectorData();
+        data.setId(id);
+        data.setPluginName(plugin);
+        data.setName(name);
+        return data;
+    }
+
+    private DiscoveryDTO discovery(final String plugin, final String name) {
+        final DiscoveryDTO dto = new DiscoveryDTO();
+        dto.setPluginName(plugin);
+        dto.setName(name);
+        dto.setType("local");
+        final DiscoveryHandlerDTO handler = new DiscoveryHandlerDTO();
+        handler.setId(plugin + "-" + name);
+        dto.setDiscoveryHandler(handler);
+        return dto;
+    }
+
+    private DiscoveryUpstreamDTO upstream(final String handler, final String 
url) {
+        final DiscoveryUpstreamDTO dto = new DiscoveryUpstreamDTO();
+        dto.setDiscoveryHandlerId(handler);
+        dto.setUrl(url);
+        dto.setStatus(0);
+        dto.setWeight(50);
+        return dto;
+    }
+}
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DictImportIntegrationTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DictImportIntegrationTest.java
index 9fb64ce1ff..408f973e47 100644
--- 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DictImportIntegrationTest.java
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DictImportIntegrationTest.java
@@ -21,6 +21,7 @@ package org.apache.shenyu.admin.service;
 import jakarta.annotation.Resource;
 import org.apache.shenyu.admin.AbstractSpringIntegrationTest;
 import org.apache.shenyu.admin.model.dto.ShenyuDictDTO;
+import org.apache.shenyu.admin.model.result.ConfigImportResult;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.dao.DataIntegrityViolationException;
@@ -32,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 /**
- * Verify dict imports roll back dictionary rows and synchronous audit events.
+ * Verify dict imports deduplicate business keys and retain transactional 
audit events.
  */
 public class DictImportIntegrationTest extends AbstractSpringIntegrationTest {
 
@@ -50,8 +51,12 @@ public class DictImportIntegrationTest extends 
AbstractSpringIntegrationTest {
 
     @Test
     public void testFailedImportRollsBackRowsAndAuditLog() {
+        final ShenyuDictDTO first = dict("dict-import-first");
+        first.setId("dict-import-collision");
+        final ShenyuDictDTO second = dict("dict-import-second");
+        second.setId(first.getId());
         assertThrows(DataIntegrityViolationException.class,
-                () -> 
shenyuDictService.importData(List.of(dict("dict-import-first"), 
dict("dict-import-first"))));
+                () -> shenyuDictService.importData(List.of(first, second)));
         assertEquals(0, dictCount());
         assertEquals(0, auditCount());
     }
@@ -63,6 +68,30 @@ public class DictImportIntegrationTest extends 
AbstractSpringIntegrationTest {
         assertEquals(2, auditCount());
     }
 
+    @Test
+    public void 
testDuplicateBusinessKeysReportFailureWithoutDuplicateRowsOrAuditLogs() {
+        final ConfigImportResult result = 
shenyuDictService.importData(List.of(dict("dict-import-first"),
+                dict("dict-import-first"), dict("dict-import-second")));
+
+        assertEquals(2, result.getSuccessCount());
+        assertEquals("import fail dict: dict-import-first", 
result.getFailMessage());
+        assertEquals(2, dictCount());
+        assertEquals(2, auditCount());
+    }
+
+    @Test
+    public void testExistingAndBatchDuplicatesShareTheSameDetection() {
+        assertEquals(1, 
shenyuDictService.importData(List.of(dict("dict-import-first"))).getSuccessCount());
+
+        final ConfigImportResult result = 
shenyuDictService.importData(List.of(dict("dict-import-first"),
+                dict("dict-import-second"), dict("dict-import-second")));
+
+        assertEquals(1, result.getSuccessCount());
+        assertEquals("import fail dict: dict-import-first,dict-import-second", 
result.getFailMessage());
+        assertEquals(2, dictCount());
+        assertEquals(2, auditCount());
+    }
+
     private int dictCount() {
         return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM shenyu_dict 
WHERE type = 'dict-import-test'", Integer.class);
     }
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ShenyuDictServiceTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ShenyuDictServiceTest.java
index c99b04f112..705e3e70d4 100644
--- 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ShenyuDictServiceTest.java
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ShenyuDictServiceTest.java
@@ -164,6 +164,7 @@ public final class ShenyuDictServiceTest {
         List<ShenyuDictDO> shenyuDictDOList = 
Collections.singletonList(buildShenyuDictDO("haha"));
         
given(this.shenyuDictMapper.selectByQuery(any())).willReturn(shenyuDictDOList);
         List<ShenyuDictDTO> shenyuDictDTOList = 
Collections.singletonList(buildShenyuDictDTO(null, "lala"));
+        given(this.shenyuDictMapper.insertSelective(any())).willReturn(1);
         ConfigImportResult configImportResult = 
shenyuDictService.importData(shenyuDictDTOList);
 
         assertNotNull(configImportResult);

Reply via email to