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 3e06a6481b fix(sync): fully replace websocket snapshots (#7094)
3e06a6481b is described below

commit 3e06a6481b25d36dad1865549644ecbe5ede57dd
Author: Liming Deng <[email protected]>
AuthorDate: Thu Oct 1 06:30:09 2026 +0800

    fix(sync): fully replace websocket snapshots (#7094)
---
 .../shenyu/admin/listener/DataChangedListener.java | 44 ++++++++++++++++++
 .../shenyu/admin/listener/DataChangedEvent.java    | 25 +++++++++++
 .../admin/listener/DataChangedEventDispatcher.java | 18 +++++---
 .../websocket/WebsocketDataChangedListener.java    | 52 ++++++++++++++++++++++
 .../admin/service/impl/AppAuthServiceImpl.java     |  5 +++
 .../admin/service/impl/SyncDataServiceImpl.java    |  7 +--
 .../listener/DataChangedEventDispatcherTest.java   | 50 ++++++++++-----------
 .../WebsocketDataChangedListenerTest.java          | 31 +++++++++++++
 .../shenyu/admin/service/AppAuthServiceTest.java   | 16 +++++++
 .../shenyu/admin/service/SyncDataServiceTest.java  | 33 +++++++++++++-
 .../plugin/sign/cache/SignAuthDataCache.java       |  7 +++
 .../sign/subscriber/SignAuthDataSubscriber.java    |  5 +++
 .../subscriber/SignAuthDataSubscriberTest.java     | 11 +++++
 .../websocket/handler/AbstractDataHandler.java     | 16 +++----
 .../data/websocket/handler/PluginDataHandler.java  |  1 +
 .../data/websocket/handler/RuleDataHandler.java    |  1 +
 .../websocket/handler/SelectorDataHandler.java     |  1 +
 .../websocket/handler/PluginDataHandlerTest.java   | 19 ++++++--
 .../websocket/handler/RuleDataHandlerTest.java     | 11 +++--
 .../websocket/handler/SelectorDataHandlerTest.java | 11 +++--
 .../handler/WebsocketDataHandlerTest.java          |  2 +
 21 files changed, 310 insertions(+), 56 deletions(-)

diff --git 
a/shenyu-admin-listener/shenyu-admin-listener-api/src/main/java/org/apache/shenyu/admin/listener/DataChangedListener.java
 
b/shenyu-admin-listener/shenyu-admin-listener-api/src/main/java/org/apache/shenyu/admin/listener/DataChangedListener.java
index 01b149a11a..e31e83b642 100644
--- 
a/shenyu-admin-listener/shenyu-admin-listener-api/src/main/java/org/apache/shenyu/admin/listener/DataChangedListener.java
+++ 
b/shenyu-admin-listener/shenyu-admin-listener-api/src/main/java/org/apache/shenyu/admin/listener/DataChangedListener.java
@@ -44,6 +44,17 @@ public interface DataChangedListener {
     default void onAppAuthChanged(List<AppAuthData> changed, DataEventTypeEnum 
eventType) {
     }
 
+    /**
+     * Invoke this method when AppAuth was received for a known namespace.
+     *
+     * @param changed changed data
+     * @param eventType event type
+     * @param namespaceId namespace id
+     */
+    default void onAppAuthChanged(List<AppAuthData> changed, DataEventTypeEnum 
eventType, String namespaceId) {
+        onAppAuthChanged(changed, eventType);
+    }
+
     /**
      * invoke this method when Plugin was received.
      *
@@ -53,6 +64,17 @@ public interface DataChangedListener {
     default void onPluginChanged(List<PluginData> changed, DataEventTypeEnum 
eventType) {
     }
 
+    /**
+     * Invoke this method when Plugin was received for a known namespace.
+     *
+     * @param changed changed data
+     * @param eventType event type
+     * @param namespaceId namespace id
+     */
+    default void onPluginChanged(List<PluginData> changed, DataEventTypeEnum 
eventType, String namespaceId) {
+        onPluginChanged(changed, eventType);
+    }
+
     /**
      * invoke this method when Selector was received.
      *
@@ -62,6 +84,17 @@ public interface DataChangedListener {
     default void onSelectorChanged(List<SelectorData> changed, 
DataEventTypeEnum eventType) {
     }
 
+    /**
+     * Invoke this method when Selector was received for a known namespace.
+     *
+     * @param changed changed data
+     * @param eventType event type
+     * @param namespaceId namespace id
+     */
+    default void onSelectorChanged(List<SelectorData> changed, 
DataEventTypeEnum eventType, String namespaceId) {
+        onSelectorChanged(changed, eventType);
+    }
+
     /**
      * On meta data changed.
      *
@@ -81,6 +114,17 @@ public interface DataChangedListener {
     default void onRuleChanged(List<RuleData> changed, DataEventTypeEnum 
eventType) {
     }
 
+    /**
+     * Invoke this method when Rule was received for a known namespace.
+     *
+     * @param changed changed data
+     * @param eventType event type
+     * @param namespaceId namespace id
+     */
+    default void onRuleChanged(List<RuleData> changed, DataEventTypeEnum 
eventType, String namespaceId) {
+        onRuleChanged(changed, eventType);
+    }
+
     /**
      * invoke this method when ProxySelector was changed.
      *
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/DataChangedEvent.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/DataChangedEvent.java
index e89105528e..7a14a33faa 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/DataChangedEvent.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/DataChangedEvent.java
@@ -39,6 +39,8 @@ public class DataChangedEvent extends ApplicationEvent {
 
     private final ConfigGroupEnum groupKey;
 
+    private final String namespaceId;
+
     /**
      * Instantiates a new Data changed event.
      *
@@ -47,9 +49,23 @@ public class DataChangedEvent extends ApplicationEvent {
      * @param source      the source
      */
     public DataChangedEvent(final ConfigGroupEnum groupKey, final 
DataEventTypeEnum type, final List<?> source) {
+        this(groupKey, type, source, null);
+    }
+
+    /**
+     * Instantiates a new data changed event for a known namespace.
+     *
+     * @param groupKey group key
+     * @param type event type
+     * @param source source data
+     * @param namespaceId namespace id
+     */
+    public DataChangedEvent(final ConfigGroupEnum groupKey, final 
DataEventTypeEnum type,
+                            final List<?> source, final String namespaceId) {
         
super(source.stream().filter(Objects::nonNull).collect(Collectors.toList()));
         this.eventType = type;
         this.groupKey = groupKey;
+        this.namespaceId = namespaceId;
     }
 
     /**
@@ -75,4 +91,13 @@ public class DataChangedEvent extends ApplicationEvent {
         return this.groupKey;
     }
 
+    /**
+     * Gets namespace id.
+     *
+     * @return namespace id, or null when it was not supplied
+     */
+    public String getNamespaceId() {
+        return namespaceId;
+    }
+
 }
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcher.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcher.java
index a13594ea72..0962677b25 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcher.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcher.java
@@ -80,23 +80,29 @@ public class DataChangedEventDispatcher implements 
ApplicationListener<DataChang
                 LOG.info("received DataChangedEvent, not master, pass");
                 return;
             }
-            final int size = event.getSource() instanceof java.util.Collection 
? ((java.util.Collection<?>) event.getSource()).size() : 1;
-            LOG.info("received DataChangedEvent, group={}, size={}, type={}", 
event.getGroupKey(), size, event.getEventType());
+            final int size = event.getSource() instanceof java.util.Collection
+                    ? ((java.util.Collection<?>) event.getSource()).size() : 1;
+            LOG.info("received DataChangedEvent, group={}, size={}, type={}",
+                    event.getGroupKey(), size, event.getEventType());
             if (LOG.isDebugEnabled()) {
                 LOG.debug("received DataChangedEvent payload: {}", 
JsonUtils.toJson(event));
             }
             switch (event.getGroupKey()) {
                 case APP_AUTH:
-                    listener.onAppAuthChanged((List<AppAuthData>) 
event.getSource(), event.getEventType());
+                    listener.onAppAuthChanged((List<AppAuthData>) 
event.getSource(),
+                            event.getEventType(), event.getNamespaceId());
                     break;
                 case PLUGIN:
-                    listener.onPluginChanged((List<PluginData>) 
event.getSource(), event.getEventType());
+                    listener.onPluginChanged((List<PluginData>) 
event.getSource(),
+                            event.getEventType(), event.getNamespaceId());
                     break;
                 case RULE:
-                    listener.onRuleChanged((List<RuleData>) event.getSource(), 
event.getEventType());
+                    listener.onRuleChanged((List<RuleData>) event.getSource(),
+                            event.getEventType(), event.getNamespaceId());
                     break;
                 case SELECTOR:
-                    listener.onSelectorChanged((List<SelectorData>) 
event.getSource(), event.getEventType());
+                    listener.onSelectorChanged((List<SelectorData>) 
event.getSource(),
+                            event.getEventType(), event.getNamespaceId());
                     break;
                 case META_DATA:
                     listener.onMetaDataChanged((List<MetaData>) 
event.getSource(), event.getEventType());
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListener.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListener.java
index bb0f2601b0..f9f28e4acc 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListener.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListener.java
@@ -35,6 +35,7 @@ import org.apache.shenyu.common.enums.ConfigGroupEnum;
 import org.apache.shenyu.common.enums.DataEventTypeEnum;
 import org.apache.shenyu.common.utils.GsonUtils;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -65,6 +66,16 @@ public class WebsocketDataChangedListener implements 
DataChangedListener {
         }
     }
 
+    @Override
+    public void onPluginChanged(final List<PluginData> changed, final 
DataEventTypeEnum eventType,
+                                final String namespaceId) {
+        if (CollectionUtils.isEmpty(changed)) {
+            sendEmptySnapshot(ConfigGroupEnum.PLUGIN, eventType, namespaceId);
+            return;
+        }
+        onPluginChanged(changed, eventType);
+    }
+
     @Override
     public void onSelectorChanged(
             final List<SelectorData> selectorDataList, final DataEventTypeEnum 
eventType) {
@@ -86,6 +97,16 @@ public class WebsocketDataChangedListener implements 
DataChangedListener {
         }
     }
 
+    @Override
+    public void onSelectorChanged(final List<SelectorData> changed, final 
DataEventTypeEnum eventType,
+                                  final String namespaceId) {
+        if (CollectionUtils.isEmpty(changed)) {
+            sendEmptySnapshot(ConfigGroupEnum.SELECTOR, eventType, 
namespaceId);
+            return;
+        }
+        onSelectorChanged(changed, eventType);
+    }
+
     @Override
     public void onRuleChanged(
             final List<RuleData> ruleDataList, final DataEventTypeEnum 
eventType) {
@@ -106,6 +127,16 @@ public class WebsocketDataChangedListener implements 
DataChangedListener {
         }
     }
 
+    @Override
+    public void onRuleChanged(final List<RuleData> changed, final 
DataEventTypeEnum eventType,
+                              final String namespaceId) {
+        if (CollectionUtils.isEmpty(changed)) {
+            sendEmptySnapshot(ConfigGroupEnum.RULE, eventType, namespaceId);
+            return;
+        }
+        onRuleChanged(changed, eventType);
+    }
+
     @Override
     public void onAppAuthChanged(
             final List<AppAuthData> appAuthDataList, final DataEventTypeEnum 
eventType) {
@@ -127,6 +158,27 @@ public class WebsocketDataChangedListener implements 
DataChangedListener {
         }
     }
 
+    @Override
+    public void onAppAuthChanged(final List<AppAuthData> changed, final 
DataEventTypeEnum eventType,
+                                 final String namespaceId) {
+        if (CollectionUtils.isEmpty(changed)) {
+            sendEmptySnapshot(ConfigGroupEnum.APP_AUTH, eventType, 
namespaceId);
+            return;
+        }
+        onAppAuthChanged(changed, eventType);
+    }
+
+    private void sendEmptySnapshot(final ConfigGroupEnum group, final 
DataEventTypeEnum eventType,
+                                   final String namespaceId) {
+        if (StringUtils.isBlank(namespaceId)
+                || (eventType != DataEventTypeEnum.REFRESH && eventType != 
DataEventTypeEnum.MYSELF)) {
+            return;
+        }
+        WebsocketData<Object> websocketData =
+                new WebsocketData<>(group.name(), eventType.name(), 
Collections.emptyList());
+        WebsocketCollector.send(namespaceId, 
GsonUtils.getInstance().toJson(websocketData), eventType);
+    }
+
     @Override
     public void onMetaDataChanged(
             final List<MetaData> metaDataList, final DataEventTypeEnum 
eventType) {
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 0aeb8373d5..8fae8407de 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
@@ -277,6 +277,11 @@ public class AppAuthServiceImpl implements AppAuthService {
     @Override
     public ShenyuAdminResult syncDataByNamespaceId(final String namespaceId) {
         List<AppAuthDO> appAuthDOList = 
appAuthMapper.selectAllByNamespaceId(namespaceId);
+        if (CollectionUtils.isEmpty(appAuthDOList)) {
+            eventPublisher.publishEvent(new 
DataChangedEvent(ConfigGroupEnum.APP_AUTH,
+                    DataEventTypeEnum.REFRESH, Collections.emptyList(), 
namespaceId));
+            return ShenyuAdminResult.success();
+        }
         return syncData(appAuthDOList);
     }
 
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SyncDataServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SyncDataServiceImpl.java
index f36d12e9b6..1ceedc9b79 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SyncDataServiceImpl.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SyncDataServiceImpl.java
@@ -147,13 +147,14 @@ public class SyncDataServiceImpl implements 
SyncDataService {
 
         List<PluginData> pluginDataList = 
namespacePluginService.listAll(namespaceId);
 
-        eventPublisher.publishEvent(new 
DataChangedEvent(ConfigGroupEnum.PLUGIN, type, pluginDataList));
+        eventPublisher.publishEvent(new 
DataChangedEvent(ConfigGroupEnum.PLUGIN, type, pluginDataList, namespaceId));
 
         List<SelectorData> selectorDataList = 
selectorService.listAllByNamespaceId(namespaceId);
-        eventPublisher.publishEvent(new 
DataChangedEvent(ConfigGroupEnum.SELECTOR, type, selectorDataList));
+        eventPublisher.publishEvent(
+                new DataChangedEvent(ConfigGroupEnum.SELECTOR, type, 
selectorDataList, namespaceId));
 
         List<RuleData> ruleDataList = 
ruleService.listAllByNamespaceId(namespaceId);
-        eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.RULE, 
type, ruleDataList));
+        eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.RULE, 
type, ruleDataList, namespaceId));
 
         metaDataService.syncDataByNamespaceId(namespaceId);
         discoveryService.syncDataByNamespaceId(namespaceId);
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcherTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcherTest.java
index fad86580f1..52236191bd 100644
--- 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcherTest.java
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcherTest.java
@@ -123,10 +123,10 @@ public final class DataChangedEventDispatcherTest {
         ConfigGroupEnum configGroupEnum = ConfigGroupEnum.APP_AUTH;
         DataChangedEvent dataChangedEvent = new 
DataChangedEvent(configGroupEnum, null, new ArrayList<>());
         dataChangedEventDispatcher.onApplicationEvent(dataChangedEvent);
-        verify(httpLongPollingDataChangedListener, 
times(1)).onAppAuthChanged(anyList(), any());
-        verify(nacosDataChangedListener, times(1)).onAppAuthChanged(anyList(), 
any());
-        verify(websocketDataChangedListener, 
times(1)).onAppAuthChanged(anyList(), any());
-        verify(zookeeperDataChangedListener, 
times(1)).onAppAuthChanged(anyList(), any());
+        verify(httpLongPollingDataChangedListener, 
times(1)).onAppAuthChanged(anyList(), any(), any());
+        verify(nacosDataChangedListener, times(1)).onAppAuthChanged(anyList(), 
any(), any());
+        verify(websocketDataChangedListener, 
times(1)).onAppAuthChanged(anyList(), any(), any());
+        verify(zookeeperDataChangedListener, 
times(1)).onAppAuthChanged(anyList(), any(), any());
     }
 
     /**
@@ -139,10 +139,10 @@ public final class DataChangedEventDispatcherTest {
         ConfigGroupEnum configGroupEnum = ConfigGroupEnum.PLUGIN;
         DataChangedEvent dataChangedEvent = new 
DataChangedEvent(configGroupEnum, null, new ArrayList<>());
         dataChangedEventDispatcher.onApplicationEvent(dataChangedEvent);
-        verify(httpLongPollingDataChangedListener, 
times(1)).onPluginChanged(anyList(), any());
-        verify(nacosDataChangedListener, times(1)).onPluginChanged(anyList(), 
any());
-        verify(websocketDataChangedListener, 
times(1)).onPluginChanged(anyList(), any());
-        verify(zookeeperDataChangedListener, 
times(1)).onPluginChanged(anyList(), any());
+        verify(httpLongPollingDataChangedListener, 
times(1)).onPluginChanged(anyList(), any(), any());
+        verify(nacosDataChangedListener, times(1)).onPluginChanged(anyList(), 
any(), any());
+        verify(websocketDataChangedListener, 
times(1)).onPluginChanged(anyList(), any(), any());
+        verify(zookeeperDataChangedListener, 
times(1)).onPluginChanged(anyList(), any(), any());
     }
 
     /**
@@ -155,10 +155,10 @@ public final class DataChangedEventDispatcherTest {
         ConfigGroupEnum configGroupEnum = ConfigGroupEnum.RULE;
         DataChangedEvent dataChangedEvent = new 
DataChangedEvent(configGroupEnum, null, new ArrayList<>());
         dataChangedEventDispatcher.onApplicationEvent(dataChangedEvent);
-        verify(httpLongPollingDataChangedListener, 
times(1)).onRuleChanged(anyList(), any());
-        verify(nacosDataChangedListener, times(1)).onRuleChanged(anyList(), 
any());
-        verify(websocketDataChangedListener, 
times(1)).onRuleChanged(anyList(), any());
-        verify(zookeeperDataChangedListener, 
times(1)).onRuleChanged(anyList(), any());
+        verify(httpLongPollingDataChangedListener, 
times(1)).onRuleChanged(anyList(), any(), any());
+        verify(nacosDataChangedListener, times(1)).onRuleChanged(anyList(), 
any(), any());
+        verify(websocketDataChangedListener, 
times(1)).onRuleChanged(anyList(), any(), any());
+        verify(zookeeperDataChangedListener, 
times(1)).onRuleChanged(anyList(), any(), any());
     }
 
     /**
@@ -171,10 +171,10 @@ public final class DataChangedEventDispatcherTest {
         ConfigGroupEnum configGroupEnum = ConfigGroupEnum.SELECTOR;
         DataChangedEvent dataChangedEvent = new 
DataChangedEvent(configGroupEnum, null, new ArrayList<>());
         dataChangedEventDispatcher.onApplicationEvent(dataChangedEvent);
-        verify(httpLongPollingDataChangedListener, 
times(1)).onSelectorChanged(anyList(), any());
-        verify(nacosDataChangedListener, 
times(1)).onSelectorChanged(anyList(), any());
-        verify(websocketDataChangedListener, 
times(1)).onSelectorChanged(anyList(), any());
-        verify(zookeeperDataChangedListener, 
times(1)).onSelectorChanged(anyList(), any());
+        verify(httpLongPollingDataChangedListener, 
times(1)).onSelectorChanged(anyList(), any(), any());
+        verify(nacosDataChangedListener, 
times(1)).onSelectorChanged(anyList(), any(), any());
+        verify(websocketDataChangedListener, 
times(1)).onSelectorChanged(anyList(), any(), any());
+        verify(zookeeperDataChangedListener, 
times(1)).onSelectorChanged(anyList(), any(), any());
     }
 
     /**
@@ -274,10 +274,10 @@ public final class DataChangedEventDispatcherTest {
         when(clusterProperties.isEnabled()).thenReturn(false);
         DataChangedEvent dataChangedEvent = new 
DataChangedEvent(ConfigGroupEnum.PLUGIN, null, new ArrayList<>());
         dataChangedEventDispatcher.onApplicationEvent(dataChangedEvent);
-        verify(httpLongPollingDataChangedListener, 
times(1)).onPluginChanged(anyList(), any());
-        verify(nacosDataChangedListener, times(1)).onPluginChanged(anyList(), 
any());
-        verify(websocketDataChangedListener, 
times(1)).onPluginChanged(anyList(), any());
-        verify(zookeeperDataChangedListener, 
times(1)).onPluginChanged(anyList(), any());
+        verify(httpLongPollingDataChangedListener, 
times(1)).onPluginChanged(anyList(), any(), any());
+        verify(nacosDataChangedListener, times(1)).onPluginChanged(anyList(), 
any(), any());
+        verify(websocketDataChangedListener, 
times(1)).onPluginChanged(anyList(), any(), any());
+        verify(zookeeperDataChangedListener, 
times(1)).onPluginChanged(anyList(), any(), any());
     }
 
     /**
@@ -292,7 +292,7 @@ public final class DataChangedEventDispatcherTest {
         ReflectionTestUtils.setField(dataChangedEventDispatcher, "listeners", 
Collections.unmodifiableList(orderedListeners));
         DataChangedEvent dataChangedEvent = new 
DataChangedEvent(ConfigGroupEnum.PLUGIN, null, new ArrayList<>());
         dataChangedEventDispatcher.onApplicationEvent(dataChangedEvent);
-        verify(nacosDataChangedListener, never()).onPluginChanged(anyList(), 
any());
+        verify(nacosDataChangedListener, never()).onPluginChanged(anyList(), 
any(), any());
     }
 
     /**
@@ -306,9 +306,9 @@ public final class DataChangedEventDispatcherTest {
         field.set(dataChangedEventDispatcher, null);
         DataChangedEvent dataChangedEvent = new 
DataChangedEvent(ConfigGroupEnum.PLUGIN, null, new ArrayList<>());
         dataChangedEventDispatcher.onApplicationEvent(dataChangedEvent);
-        verify(nacosDataChangedListener, times(1)).onPluginChanged(anyList(), 
any());
-        verify(httpLongPollingDataChangedListener, 
times(1)).onPluginChanged(anyList(), any());
-        verify(websocketDataChangedListener, 
times(1)).onPluginChanged(anyList(), any());
-        verify(zookeeperDataChangedListener, 
times(1)).onPluginChanged(anyList(), any());
+        verify(nacosDataChangedListener, times(1)).onPluginChanged(anyList(), 
any(), any());
+        verify(httpLongPollingDataChangedListener, 
times(1)).onPluginChanged(anyList(), any(), any());
+        verify(websocketDataChangedListener, 
times(1)).onPluginChanged(anyList(), any(), any());
+        verify(zookeeperDataChangedListener, 
times(1)).onPluginChanged(anyList(), any(), any());
     }
 }
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListenerTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListenerTest.java
index 4c7ee9f500..3e1fb5a074 100644
--- 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListenerTest.java
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListenerTest.java
@@ -196,6 +196,29 @@ public final class WebsocketDataChangedListenerTest {
         }
     }
 
+    /**
+     * test empty namespace snapshots.
+     */
+    @Test
+    public void testEmptyNamespaceSnapshots() {
+        String namespaceId = "namespace-id";
+        try (MockedStatic<WebsocketCollector> mockedStatic = 
mockStatic(WebsocketCollector.class)) {
+            websocketDataChangedListener.onPluginChanged(
+                    Collections.emptyList(), DataEventTypeEnum.MYSELF, 
namespaceId);
+            websocketDataChangedListener.onSelectorChanged(
+                    Collections.emptyList(), DataEventTypeEnum.MYSELF, 
namespaceId);
+            websocketDataChangedListener.onRuleChanged(
+                    Collections.emptyList(), DataEventTypeEnum.MYSELF, 
namespaceId);
+            websocketDataChangedListener.onAppAuthChanged(
+                    Collections.emptyList(), DataEventTypeEnum.REFRESH, 
namespaceId);
+
+            verifyEmptySnapshot(mockedStatic, namespaceId, "PLUGIN", 
DataEventTypeEnum.MYSELF);
+            verifyEmptySnapshot(mockedStatic, namespaceId, "SELECTOR", 
DataEventTypeEnum.MYSELF);
+            verifyEmptySnapshot(mockedStatic, namespaceId, "RULE", 
DataEventTypeEnum.MYSELF);
+            verifyEmptySnapshot(mockedStatic, namespaceId, "APP_AUTH", 
DataEventTypeEnum.REFRESH);
+        }
+    }
+
     /**
      * test MetaData.
      */
