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;
+ }
}