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 8853ff360f fix(admin): match imported plugin handles by field and 
type. (#7551)
8853ff360f is described below

commit 8853ff360f8b33de636d3e730f6378789016dad3
Author: redyyang <[email protected]>
AuthorDate: Tue Oct 13 10:17:26 2026 +0800

    fix(admin): match imported plugin handles by field and type. (#7551)
    
    Co-authored-by: redyyang <[email protected]>
---
 .../service/impl/PluginHandleServiceImpl.java      |  37 +++---
 .../service/impl/PluginHandleServiceImplTest.java  | 148 +++++++++++++++++++++
 2 files changed, 163 insertions(+), 22 deletions(-)

diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/PluginHandleServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/PluginHandleServiceImpl.java
index 5d8eb9b736..9d64cd7b57 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/PluginHandleServiceImpl.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/PluginHandleServiceImpl.java
@@ -20,6 +20,7 @@ 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.commons.lang3.tuple.Pair;
 import org.apache.shenyu.admin.aspect.annotation.Pageable;
 import org.apache.shenyu.admin.mapper.PluginHandleMapper;
 import org.apache.shenyu.admin.mapper.ShenyuDictMapper;
@@ -46,6 +47,7 @@ import 
org.springframework.transaction.annotation.Transactional;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -151,10 +153,11 @@ public class PluginHandleServiceImpl implements 
PluginHandleService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public ConfigImportResult importData(final List<PluginHandleDTO> 
pluginHandleList, final ConfigsImportContext context) {
-        Map<String, List<PluginHandleVO>> existHandleMap = listAllData()
+        Map<String, Set<Pair<String, Integer>>> existHandleMap = listAllData()
                 .stream()
                 .filter(Objects::nonNull)
-                .collect(Collectors.groupingBy(PluginHandleVO::getPluginId));
+                .collect(Collectors.groupingBy(PluginHandleVO::getPluginId,
+                        Collectors.mapping(handle -> 
Pair.of(handle.getField(), handle.getType()), Collectors.toSet())));
         Map<String, List<PluginHandleDTO>> importHandleMap = pluginHandleList
                 .stream()
                 .filter(Objects::nonNull)
@@ -168,26 +171,16 @@ public class PluginHandleServiceImpl implements 
PluginHandleService {
                 continue;
             }
             List<PluginHandleDTO> handles = pluginHandleEntry.getValue();
-            if (CollectionUtils.isNotEmpty(handles)) {
-                if (existHandleMap.containsKey(pluginId)) {
-                    // check and just add difference handle
-                    List<PluginHandleVO> existHandles = 
existHandleMap.getOrDefault(pluginId, Lists.newArrayList());
-                    Set<String> handleFiledMap = existHandles.stream()
-                            .map(PluginHandleVO::getField)
-                            .collect(Collectors.toSet());
-                    for (PluginHandleDTO handle : handles) {
-                        if (!handleFiledMap.contains(handle.getField())) {
-                            handle.setPluginId(pluginId);
-                            create(handle);
-                            successCount++;
-                        }
-                    }
-                } else {
-                    for (PluginHandleDTO handle : handles) {
-                        handle.setPluginId(pluginId);
-                        create(handle);
-                        successCount++;
-                    }
+            Set<Pair<String, Integer>> existHandleKeys = 
existHandleMap.computeIfAbsent(pluginId, key -> new HashSet<>());
+            for (PluginHandleDTO handle : handles) {
+                Pair<String, Integer> handleKey = Pair.of(handle.getField(), 
handle.getType());
+                if (existHandleKeys.contains(handleKey)) {
+                    continue;
+                }
+                handle.setPluginId(pluginId);
+                if (create(handle) > 0) {
+                    existHandleKeys.add(handleKey);
+                    successCount++;
                 }
             }
         }
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/impl/PluginHandleServiceImplTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/impl/PluginHandleServiceImplTest.java
index 4bf29cebac..bdfd714d9e 100644
--- 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/impl/PluginHandleServiceImplTest.java
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/impl/PluginHandleServiceImplTest.java
@@ -19,13 +19,29 @@ package org.apache.shenyu.admin.service.impl;
 
 import org.apache.shenyu.admin.mapper.PluginHandleMapper;
 import org.apache.shenyu.admin.mapper.ShenyuDictMapper;
+import org.apache.shenyu.admin.model.dto.PluginHandleDTO;
+import org.apache.shenyu.admin.model.entity.PluginHandleDO;
+import org.apache.shenyu.admin.model.result.ConfigImportResult;
+import org.apache.shenyu.admin.service.configs.ConfigsImportContext;
 import org.apache.shenyu.admin.service.publish.PluginHandleEventPublisher;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 /**
@@ -34,6 +50,9 @@ import static org.mockito.Mockito.when;
 @ExtendWith(MockitoExtension.class)
 public final class PluginHandleServiceImplTest {
 
+    @InjectMocks
+    private PluginHandleServiceImpl pluginHandleService;
+
     @Mock
     private PluginHandleMapper pluginHandleMapper;
 
@@ -49,4 +68,133 @@ public final class PluginHandleServiceImplTest {
         when(pluginHandleMapper.selectById("missing")).thenReturn(null);
         assertNull(service.findById("missing"));
     }
+
+    @Test
+    public void importShouldInsertSameFieldWithDifferentType() {
+        
when(pluginHandleMapper.selectByQuery(any())).thenReturn(List.of(existingHandle("target-plugin",
 "sharedField", 1)));
+        when(pluginHandleMapper.insertSelective(any())).thenReturn(1);
+        PluginHandleDTO handle = importHandle("source-plugin", "sharedField", 
2);
+
+        ConfigImportResult result = 
pluginHandleService.importData(List.of(handle), importContext());
+
+        assertEquals(1, result.getSuccessCount());
+        ArgumentCaptor<PluginHandleDO> captor = 
ArgumentCaptor.forClass(PluginHandleDO.class);
+        verify(pluginHandleMapper).insertSelective(captor.capture());
+        PluginHandleDO inserted = captor.getValue();
+        assertEquals("target-plugin", inserted.getPluginId());
+        assertEquals("sharedField", inserted.getField());
+        assertEquals(2, inserted.getType());
+        verify(eventPublisher).onCreated(inserted);
+    }
+
+    @Test
+    public void importShouldSkipExistingFieldAndType() {
+        
when(pluginHandleMapper.selectByQuery(any())).thenReturn(List.of(existingHandle("target-plugin",
 "sharedField", 1)));
+
+        ConfigImportResult result = 
pluginHandleService.importData(List.of(importHandle("source-plugin", 
"sharedField", 1)), importContext());
+
+        assertEquals(0, result.getSuccessCount());
+        verify(pluginHandleMapper, never()).insertSelective(any());
+        verifyNoInteractions(eventPublisher);
+    }
+
+    @Test
+    public void importShouldTrackInsertedFieldAndType() {
+        
when(pluginHandleMapper.selectByQuery(any())).thenReturn(List.of(existingHandle("target-plugin",
 "sharedField", 1)));
+        when(pluginHandleMapper.insertSelective(any())).thenReturn(1);
+        List<PluginHandleDTO> handles = List.of(importHandle("source-plugin", 
"sharedField", 1),
+                importHandle("source-plugin", "sharedField", 2), 
importHandle("source-plugin", "sharedField", 2),
+                importHandle("source-plugin", "otherField", 2));
+
+        ConfigImportResult result = pluginHandleService.importData(handles, 
importContext());
+
+        assertEquals(2, result.getSuccessCount());
+        ArgumentCaptor<PluginHandleDO> captor = 
ArgumentCaptor.forClass(PluginHandleDO.class);
+        verify(pluginHandleMapper, times(2)).insertSelective(captor.capture());
+        assertEquals(List.of("sharedField", "otherField"), 
captor.getAllValues().stream().map(PluginHandleDO::getField).toList());
+        assertEquals(List.of(2, 2), 
captor.getAllValues().stream().map(PluginHandleDO::getType).toList());
+        assertEquals(List.of("target-plugin", "target-plugin"), 
captor.getAllValues().stream().map(PluginHandleDO::getPluginId).toList());
+        verify(eventPublisher, times(2)).onCreated(any(PluginHandleDO.class));
+    }
+
+    @Test
+    public void importShouldDeduplicateHandlesForNewPlugin() {
+        
when(pluginHandleMapper.selectByQuery(any())).thenReturn(Collections.emptyList());
+        when(pluginHandleMapper.insertSelective(any())).thenReturn(1);
+        List<PluginHandleDTO> handles = List.of(importHandle("source-plugin", 
"sharedField", 1),
+                importHandle("source-plugin", "sharedField", 1), 
importHandle("source-plugin", "sharedField", 2));
+
+        ConfigImportResult result = pluginHandleService.importData(handles, 
importContext());
+
+        assertEquals(2, result.getSuccessCount());
+        ArgumentCaptor<PluginHandleDO> captor = 
ArgumentCaptor.forClass(PluginHandleDO.class);
+        verify(pluginHandleMapper, times(2)).insertSelective(captor.capture());
+        assertEquals(List.of(1, 2), 
captor.getAllValues().stream().map(PluginHandleDO::getType).toList());
+        verify(eventPublisher, times(2)).onCreated(any(PluginHandleDO.class));
+    }
+
+    @Test
+    public void importShouldDeduplicateAfterPluginIdRemapping() {
+        
when(pluginHandleMapper.selectByQuery(any())).thenReturn(Collections.emptyList());
+        when(pluginHandleMapper.insertSelective(any())).thenReturn(1);
+        ConfigsImportContext context = importContext();
+        context.getPluginTemplateIdMapping().put("other-source-plugin", 
"target-plugin");
+        List<PluginHandleDTO> handles = List.of(importHandle("source-plugin", 
"sharedField", 1),
+                importHandle("other-source-plugin", "sharedField", 1));
+
+        ConfigImportResult result = pluginHandleService.importData(handles, 
context);
+
+        assertEquals(1, result.getSuccessCount());
+        ArgumentCaptor<PluginHandleDO> captor = 
ArgumentCaptor.forClass(PluginHandleDO.class);
+        verify(pluginHandleMapper).insertSelective(captor.capture());
+        assertEquals("target-plugin", captor.getValue().getPluginId());
+        verify(eventPublisher).onCreated(captor.getValue());
+    }
+
+    @Test
+    public void importShouldSkipUnmappedPlugin() {
+        
when(pluginHandleMapper.selectByQuery(any())).thenReturn(Collections.emptyList());
+
+        ConfigImportResult result = 
pluginHandleService.importData(List.of(importHandle("missing-plugin", 
"sharedField", 1)), importContext());
+
+        assertEquals(0, result.getSuccessCount());
+        verify(pluginHandleMapper, never()).insertSelective(any());
+        verifyNoInteractions(eventPublisher);
+    }
+
+    @Test
+    public void importShouldTrackOnlySuccessfulInsertions() {
+        
when(pluginHandleMapper.selectByQuery(any())).thenReturn(Collections.emptyList());
+        when(pluginHandleMapper.insertSelective(any())).thenReturn(0, 1);
+        List<PluginHandleDTO> handles = List.of(importHandle("source-plugin", 
"sharedField", 1),
+                importHandle("source-plugin", "sharedField", 1));
+
+        ConfigImportResult result = pluginHandleService.importData(handles, 
importContext());
+
+        assertEquals(1, result.getSuccessCount());
+        verify(pluginHandleMapper, times(2)).insertSelective(any());
+        verify(eventPublisher).onCreated(any(PluginHandleDO.class));
+    }
+
+    private ConfigsImportContext importContext() {
+        ConfigsImportContext context = new ConfigsImportContext();
+        context.getPluginTemplateIdMapping().put("source-plugin", 
"target-plugin");
+        return context;
+    }
+
+    private PluginHandleDO existingHandle(final String pluginId, final String 
field, final Integer type) {
+        Timestamp timestamp = Timestamp.valueOf("2026-10-07 00:00:00");
+        return 
PluginHandleDO.builder().pluginId(pluginId).field(field).dataType(2).type(type).sort(0)
+                .dateCreated(timestamp).dateUpdated(timestamp).build();
+    }
+
+    private PluginHandleDTO importHandle(final String pluginId, final String 
field, final Integer type) {
+        PluginHandleDTO handle = new PluginHandleDTO();
+        handle.setPluginId(pluginId);
+        handle.setField(field);
+        handle.setDataType(2);
+        handle.setType(type);
+        handle.setSort(0);
+        return handle;
+    }
 }

Reply via email to