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 91f4277740 feat: implement REFRESH event logic for node data
synchronization and add unit tests (#6958)
91f4277740 is described below
commit 91f4277740fed6e9fe87eb34e17139c3cc766729
Author: Limbo <[email protected]>
AuthorDate: Sun Aug 23 16:37:24 2026 +0800
feat: implement REFRESH event logic for node data synchronization and add
unit tests (#6958)
Co-authored-by: aias00 <[email protected]>
---
.../listener/AbstractNodeDataChangedListener.java | 14 +++
.../AbstractNodeDataChangedListenerTest.java | 128 +++++++++++++++++++++
2 files changed, 142 insertions(+)
diff --git
a/shenyu-admin-listener/shenyu-admin-listener-api/src/main/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListener.java
b/shenyu-admin-listener/shenyu-admin-listener-api/src/main/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListener.java
index 99462c6516..21e6b6d594 100644
---
a/shenyu-admin-listener/shenyu-admin-listener-api/src/main/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListener.java
+++
b/shenyu-admin-listener/shenyu-admin-listener-api/src/main/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListener.java
@@ -226,6 +226,9 @@ public abstract class AbstractNodeDataChangedListener
implements DataChangedList
this.delChangedMapToList(nameToIdMap, configKeyPrefix);
break;
case REFRESH:
+ changedList.forEach(changedData ->
publishConfig(configKeyPrefix + mappingKey.apply(changedData) +
DefaultNodeConstants.JOIN_POINT + mappingValue.apply(changedData),
changedData));
+ this.refreshChangedMapToList(nameToIdMap, configKeyPrefix);
+ break;
case MYSELF:
default:
changedList.forEach(changedData ->
publishConfig(configKeyPrefix + mappingKey.apply(changedData) +
DefaultNodeConstants.JOIN_POINT + mappingValue.apply(changedData),
changedData));
@@ -239,6 +242,17 @@ public abstract class AbstractNodeDataChangedListener
implements DataChangedList
}
}
+ private void refreshChangedMapToList(final Map<String, List<String>>
stringListMap, final String configKeyPrefix) {
+ stringListMap.forEach((key, listIds) -> {
+ final String listKey = configKeyPrefix + key +
DefaultNodeConstants.POINT_LIST;
+ final String oldNodeListStr =
Optional.ofNullable(getConfig(listKey)).orElse(DefaultNodeConstants.EMPTY_ARRAY_STR);
+ final List<String> oldNodeList =
GsonUtils.getInstance().fromList(oldNodeListStr, String.class);
+ oldNodeList.removeAll(listIds);
+ oldNodeList.forEach(id -> delConfig(configKeyPrefix + key +
DefaultNodeConstants.JOIN_POINT + id));
+ publishConfig(listKey, listIds);
+ });
+ }
+
private void putChangedMapToList(final Map<String, List<String>>
stringListMap, final String configKeyPrefix) {
stringListMap.forEach((key, listIds) -> {
final String oldNodeListStr =
Optional.ofNullable(getConfig(configKeyPrefix + key +
DefaultNodeConstants.POINT_LIST))
diff --git
a/shenyu-admin-listener/shenyu-admin-listener-api/src/test/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListenerTest.java
b/shenyu-admin-listener/shenyu-admin-listener-api/src/test/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListenerTest.java
new file mode 100644
index 0000000000..f552617407
--- /dev/null
+++
b/shenyu-admin-listener/shenyu-admin-listener-api/src/test/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListenerTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.listener;
+
+import org.apache.shenyu.common.constant.DefaultNodeConstants;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.DataEventTypeEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public final class AbstractNodeDataChangedListenerTest {
+
+ private static final String NAMESPACE_ID = "namespace";
+
+ private static final String FIRST_PLUGIN = "firstPlugin";
+
+ private static final String SECOND_PLUGIN = "secondPlugin";
+
+ private static final String RETAINED_ID = "retained";
+
+ private static final String ADDED_ID = "added";
+
+ private static final String STALE_ID = "stale";
+
+ @Test
+ public void testOnSelectorChangedRefreshReplacesPluginSelectorIds() {
+ TestNodeDataChangedListener listener = new
TestNodeDataChangedListener();
+ listener.putConfig(selectorListKey(FIRST_PLUGIN),
Arrays.asList(RETAINED_ID, STALE_ID));
+ listener.putConfig(selectorListKey(SECOND_PLUGIN),
Arrays.asList(RETAINED_ID, STALE_ID));
+ listener.putConfig(selectorDataKey(FIRST_PLUGIN, STALE_ID), new
SelectorData());
+ listener.putConfig(selectorDataKey(SECOND_PLUGIN, STALE_ID), new
SelectorData());
+
+ listener.onSelectorChanged(Arrays.asList(
+ selectorData(FIRST_PLUGIN, RETAINED_ID),
+ selectorData(SECOND_PLUGIN, RETAINED_ID),
+ selectorData(FIRST_PLUGIN, ADDED_ID)),
DataEventTypeEnum.REFRESH);
+
+ assertEquals(Arrays.asList(RETAINED_ID, ADDED_ID),
listener.config(selectorListKey(FIRST_PLUGIN)));
+ assertEquals(Arrays.asList(RETAINED_ID),
listener.config(selectorListKey(SECOND_PLUGIN)));
+ assertNull(listener.config(selectorDataKey(FIRST_PLUGIN, STALE_ID)));
+ assertNull(listener.config(selectorDataKey(SECOND_PLUGIN, STALE_ID)));
+ assertTrue(listener.wasDeleted(selectorDataKey(FIRST_PLUGIN,
STALE_ID)));
+ assertTrue(listener.wasDeleted(selectorDataKey(SECOND_PLUGIN,
STALE_ID)));
+ }
+
+ private static SelectorData selectorData(final String pluginName, final
String selectorId) {
+ return SelectorData.builder()
+ .namespaceId(NAMESPACE_ID)
+ .pluginName(pluginName)
+ .id(selectorId)
+ .build();
+ }
+
+ private static String selectorListKey(final String pluginName) {
+ return NAMESPACE_ID + ".selector." + pluginName +
DefaultNodeConstants.POINT_LIST;
+ }
+
+ private static String selectorDataKey(final String pluginName, final
String selectorId) {
+ return NAMESPACE_ID + ".selector." + pluginName +
DefaultNodeConstants.JOIN_POINT + selectorId;
+ }
+
+ private static final class TestNodeDataChangedListener extends
AbstractNodeDataChangedListener {
+
+ private final Map<String, Object> configs = new HashMap<>();
+
+ private final Set<String> deletedDataIds = new HashSet<>();
+
+ private TestNodeDataChangedListener() {
+ super(new ChangeData("plugin", "selector", "rule", "auth", "meta",
"proxy.selector", "discovery"));
+ }
+
+ @Override
+ public void doPublishConfig(final String dataId, final Object data) {
+ configs.put(dataId, data);
+ }
+
+ @Override
+ protected void doDelConfig(final String dataId) {
+ configs.remove(dataId);
+ deletedDataIds.add(dataId);
+ }
+
+ @Override
+ public String getConfig(final String dataId) {
+ return Optional.ofNullable(configs.get(dataId))
+ .map(GsonUtils.getInstance()::toJson)
+ .orElse(null);
+ }
+
+ private void putConfig(final String dataId, final Object data) {
+ configs.put(dataId, data);
+ }
+
+ private Object config(final String dataId) {
+ return configs.get(dataId);
+ }
+
+ private boolean wasDeleted(final String dataId) {
+ return deletedDataIds.contains(dataId);
+ }
+ }
+}