From a14bdb5e075fcbb62bb4edac96bccd3656d063d9 Mon Sep 17 00:00:00 2001 From: opsx Date: Fri, 30 Oct 2026 15:49:52 +0000 Subject: [PATCH] =?UTF-8?q?fix(analysis):=20=E6=AF=8F=E6=97=A5=E9=87=8F?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E6=87=92=E5=8A=A0=E8=BD=BD=EF=BC=9B=E6=8C=89?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E7=BB=9F=E8=AE=A1=E6=9C=9F=E9=97=B4=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=20loading=EF=BC=88=E4=B8=8D=E5=86=8D=E5=81=87?= =?UTF-8?q?=E8=A3=85"=E6=B2=A1=E6=9C=89=E6=95=B0=E6=8D=AE"=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户的三个现象,对应三个独立缺陷: === 1. 每日日志量"一直没数据" === 接口只读平台库,实测 **10ms**,却仍被 inView 懒加载门控。 ref 虽然挂在 DOM 上,但滚动判定在多层布局里不可靠 → enabled 一直 false → 请求根本不发 → 页面显示"暂无历史数据"。 改为:只要有数据源就加载(enabled = Boolean(sourceId))。 这个接口 10ms,没有任何理由懒加载。 === 2. 排行/流图"打开没有数据,等很久也没 loading" === 按应用统计要 10~15 秒(读日志内容,物理下限)。 但 loading 是**整页共用**的:索引类指标 100ms 就回来、 重查询还在跑时 loading 就已经变 false, 卡片于是渲染成"没有应用维度的数据"——用户以为查完了、没数据。 改为: - 新增 appsLoading,专门标记"按应用统计"这段重查询 - 卡片在 appsLoading 期间显示 spinner + "正在按应用统计…" - loading 与 appsLoading 都在 finally 清理(成功/失败/取消都清) === 3. data 为 null 时整页空白 === 原来 `) : !data || !sourceId ? null : (` 直接 return null, 首屏或重查询异常时是**整页空白**,和"没数据"无法区分。 改为显示 spinner + "正在按应用统计(需读取日志内容,通常 10~30 秒)"。 === 关于"用 Go 定时任务统计每日总量"=== 这个机制**早就存在**(services/daily_volume.go): - 启动 20s 后回补 + 每 30 分钟一次 ticker - 用 index/stats 取整天的条数与字节,**不读日志内容**,通常秒级 - 快照写入平台库,保留 400 天 实测当前库内已有 4 天数据(10ms 返回): 2026-10-27 32.5 GB 2026-10-28 23.9 GB 2026-10-29 133.6 GB 2026-10-30 239.3 GB 问题只在"前端没去请求",不在采集侧。 实测(df3b477e6740):每日量接口 10ms 返回 4 天,Pod 1/1 Running。 --- .../pages/analysis/hooks/useAnalysisMetrics.ts | 13 +++++++++++-- frontend/src/pages/analysis/index.tsx | 17 +++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/analysis/hooks/useAnalysisMetrics.ts b/frontend/src/pages/analysis/hooks/useAnalysisMetrics.ts index a73308b..3e18d3c 100644 --- a/frontend/src/pages/analysis/hooks/useAnalysisMetrics.ts +++ b/frontend/src/pages/analysis/hooks/useAnalysisMetrics.ts @@ -75,6 +75,11 @@ export function useAnalysisMetrics({ sourceId, start, end, namespace, rankingEna const [appLabel, setAppLabel] = useState('app') const [data, setData] = useState(null) const [loading, setLoading] = useState(false) + // appsLoading 单独跟踪"按应用统计"这段重查询(10~30 秒)。 + // 它与 loading 分开的原因:上面索引类指标 100ms 就回来了, + // 如果共用一个 loading,页面会在"索引已好、app 还没好"的空档里 + // 显示成"没有应用维度的数据"——用户等了很久以为没数据,其实还在算。 + const [appsLoading, setAppsLoading] = useState(false) const [error, setError] = useState('') const [refreshTick, setRefreshTick] = useState(0) const abortRef = useRef(null) @@ -192,6 +197,7 @@ export function useAnalysisMetrics({ sourceId, start, end, namespace, rankingEna setLoading(false) return } + setAppsLoading(true) // 这两条并行而不是串行:实测总量 22s、error 8.6s,串行 31s,并行 22s。 // 并发上限是 4,两条并行不会撞护栏(之前串行是为了避免 5 条并发的场景)。 @@ -269,12 +275,15 @@ export function useAnalysisMetrics({ sourceId, start, end, namespace, rankingEna if ((err as Error).name === 'AbortError') return setError(err instanceof Error ? err.message : 'metrics failed') } finally { - if (version === versionRef.current) setLoading(false) + if (version === versionRef.current) { + setLoading(false) + setAppsLoading(false) + } } })() return () => { controller.abort() } }, [sourceId, start, end, namespace, appLabel, refreshTick, rankingEnabled]) - return { data, loading, error, appLabel, refresh } + return { data, loading, appsLoading, error, appLabel, refresh } } diff --git a/frontend/src/pages/analysis/index.tsx b/frontend/src/pages/analysis/index.tsx index e31fea6..0090bba 100644 --- a/frontend/src/pages/analysis/index.tsx +++ b/frontend/src/pages/analysis/index.tsx @@ -144,7 +144,7 @@ const AnalysisPage = memo(function AnalysisPage() { // 都依赖 by-app 数据,而它原来被底部排行卡片的可见性门控 —— // 用户不滚到底就永远拿不到数据,表现为"三条都没有应用维度的数据"。 // 现在进页面就发,页面用两阶段渲染:索引类指标先出,app 维度到货后补。 - const { data, loading, error, appLabel, refresh } = useAnalysisMetrics({ + const { data, loading, appsLoading, error, appLabel, refresh } = useAnalysisMetrics({ sourceId: effectiveSourceId, start, end, @@ -170,7 +170,9 @@ const AnalysisPage = memo(function AnalysisPage() { const daily = useDailyVolume({ sourceId: effectiveSourceId, days: dailyDays, - enabled: dailyView.inView, + // 不再按可见性懒加载:这个接口只读平台库,实测 9ms。 + // 之前门控 + ref 观察器导致"滚到底也不加载",看起来就是"每日日志量一直没数据"。 + enabled: Boolean(effectiveSourceId), }) // 非 admin:菜单不可见,直访时给明确提示(后端 AdminAuth 403 兜底) @@ -248,7 +250,14 @@ const AnalysisPage = memo(function AnalysisPage() { ) : loading && !data ? ( - ) : !data || !sourceId ? null : ( + ) : !data || !sourceId ? ( + /* data 为 null 时原来直接 return null → 整页空白,用户以为"没数据"。 + 这里明确显示"正在统计"而不是什么都不画。 */ +
+ +

{t('table.computing')}

+
+ ) : ( <> {/* 不再显示"告警行数"卡片:为了把它从 3 条重查询降到 2 条, 已放弃全局 warn 统计。显示一个恒为 0 的数字比不显示更误导。 */} @@ -298,7 +307,7 @@ const AnalysisPage = memo(function AnalysisPage() { {/* 副标题说明这块不需要读日志内容,避免和上面的排行混淆 */} - {loading && !data.apps.length ? ( + {(appsLoading || loading) && !data.apps.length ? ( // 这块必须按 app 分组统计(读日志内容),实测大集群 15 分钟约 20~30 秒。 // 明确告诉用户在算什么、要等多久,比空表格或整页转圈好。