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

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 457fa7baa fix(web): sequence audit, client connection, cluster 
inventory and DLQ group requests (#2592)
457fa7baa is described below

commit 457fa7baa62097bb7895ca6384ef895a554d5eba
Author: xdz997 <[email protected]>
AuthorDate: Thu Aug 27 14:18:09 2026 +0800

    fix(web): sequence audit, client connection, cluster inventory and DLQ 
group requests (#2592)
    
    * fix: sequence audit log filter requests
    
    * fix: sequence client connection requests
    
    * fix: sequence cluster inventory requests
    
    * fix: sequence DLQ group requests
---
 .../pages/cluster/__tests__/ClientsPage.test.tsx   | 69 ++++++++++++++++-
 .../pages/cluster/__tests__/ClusterPage.test.tsx   | 31 ++++++++
 web/src/pages/cluster/clients.tsx                  | 44 ++++++-----
 web/src/pages/cluster/index.tsx                    | 90 +++++++++++-----------
 web/src/pages/instance/__tests__/DLQPage.test.tsx  | 25 ++++++
 web/src/pages/instance/dlq.tsx                     | 24 +++---
 web/src/pages/ops/__tests__/AuditPage.test.tsx     | 47 ++++++++++-
 web/src/pages/ops/audit.tsx                        | 75 +++++++++++-------
 8 files changed, 299 insertions(+), 106 deletions(-)

diff --git a/web/src/pages/cluster/__tests__/ClientsPage.test.tsx 
b/web/src/pages/cluster/__tests__/ClientsPage.test.tsx
index 8573facb5..e8b5bae57 100644
--- a/web/src/pages/cluster/__tests__/ClientsPage.test.tsx
+++ b/web/src/pages/cluster/__tests__/ClientsPage.test.tsx
@@ -16,7 +16,7 @@
  */
 
 import { App } from 'antd';
-import { render, screen, waitFor, within } from '@testing-library/react';
+import { act, render, screen, waitFor, within } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import type React from 'react';
 import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 
'vitest';
@@ -139,6 +139,16 @@ const renderWithProviders = (ui: React.ReactElement) =>
     </App>,
   );
 
+const deferred = <T,>() => {
+  let resolve!: (value: T) => void;
+  let reject!: (reason?: unknown) => void;
+  const promise = new Promise<T>((resolvePromise, rejectPromise) => {
+    resolve = resolvePromise;
+    reject = rejectPromise;
+  });
+  return { promise, resolve, reject };
+};
+
 describe('Clients page', () => {
   it('returns to the first page when the connection search changes', async () 
=> {
     const pagedConnections = Array.from({ length: 21 }, (_, index) => ({
@@ -392,6 +402,63 @@ describe('Clients page', () => {
     
expect(within(screen.getByTestId('connection-total')).getByText('0')).toBeInTheDocument();
   });
 
+  it('ignores a stale connection response after switching nameservers', async 
() => {
+    const user = userEvent.setup();
+    const stale = deferred<ClientConnection[]>();
+    const latest = deferred<ClientConnection[]>();
+    vi.mocked(connectionsService.listConnections)
+      .mockImplementationOnce(() => stale.promise)
+      .mockImplementationOnce(() => latest.promise);
+
+    renderWithProviders(<ClientsPage />);
+
+    await user.click(screen.getByRole('combobox', { name: 'NameServer' }));
+    await user.click(
+      await screen.findByText('rocketmq2 (namesrv-2:9876)', {
+        selector: '.ant-select-item-option-content',
+      }),
+    );
+
+    await waitFor(() =>
+      expect(connectionsService.listConnections).toHaveBeenLastCalledWith({
+        namesrvAddr: 'namesrv-2:9876',
+      }),
+    );
+
+    await act(async () => {
+      latest.resolve(connections);
+      stale.resolve([{ ...connection, clientId: '[email protected]:49160' 
}]);
+      await Promise.resolve();
+      await Promise.resolve();
+    });
+
+    
expect(screen.queryByText('[email protected]:49160')).not.toBeInTheDocument();
+    
expect(within(screen.getByTestId('connection-total')).getByText('3')).toBeInTheDocument();
+  });
+
+  it('ignores a stale registry response after a retry', async () => {
+    const user = userEvent.setup();
+    const stale = deferred<ClusterInfo[]>();
+    vi.mocked(clusterService.listRegistryClusters)
+      .mockRejectedValueOnce(new Error('Unable to load registry clusters'))
+      .mockImplementationOnce(() => stale.promise);
+
+    renderWithProviders(<ClientsPage />);
+
+    expect(await screen.findByText('Unable to load registry 
clusters')).toBeInTheDocument();
+    await user.click(screen.getByRole('button', { name: /重\s*试/ }));
+    await waitFor(() =>
+      expect(clusterService.listRegistryClusters).toHaveBeenNthCalledWith(2),
+    );
+
+    await act(async () => {
+      stale.resolve([]);
+    });
+
+    expect(screen.queryByText('Unable to load registry 
clusters')).not.toBeInTheDocument();
+    expect(connectionsService.listConnections).toHaveBeenCalledTimes(0);
+  });
+
   it('surfaces registry discovery failures and allows retrying', async () => {
     vi.mocked(clusterService.listRegistryClusters)
       .mockRejectedValueOnce(new Error('Unable to load registry clusters'))
diff --git a/web/src/pages/cluster/__tests__/ClusterPage.test.tsx 
b/web/src/pages/cluster/__tests__/ClusterPage.test.tsx
index 12037bf15..be42cd09d 100644
--- a/web/src/pages/cluster/__tests__/ClusterPage.test.tsx
+++ b/web/src/pages/cluster/__tests__/ClusterPage.test.tsx
@@ -757,4 +757,35 @@ describe('Cluster page', () => {
 
     expect(clusterServiceMocks.listClusters).toHaveBeenCalledTimes(1);
   });
+
+  it('ignores stale registry-cluster and nameserver responses', async () => {
+    const staleClusters = deferred<ClusterInfo[]>();
+    const latestClusters = deferred<ClusterInfo[]>();
+    clusterServiceMocks.listRegistryClusters
+      .mockImplementationOnce(() => staleClusters.promise)
+      .mockImplementationOnce(() => latestClusters.promise);
+
+    renderWithProviders(<ClusterPage />);
+    await flushPromises();
+    fireEvent.click(screen.getByRole('button', { name: '刷新' }));
+    await flushPromises();
+
+    await act(async () => {
+      staleClusters.resolve([buildCluster({ connections: 501 })]);
+      await staleClusters.promise;
+      await Promise.resolve();
+    });
+
+    expect(screen.queryByText('501')).not.toBeInTheDocument();
+
+    fireEvent.click(screen.getByRole('tab', { name: /Proxy 管理/ }));
+
+    await act(async () => {
+      latestClusters.resolve([buildCluster({ connections: 502 })]);
+      await latestClusters.promise;
+      await Promise.resolve();
+    });
+
+    expect(screen.getByText('502')).toBeInTheDocument();
+  });
 });
diff --git a/web/src/pages/cluster/clients.tsx 
b/web/src/pages/cluster/clients.tsx
index 24700fcef..d696550de 100644
--- a/web/src/pages/cluster/clients.tsx
+++ b/web/src/pages/cluster/clients.tsx
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import { useEffect, useMemo, useState } from 'react';
+import { useEffect, useMemo, useRef, useState } from 'react';
 import {
   Alert,
   Button,
@@ -137,6 +137,8 @@ const ClientsPage = () => {
   const [columnFilters, setColumnFilters] = useState<ClientTableFilters>({});
   const [currentPage, setCurrentPage] = useState(1);
   const [pageSize, setPageSize] = useState(20);
+  const registryRequestRef = useRef(0);
+  const connectionRequestRef = useRef(0);
 
   const selectedCluster = registryClusters.find((cluster) => cluster.endpoint 
=== selectedEndpoint);
 
@@ -150,6 +152,7 @@ const ClientsPage = () => {
   );
 
   const handleNameserverChange = (endpoint: string) => {
+    connectionRequestRef.current += 1;
     setCurrentPage(1);
     setSelectedEndpoint(endpoint);
     setConnections([]);
@@ -160,11 +163,11 @@ const ClientsPage = () => {
   };
 
   useEffect(() => {
-    let cancelled = false;
+    const requestId = ++registryRequestRef.current;
 
     void listRegistryClusters()
       .then((nextClusters) => {
-        if (cancelled) return;
+        if (registryRequestRef.current !== requestId) return;
         setRegistryClusters(nextClusters);
         setSelectedEndpoint((current) => {
           if (current && nextClusters.some((cluster) => cluster.endpoint === 
current)) {
@@ -175,38 +178,35 @@ const ClientsPage = () => {
         setLoadError(null);
       })
       .catch((error) => {
-        if (cancelled) return;
+        if (registryRequestRef.current !== requestId) return;
         setRegistryClusters([]);
         setSelectedEndpoint(undefined);
         setConnections([]);
         setLoadError(getLoadErrorMessage(error));
       })
       .finally(() => {
-        if (!cancelled) setLoading(false);
+        if (registryRequestRef.current === requestId) setLoading(false);
       });
-
-    return () => {
-      cancelled = true;
-    };
   }, [registryLoadKey]);
 
   useEffect(() => {
-    let cancelled = false;
+    const requestId = ++connectionRequestRef.current;
     if (!selectedEndpoint || !selectedCluster) {
-      return () => {
-        cancelled = true;
-      };
+      return;
     }
+    void Promise.resolve().then(() => {
+      if (connectionRequestRef.current === requestId) setLoading(true);
+    });
 
     void listConnections({ namesrvAddr: selectedEndpoint })
       .then((nextConnections) => {
-        if (!cancelled) {
+        if (connectionRequestRef.current === requestId) {
           setConnections(nextConnections);
           setLoadError(null);
         }
       })
       .catch((error) => {
-        if (!cancelled) {
+        if (connectionRequestRef.current === requestId) {
           setConnections([]);
           setClusterFilter('ALL');
           setSelectedConnection(null);
@@ -214,14 +214,18 @@ const ClientsPage = () => {
         }
       })
       .finally(() => {
-        if (!cancelled) setLoading(false);
+        if (connectionRequestRef.current === requestId) setLoading(false);
       });
-
-    return () => {
-      cancelled = true;
-    };
   }, [connectionLoadKey, selectedEndpoint, selectedCluster]);
 
+  useEffect(
+    () => () => {
+      registryRequestRef.current += 1;
+      connectionRequestRef.current += 1;
+    },
+    [],
+  );
+
   /* ─── Cluster options using nsClusterName ─── */
   const clusterOptions = useMemo(() => {
     const clusterNames = [
diff --git a/web/src/pages/cluster/index.tsx b/web/src/pages/cluster/index.tsx
index 254882f4a..58051a758 100644
--- a/web/src/pages/cluster/index.tsx
+++ b/web/src/pages/cluster/index.tsx
@@ -129,77 +129,75 @@ const ClusterPage = () => {
   const [selectedProxy, setSelectedProxy] = useState<ProxyDetail | null>(null);
   const [configForm] = Form.useForm();
 
-  const loadNsRegistry = useCallback(async () => {
-    try {
-      setNsRegistry(await listNameserverRegistry());
-    } catch {
-      setNsRegistry([]);
-    }
-  }, []);
-
-  useEffect(() => {
-    let active = true;
-    listNameserverRegistry()
-      .then((entries) => {
-        if (active) setNsRegistry(entries);
-      })
-      .catch(() => {
-        if (active) setNsRegistry([]);
-      });
-    return () => {
-      active = false;
-    };
-  }, []);
-
   const [k8sIdOptions, setK8sIdOptions] = useState<string[]>([]);
 
   const [registryClusters, setRegistryClusters] = useState<ClusterInfo[]>([]);
   const [registryLoading, setRegistryLoading] = useState(true);
+  const nsRegistryRequestRef = useRef(0);
+  const registryClustersRequestRef = useRef(0);
+  const k8sCertsRequestRef = useRef(0);
 
   const loadRegistryClusters = useCallback(async () => {
-    setRegistryLoading(true);
+    const requestId = ++registryClustersRequestRef.current;
+    void Promise.resolve().then(() => {
+      if (registryClustersRequestRef.current === requestId) 
setRegistryLoading(true);
+    });
     try {
-      setRegistryClusters(await listRegistryClusters());
+      const nextClusters = await listRegistryClusters();
+      if (registryClustersRequestRef.current === requestId) {
+        setRegistryClusters(nextClusters);
+      }
     } catch {
-      setRegistryClusters([]);
+      if (registryClustersRequestRef.current === requestId) {
+        setRegistryClusters([]);
+      }
     } finally {
-      setRegistryLoading(false);
+      if (registryClustersRequestRef.current === requestId) {
+        setRegistryLoading(false);
+      }
     }
   }, []);
 
   useEffect(() => {
-    let active = true;
-    listRegistryClusters()
-      .then((next) => {
-        if (active) setRegistryClusters(next);
-      })
-      .catch(() => {
-        if (active) setRegistryClusters([]);
-      })
-      .finally(() => {
-        if (active) setRegistryLoading(false);
-      });
-    return () => {
-      active = false;
-    };
+    void Promise.resolve().then(loadRegistryClusters);
+  }, [loadRegistryClusters]);
+
+  const loadNsRegistry = useCallback(async () => {
+    const requestId = ++nsRegistryRequestRef.current;
+    try {
+      const entries = await listNameserverRegistry();
+      if (nsRegistryRequestRef.current === requestId) setNsRegistry(entries);
+    } catch {
+      if (nsRegistryRequestRef.current === requestId) setNsRegistry([]);
+    }
   }, []);
 
   useEffect(() => {
-    let active = true;
+    void Promise.resolve().then(loadNsRegistry);
+  }, [loadNsRegistry]);
+
+  useEffect(() => {
+    const requestId = ++k8sCertsRequestRef.current;
     listK8sCerts()
       .then((certs) => {
-        if (active) {
+        if (k8sCertsRequestRef.current === requestId) {
           setK8sIdOptions([...new Set(certs.map((cert) => 
cert.k8sId).filter(Boolean))]);
         }
       })
       .catch(() => {
-        if (active) setK8sIdOptions([]);
+        if (k8sCertsRequestRef.current === requestId) setK8sIdOptions([]);
       });
-    return () => {
-      active = false;
-    };
   }, []);
 
+  useEffect(
+    () => () => {
+      nsRegistryRequestRef.current += 1;
+      registryClustersRequestRef.current += 1;
+      k8sCertsRequestRef.current += 1;
+    },
+    [],
+  );
+
   const [nsCreateModalOpen, setNsCreateModalOpen] = useState(false);
   const [nsModalMode, setNsModalMode] = useState<'create' | 'edit'>('create');
   const [nsEditId, setNsEditId] = useState<number | null>(null);
diff --git a/web/src/pages/instance/__tests__/DLQPage.test.tsx 
b/web/src/pages/instance/__tests__/DLQPage.test.tsx
index 55860b563..df86adf25 100644
--- a/web/src/pages/instance/__tests__/DLQPage.test.tsx
+++ b/web/src/pages/instance/__tests__/DLQPage.test.tsx
@@ -487,4 +487,29 @@ describe('DLQ page', () => {
     expect(within(retryDialog as 
HTMLElement).getByText('-cg-"payment"')).toBeInTheDocument();
     expect(messageService.listDLQGroups).toHaveBeenCalledTimes(2);
   });
+
+  it('ignores a stale group response after changing instances', async () => {
+    let resolveFirstInstance!: (page: DLQGroupPage) => void;
+    vi.mocked(messageService.listDLQGroups)
+      .mockImplementationOnce(
+        () =>
+          new Promise<DLQGroupPage>((resolve) => {
+            resolveFirstInstance = resolve;
+          }),
+      )
+      .mockResolvedValueOnce(pageOf([secondDlqGroup]));
+    const user = userEvent.setup();
+    renderWithProviders(<DLQPage />);
+
+    await user.click(screen.getAllByRole('combobox')[0]);
+    await user.click(
+      await screen.findByText('instance-2', { selector: 
'.ant-select-item-option-content' }),
+    );
+    expect(await screen.findByText('-cg-"payment"')).toBeInTheDocument();
+
+    await act(async () => resolveFirstInstance(pageOf([dlqGroup])));
+
+    expect(screen.queryByText('cg-order')).not.toBeInTheDocument();
+    expect(screen.getByText('-cg-"payment"')).toBeInTheDocument();
+  });
 });
diff --git a/web/src/pages/instance/dlq.tsx b/web/src/pages/instance/dlq.tsx
index 919e8924e..5cb14544e 100644
--- a/web/src/pages/instance/dlq.tsx
+++ b/web/src/pages/instance/dlq.tsx
@@ -142,6 +142,7 @@ const DLQPage = () => {
   const [detailError, setDetailError] = useState<string | null>(null);
   const detailRequestIdRef = useRef(0);
   const retryRequestIdRef = useRef(0);
+  const groupRequestIdRef = useRef(0);
 
   useEffect(
     () => () => {
@@ -175,16 +176,14 @@ const DLQPage = () => {
   }
 
   useEffect(() => {
-    let cancelled = false;
+    const requestId = ++groupRequestIdRef.current;
 
     if (!selectedInstanceId) {
       void Promise.resolve().then(() => {
-        if (cancelled) return;
+        if (groupRequestIdRef.current !== requestId) return;
         setLoading(false);
       });
-      return () => {
-        cancelled = true;
-      };
+      return;
     }
 
     // Clear `loading` inside the same callback as the data updates so rows and
@@ -192,7 +191,7 @@ const DLQPage = () => {
     // visible for a render while the spin overlay still blocks pointer events.
     void listDLQGroups(selectedInstanceId, search || undefined, page, pageSize)
       .then((result) => {
-        if (!cancelled) {
+        if (groupRequestIdRef.current === requestId) {
           setGroups(result.items);
           setTotal(result.total);
           setLoadError(null);
@@ -206,17 +205,20 @@ const DLQPage = () => {
         }
       })
       .catch((error) => {
-        if (!cancelled) {
+        if (groupRequestIdRef.current === requestId) {
           setLoadError(getErrorMessage(error, DEFAULT_LOAD_ERROR));
           setLoading(false);
         }
       });
-
-    return () => {
-      cancelled = true;
-    };
   }, [refreshKey, selectedInstanceId, search, page, pageSize]);
 
+  useEffect(
+    () => () => {
+      groupRequestIdRef.current += 1;
+    },
+    [],
+  );
+
   const selectedGroups = useMemo(() => {
     const selected = new Set(selectedGroupNames);
     return groups.filter((group) => selected.has(group.groupName));
diff --git a/web/src/pages/ops/__tests__/AuditPage.test.tsx 
b/web/src/pages/ops/__tests__/AuditPage.test.tsx
index d1600138d..65489e0a7 100644
--- a/web/src/pages/ops/__tests__/AuditPage.test.tsx
+++ b/web/src/pages/ops/__tests__/AuditPage.test.tsx
@@ -16,7 +16,7 @@
  */
 
 import { App } from 'antd';
-import { render, screen, waitFor } from '@testing-library/react';
+import { act, render, screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import type React from 'react';
 import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 
'vitest';
@@ -38,6 +38,14 @@ const renderWithProviders = (ui: React.ReactElement) =>
     </App>,
   );
 
+const deferred = <T,>() => {
+  let resolve!: (value: T) => void;
+  const promise = new Promise<T>((resolvePromise) => {
+    resolve = resolvePromise;
+  });
+  return { promise, resolve };
+};
+
 describe('Audit page', () => {
   let createObjectURL: ReturnType<typeof vi.fn>;
   let revokeObjectURL: ReturnType<typeof vi.fn>;
@@ -112,6 +120,11 @@ describe('Audit page', () => {
 
     expect(await screen.findByText('topic-a')).toBeInTheDocument();
     await user.type(screen.getByPlaceholderText('搜索操作人或操作对象'), 'topic-a');
+    await waitFor(() =>
+      expect(opsService.listAuditRecords).toHaveBeenLastCalledWith(
+        expect.objectContaining({ search: 'topic-a' }),
+      ),
+    );
     await user.click(screen.getByRole('button', { name: /导出/ }));
 
     await waitFor(() =>
@@ -203,6 +216,38 @@ describe('Audit page', () => {
     expect(container.querySelector('.ant-spin-spinning')).not.toBeNull();
   });
 
+  it('ignores stale filter-option responses after cleanup refreshes', async () 
=> {
+    const user = userEvent.setup();
+    const staleOptions =
+      deferred<Awaited<ReturnType<typeof opsService.getAuditFilterOptions>>>();
+    vi.mocked(opsService.getAuditFilterOptions)
+      .mockImplementationOnce(() => new Promise(() => {}))
+      .mockImplementationOnce(() => staleOptions.promise);
+    vi.mocked(opsService.cleanupAuditLogs).mockResolvedValue(3);
+
+    renderWithProviders(<AuditPage />);
+
+    expect(await screen.findByText('topic-a')).toBeInTheDocument();
+    await user.click(screen.getByRole('button', { name: /清理日志/ }));
+    await user.click(await screen.findByRole('button', { name: /确认清理/ }));
+
+    await waitFor(() =>
+      expect(opsService.getAuditFilterOptions).toHaveBeenNthCalledWith(2),
+    );
+
+    await act(async () => {
+      staleOptions.resolve({
+        operationTypes: ['STALE_OPERATION'],
+        resourceTypes: [],
+        clusterIds: [],
+        results: [],
+      });
+    });
+
+    await user.click(screen.getByRole('combobox', { name: '操作类型' }));
+    expect(await screen.findByText('STALE OPERATION')).toBeInTheDocument();
+  });
+
   it('still loads audit records when filter options cannot be loaded', async 
() => {
     vi.mocked(opsService.getAuditFilterOptions).mockRejectedValueOnce(new 
Error('unavailable'));
 
diff --git a/web/src/pages/ops/audit.tsx b/web/src/pages/ops/audit.tsx
index 0884db083..98ca6cca8 100644
--- a/web/src/pages/ops/audit.tsx
+++ b/web/src/pages/ops/audit.tsx
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import { useEffect, useState } from 'react';
+import { useEffect, useMemo, useRef, useState } from 'react';
 import {
   Card,
   Table,
@@ -100,21 +100,21 @@ const AuditPage: React.FC = () => {
   const [cleanupModalOpen, setCleanupModalOpen] = useState(false);
   const [cleanupDays, setCleanupDays] = useState(30);
   const [exporting, setExporting] = useState(false);
+  const recordsRequestRef = useRef(0);
+  const filterOptionsRequestRef = useRef(0);
 
   useEffect(() => {
-    let cancelled = false;
+    const requestId = ++filterOptionsRequestRef.current;
 
     void getAuditFilterOptions()
       .then((options) => {
-        if (!cancelled) setFilterOptions(options);
+        if (filterOptionsRequestRef.current === requestId) 
setFilterOptions(options);
       })
       .catch(() => {
-        if (!cancelled) setFilterOptions(emptyFilterOptions);
+        if (filterOptionsRequestRef.current === requestId) {
+          setFilterOptions(emptyFilterOptions);
+        }
       });
-
-    return () => {
-      cancelled = true;
-    };
   }, [refreshKey]);
 
   // Debounce free-text search so the record list is not re-fetched on every
@@ -125,9 +125,9 @@ const AuditPage: React.FC = () => {
   }, [searchText]);
 
   useEffect(() => {
-    let cancelled = false;
+    const requestId = ++recordsRequestRef.current;
     void Promise.resolve().then(() => {
-      if (!cancelled) setLoading(true);
+      if (recordsRequestRef.current === requestId) setLoading(true);
     });
 
     void listAuditRecords({
@@ -143,20 +143,22 @@ const AuditPage: React.FC = () => {
       ),
     })
       .then((result) => {
-        if (cancelled) return;
+        if (recordsRequestRef.current !== requestId) return;
         setRecords(result.items);
         setTotal(result.total);
+        if (result.items.length === 0 && result.total > 0 && page > 1) {
+          setPage(Math.max(1, Math.ceil(result.total / pageSize)));
+          return;
+        }
       })
       .catch(() => {
-        if (!cancelled) message.error('审计日志加载失败,请稍后重试');
+        if (recordsRequestRef.current === requestId) {
+          message.error('审计日志加载失败,请稍后重试');
+        }
       })
       .finally(() => {
-        if (!cancelled) setLoading(false);
+        if (recordsRequestRef.current === requestId) setLoading(false);
       });
-
-    return () => {
-      cancelled = true;
-    };
   }, [
     page,
     pageSize,
@@ -169,6 +171,34 @@ const AuditPage: React.FC = () => {
     refreshKey,
   ]);
 
+  useEffect(
+    () => () => {
+      recordsRequestRef.current += 1;
+      filterOptionsRequestRef.current += 1;
+    },
+    [],
+  );
+
+  const activeFilter = useMemo(
+    () =>
+      buildAuditFilter(
+        debouncedSearchText,
+        selectedType,
+        selectedResourceType,
+        selectedClusterId,
+        dateRange,
+        resultFilter,
+      ),
+    [
+      debouncedSearchText,
+      selectedType,
+      selectedResourceType,
+      selectedClusterId,
+      dateRange,
+      resultFilter,
+    ],
+  );
+
   const { Text } = Typography;
 
   const handleCleanup = async () => {
@@ -186,16 +216,7 @@ const AuditPage: React.FC = () => {
   const handleExport = async () => {
     setExporting(true);
     try {
-      const csv = await exportAuditLogs(
-        buildAuditFilter(
-          searchText,
-          selectedType,
-          selectedResourceType,
-          selectedClusterId,
-          dateRange,
-          resultFilter,
-        ),
-      );
+      const csv = await exportAuditLogs(activeFilter);
       const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
       downloadBlob(blob, 
`rocketmq-audit-logs-${dayjs().format('YYYY-MM-DD')}.csv`);
     } catch {

Reply via email to