@@ -366,6 +389,14 @@ public final class WebsocketDataChangedListenerTest {
         }
     }
 
+    private void verifyEmptySnapshot(final MockedStatic<WebsocketCollector> 
mockedStatic, final String namespaceId,
+                                     final String groupType, final 
DataEventTypeEnum eventType) {
+        String message = String.format(
+                "{\"groupType\":\"%s\",\"eventType\":\"%s\",\"data\":[]}", 
groupType, eventType.name());
+        mockedStatic.verify(() -> WebsocketCollector.send(
+                eq(namespaceId), argThat(actualMsg -> jsonEquals(message, 
actualMsg)), eq(eventType)));
+    }
+
     private void initMetaDataList() {
         MetaData metaData = new MetaData();
         metaData.setAppName("axiba");
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/AppAuthServiceTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/AppAuthServiceTest.java
index 8c86f7c729..3715aa1c29 100644
--- 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/AppAuthServiceTest.java
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/AppAuthServiceTest.java
@@ -43,6 +43,7 @@ import 
org.apache.shenyu.admin.service.impl.AppAuthServiceImpl;
 import org.apache.shenyu.admin.utils.ShenyuResultMessage;
 import org.apache.shenyu.common.constant.AdminConstants;
 import org.apache.shenyu.common.dto.AppAuthData;
+import org.apache.shenyu.common.enums.ConfigGroupEnum;
 import org.apache.shenyu.common.exception.CommonErrorCode;
 import org.apache.shenyu.common.utils.SignUtils;
 import org.apache.shenyu.common.utils.UUIDUtils;
@@ -300,6 +301,21 @@ public final class AppAuthServiceTest {
         verify(eventPublisher, times(1)).publishEvent(any());
     }
 
+    @Test
+    public void testSyncEmptyDataByNamespaceId() {
+        String namespaceId = "namespace-id";
+        
when(appAuthMapper.selectAllByNamespaceId(namespaceId)).thenReturn(Collections.emptyList());
+
+        appAuthService.syncDataByNamespaceId(namespaceId);
+
+        ArgumentCaptor<DataChangedEvent> eventCaptor = 
ArgumentCaptor.forClass(DataChangedEvent.class);
+        verify(eventPublisher).publishEvent(eventCaptor.capture());
+        DataChangedEvent event = eventCaptor.getValue();
+        assertEquals(ConfigGroupEnum.APP_AUTH, event.getGroupKey());
+        assertEquals(namespaceId, event.getNamespaceId());
+        assertEquals(Collections.emptyList(), event.getSource());
+    }
+
     private void testApplyCreateParameterError() {
         testApplyCreateAppNameBlank();
         testApplyCreateMissingPathsWhenOpen();
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SyncDataServiceTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SyncDataServiceTest.java
index 4af22988d1..1ca6d8c448 100644
--- 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SyncDataServiceTest.java
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SyncDataServiceTest.java
@@ -17,20 +17,23 @@
 
 package org.apache.shenyu.admin.service;
 
+import org.apache.shenyu.admin.listener.DataChangedEvent;
 import org.apache.shenyu.admin.model.result.ShenyuAdminResult;
 import org.apache.shenyu.admin.model.vo.NamespacePluginVO;
 import org.apache.shenyu.admin.service.impl.SyncDataServiceImpl;
+import org.apache.shenyu.admin.service.support.AiProxyRealKeyResolver;
 import org.apache.shenyu.common.dto.ConditionData;
 import org.apache.shenyu.common.dto.PluginData;
 import org.apache.shenyu.common.dto.RuleData;
 import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.ConfigGroupEnum;
 import org.apache.shenyu.common.enums.DataEventTypeEnum;
 import org.apache.shenyu.common.enums.OperatorEnum;
 import org.apache.shenyu.common.enums.ParamTypeEnum;
 import org.apache.shenyu.common.utils.DateUtils;
-import static org.hamcrest.Matchers.lessThanOrEqualTo;
 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;
@@ -40,12 +43,17 @@ import 
org.springframework.context.ApplicationEventPublisher;
 
 import java.time.LocalDateTime;
 import java.util.Collections;
+import java.util.List;
 
 import static 
org.apache.shenyu.common.constant.Constants.SYS_DEFAULT_NAMESPACE_ID;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.BDDMockito.given;
-import org.apache.shenyu.admin.service.support.AiProxyRealKeyResolver;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 /**
  * test for SyncDataService.
@@ -127,6 +135,27 @@ public final class SyncDataServiceTest {
         assertThat(syncDataService.syncPluginData(pluginVO.getId()), 
lessThanOrEqualTo(false));
     }
 
+    @Test
+    public void syncAllByNamespaceIdPublishesEmptySnapshotsTest() {
+        String namespaceId = "namespace-id";
+        
given(this.namespacePluginService.listAll(namespaceId)).willReturn(Collections.emptyList());
+        
given(this.selectorService.listAllByNamespaceId(namespaceId)).willReturn(Collections.emptyList());
+        
given(this.ruleService.listAllByNamespaceId(namespaceId)).willReturn(Collections.emptyList());
+
+        
assertTrue(syncDataService.syncAllByNamespaceId(DataEventTypeEnum.MYSELF, 
namespaceId));
+
+        ArgumentCaptor<DataChangedEvent> eventCaptor = 
ArgumentCaptor.forClass(DataChangedEvent.class);
+        verify(eventPublisher, times(3)).publishEvent(eventCaptor.capture());
+        List<DataChangedEvent> events = eventCaptor.getAllValues();
+        assertEquals(ConfigGroupEnum.PLUGIN, events.get(0).getGroupKey());
+        assertEquals(ConfigGroupEnum.SELECTOR, events.get(1).getGroupKey());
+        assertEquals(ConfigGroupEnum.RULE, events.get(2).getGroupKey());
+        events.forEach(event -> {
+            assertEquals(namespaceId, event.getNamespaceId());
+            assertTrue(event.getSource().isEmpty());
+        });
+    }
+
 
     /**
      * build mock PluginData.
diff --git 
a/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/main/java/org/apache/shenyu/plugin/sign/cache/SignAuthDataCache.java
 
b/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/main/java/org/apache/shenyu/plugin/sign/cache/SignAuthDataCache.java
index 822d843d32..fcb4e1712e 100644
--- 
a/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/main/java/org/apache/shenyu/plugin/sign/cache/SignAuthDataCache.java
+++ 
b/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/main/java/org/apache/shenyu/plugin/sign/cache/SignAuthDataCache.java
@@ -63,6 +63,13 @@ public final class SignAuthDataCache {
     public void removeAuthData(final AppAuthData data) {
         AUTH_MAP.remove(data.getAppKey());
     }
+
+    /**
+     * Clean all auth data.
+     */
+    public void cleanAuthData() {
+        AUTH_MAP.clear();
+    }
     
     /**
      * Obtain auth data app auth data.
diff --git 
a/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/main/java/org/apache/shenyu/plugin/sign/subscriber/SignAuthDataSubscriber.java
 
b/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/main/java/org/apache/shenyu/plugin/sign/subscriber/SignAuthDataSubscriber.java
index d360786f76..ee83757548 100644
--- 
a/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/main/java/org/apache/shenyu/plugin/sign/subscriber/SignAuthDataSubscriber.java
+++ 
b/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/main/java/org/apache/shenyu/plugin/sign/subscriber/SignAuthDataSubscriber.java
@@ -35,4 +35,9 @@ public class SignAuthDataSubscriber implements 
AuthDataSubscriber {
     public void unSubscribe(final AppAuthData appAuthData) {
         SignAuthDataCache.getInstance().removeAuthData(appAuthData);
     }
+
+    @Override
+    public void refresh() {
+        SignAuthDataCache.getInstance().cleanAuthData();
+    }
 }
diff --git 
a/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/test/java/org/apache/shenyu/plugin/sign/subscriber/SignAuthDataSubscriberTest.java
 
b/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/test/java/org/apache/shenyu/plugin/sign/subscriber/SignAuthDataSubscriberTest.java
index d56e3dab07..39704c933d 100644
--- 
a/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/test/java/org/apache/shenyu/plugin/sign/subscriber/SignAuthDataSubscriberTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/test/java/org/apache/shenyu/plugin/sign/subscriber/SignAuthDataSubscriberTest.java
@@ -60,4 +60,15 @@ public class SignAuthDataSubscriberTest {
         signAuthDataSubscriber.unSubscribe(appAuthData);
         
assertNull(SignAuthDataCache.getInstance().obtainAuthData("D9FD95F496C9495DB5604222A13C3D08"));
     }
+
+    @Test
+    void refresh() {
+        AppAuthData appAuthData = new AppAuthData();
+        appAuthData.setAppKey("stale-key");
+        signAuthDataSubscriber.onSubscribe(appAuthData);
+
+        signAuthDataSubscriber.refresh();
+
+        
assertNull(SignAuthDataCache.getInstance().obtainAuthData("stale-key"));
+    }
 }
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/AbstractDataHandler.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/AbstractDataHandler.java
index 48188dd67a..a75e443ee1 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/AbstractDataHandler.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/AbstractDataHandler.java
@@ -17,6 +17,7 @@
 
 package org.apache.shenyu.plugin.sync.data.websocket.handler;
 
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.commons.collections4.CollectionUtils;
@@ -61,23 +62,22 @@ public abstract class AbstractDataHandler<T> implements 
DataHandler {
     @Override
     public void handle(final String json, final String eventType) {
         List<T> dataList = convert(json);
-
-        if (CollectionUtils.isEmpty(dataList)) {
-            return;
-        }
-
         DataEventTypeEnum eventTypeEnum = 
DataEventTypeEnum.acquireByName(eventType);
         switch (eventTypeEnum) {
             case REFRESH:
             case MYSELF:
-                doRefresh(dataList);
+                doRefresh(CollectionUtils.isEmpty(dataList) ? 
Collections.emptyList() : dataList);
                 break;
             case UPDATE:
             case CREATE:
-                doUpdate(dataList);
+                if (CollectionUtils.isNotEmpty(dataList)) {
+                    doUpdate(dataList);
+                }
                 break;
             case DELETE:
-                doDelete(dataList);
+                if (CollectionUtils.isNotEmpty(dataList)) {
+                    doDelete(dataList);
+                }
                 break;
             default:
                 break;
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/PluginDataHandler.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/PluginDataHandler.java
index 40e4b22719..719414fe54 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/PluginDataHandler.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/PluginDataHandler.java
@@ -41,6 +41,7 @@ public class PluginDataHandler extends 
AbstractDataHandler<PluginData> {
 
     @Override
     protected void doRefresh(final List<PluginData> dataList) {
+        pluginDataSubscriber.refreshPluginDataAll();
         pluginDataSubscriber.onPluginRefresh(dataList);
     }
 
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/RuleDataHandler.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/RuleDataHandler.java
index 31e8ff697b..a447b19ee9 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/RuleDataHandler.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/RuleDataHandler.java
@@ -40,6 +40,7 @@ public class RuleDataHandler extends 
AbstractDataHandler<RuleData> {
 
     @Override
     protected void doRefresh(final List<RuleData> dataList) {
+        pluginDataSubscriber.refreshRuleDataAll();
         pluginDataSubscriber.onRuleRefresh(dataList);
     }
 
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/SelectorDataHandler.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/SelectorDataHandler.java
index 8682485636..7db231633e 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/SelectorDataHandler.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/SelectorDataHandler.java
@@ -41,6 +41,7 @@ public class SelectorDataHandler extends 
AbstractDataHandler<SelectorData> {
 
     @Override
     protected void doRefresh(final List<SelectorData> dataList) {
+        pluginDataSubscriber.refreshSelectorDataAll();
         pluginDataSubscriber.onSelectorRefresh(dataList);
     }
 
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/PluginDataHandlerTest.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/PluginDataHandlerTest.java
index 978d49030f..54e5626173 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/PluginDataHandlerTest.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/PluginDataHandlerTest.java
@@ -19,6 +19,7 @@ package org.apache.shenyu.plugin.sync.data.websocket.handler;
 
 import com.google.gson.Gson;
 import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.enums.DataEventTypeEnum;
 import org.apache.shenyu.sync.data.api.PluginDataSubscriber;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -31,7 +32,6 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 public final class PluginDataHandlerTest {
@@ -60,24 +60,35 @@ public final class PluginDataHandlerTest {
     public void testDoRefresh() {
         List<PluginData> pluginDataList = createFakePluginDataObjects(3);
         pluginDataHandler.doRefresh(pluginDataList);
+        verify(subscriber).refreshPluginDataAll();
         verify(subscriber).onPluginRefresh(pluginDataList);
     }
 
 
     @ParameterizedTest
     @ValueSource(strings = {"REFRESH", "MYSELF"})
-    void testRefreshEventsUseOnlyTheBatchCallback(final String eventType) {
+    void testRefreshEventsClearThenUseBatchCallback(final String eventType) {
         List<PluginData> batch = createFakePluginDataObjects(2);
         pluginDataHandler.handle(new Gson().toJson(batch), eventType);
+        verify(subscriber).refreshPluginDataAll();
         verify(subscriber).onPluginRefresh(batch);
         verifyNoMoreInteractions(subscriber);
     }
 
     @ParameterizedTest
     @ValueSource(strings = {"REFRESH", "MYSELF"})
-    void testEmptyRefreshDoesNotCallSubscriber(final String eventType) {
+    void testEmptyRefreshClearsSnapshot(final String eventType) {
         pluginDataHandler.handle("[]", eventType);
-        verifyNoInteractions(subscriber);
+        verify(subscriber).refreshPluginDataAll();
+        verify(subscriber).onPluginRefresh(java.util.Collections.emptyList());
+        verifyNoMoreInteractions(subscriber);
+    }
+
+    @Test
+    public void testEmptySnapshotStillClearsCachedData() {
+        pluginDataHandler.handle("[]", DataEventTypeEnum.MYSELF.name());
+
+        verify(subscriber).refreshPluginDataAll();
     }
 
     @Test
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/RuleDataHandlerTest.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/RuleDataHandlerTest.java
index 648483b3e5..fbb524845b 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/RuleDataHandlerTest.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/RuleDataHandlerTest.java
@@ -33,7 +33,6 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 public final class RuleDataHandlerTest {
@@ -65,24 +64,28 @@ public final class RuleDataHandlerTest {
     public void testDoRefresh() {
         List<RuleData> ruleDataList = createFakeRuleDateObjects(3);
         ruleDataHandler.doRefresh(ruleDataList);
+        verify(subscriber).refreshRuleDataAll();
         verify(subscriber).onRuleRefresh(ruleDataList);
     }
 
 
     @ParameterizedTest
     @ValueSource(strings = {"REFRESH", "MYSELF"})
-    void testRefreshEventsUseOnlyTheBatchCallback(final String eventType) {
+    void testRefreshEventsClearThenUseBatchCallback(final String eventType) {
         List<RuleData> batch = createFakeRuleDateObjects(2);
         ruleDataHandler.handle(new Gson().toJson(batch), eventType);
+        verify(subscriber).refreshRuleDataAll();
         verify(subscriber).onRuleRefresh(batch);
         verifyNoMoreInteractions(subscriber);
     }
 
     @ParameterizedTest
     @ValueSource(strings = {"REFRESH", "MYSELF"})
-    void testEmptyRefreshDoesNotCallSubscriber(final String eventType) {
+    void testEmptyRefreshClearsSnapshot(final String eventType) {
         ruleDataHandler.handle("[]", eventType);
-        verifyNoInteractions(subscriber);
+        verify(subscriber).refreshRuleDataAll();
+        verify(subscriber).onRuleRefresh(java.util.Collections.emptyList());
+        verifyNoMoreInteractions(subscriber);
     }
 
     @Test
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/SelectorDataHandlerTest.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/SelectorDataHandlerTest.java
index e58e7037ee..7a5e8833e8 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/SelectorDataHandlerTest.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/SelectorDataHandlerTest.java
@@ -33,7 +33,6 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 public final class SelectorDataHandlerTest {
@@ -65,25 +64,29 @@ public final class SelectorDataHandlerTest {
     public void testDoRefresh() {
         List<SelectorData> selectorDataList = createFakeSelectorDataObjects(3);
         selectorDataHandler.doRefresh(selectorDataList);
+        verify(subscriber).refreshSelectorDataAll();
         verify(subscriber).onSelectorRefresh(selectorDataList);
     }
 
 
     @ParameterizedTest
     @ValueSource(strings = {"REFRESH", "MYSELF"})
-    void testRefreshEventsUseOnlyTheBatchCallback(final String eventType) {
+    void testRefreshEventsClearThenUseBatchCallback(final String eventType) {
         List<SelectorData> batch = createFakeSelectorDataObjects(2);
         batch.forEach(data -> data.setContinued(true));
         selectorDataHandler.handle(new Gson().toJson(batch), eventType);
+        verify(subscriber).refreshSelectorDataAll();
         verify(subscriber).onSelectorRefresh(batch);
         verifyNoMoreInteractions(subscriber);
     }
 
     @ParameterizedTest
     @ValueSource(strings = {"REFRESH", "MYSELF"})
-    void testEmptyRefreshDoesNotCallSubscriber(final String eventType) {
+    void testEmptyRefreshClearsSnapshot(final String eventType) {
         selectorDataHandler.handle("[]", eventType);
-        verifyNoInteractions(subscriber);
+        verify(subscriber).refreshSelectorDataAll();
+        
verify(subscriber).onSelectorRefresh(java.util.Collections.emptyList());
+        verifyNoMoreInteractions(subscriber);
     }
 
     @Test
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/WebsocketDataHandlerTest.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/WebsocketDataHandlerTest.java
index 4dca4ea295..afec30bcee 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/WebsocketDataHandlerTest.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/WebsocketDataHandlerTest.java
@@ -64,6 +64,7 @@ public final class WebsocketDataHandlerTest {
     public void testPluginRefreshExecutor() {
         String json = getJson();
         websocketDataHandler.executor(ConfigGroupEnum.PLUGIN, json, 
DataEventTypeEnum.REFRESH.name());
+        Mockito.verify(pluginDataSubscriber).refreshPluginDataAll();
         List<PluginData> pluginDataList = new 
PluginDataHandler(pluginDataSubscriber).convert(json);
         Mockito.verify(pluginDataSubscriber).onPluginRefresh(pluginDataList);
     }
@@ -72,6 +73,7 @@ public final class WebsocketDataHandlerTest {
     public void testPluginMyselfExecutor() {
         String json = getJson();
         websocketDataHandler.executor(ConfigGroupEnum.PLUGIN, json, 
DataEventTypeEnum.MYSELF.name());
+        Mockito.verify(pluginDataSubscriber).refreshPluginDataAll();
         List<PluginData> pluginDataList = new 
PluginDataHandler(pluginDataSubscriber).convert(json);
         Mockito.verify(pluginDataSubscriber).onPluginRefresh(pluginDataList);
     }

Reply via email to