From 800a92b419850237d530bd1b39a1ea89a12d05e7 Mon Sep 17 00:00:00 2001 From: xiyidaiwa Date: Mon, 10 Aug 2026 17:19:55 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A4=84=E7=90=86=20PR=20=E5=AE=A1?= =?UTF-8?q?=E6=9F=A5=E6=84=8F=E8=A7=81=EF=BC=88=E7=AB=9E=E6=80=81/?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=AE=89=E5=85=A8/=E5=86=97=E4=BD=99/?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=B3=84=E9=9C=B2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - App.vue fetchAppFromStore 的 fetch 接入 rootAbortController.signal, 组件卸载时取消请求,避免竞态与内存泄漏;catch 静默处理 AbortError。 - App.vue loadHome 远程字段由 `as string` 断言改为 typeof 运行时守卫, 避免非字符串数据注入响应式状态(非崩溃,属改进)。 - ranking.ts topUpdatedContributors 移除对 topByUpdate 已过滤结果的冗余 filter。 - storeConfig 加载成功日志改为仅打印规则条数,避免完整配置泄露且保留诊断价值。 - 未采纳:parseAppPayload 默认 origin=spark 维持不变(spark 为历史默认源, 盲改 apm 有反向误路由风险,且该字符串分支仅为极旧格式兜底)。 --- src/App.vue | 37 ++++++++++++++++++++++++++++++------- src/global/storeConfig.ts | 11 +++++++++-- src/modules/ranking.ts | 5 ++--- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/App.vue b/src/App.vue index 7001edc1..5283413e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -901,7 +901,11 @@ const fetchAppFromStore = async ( const appJsonUrl = `${APM_STORE_BASE_URL}/${finalArch}/${encodeURIComponent( category, )}/${encodeURIComponent(pkgname)}/app.json`; - const response = await fetch(appJsonUrl); + // 接入 rootAbortController.signal,确保组件卸载/详情关闭时请求可被取消, + // 避免竞态与内存泄漏(onUnmounted 会 abort 该 controller) + const response = await fetch(appJsonUrl, { + signal: rootAbortController.signal, + }); if (!response.ok) return null; const appJson = await response.json(); // img_urls 可能为字符串形式的 JSON,解析失败时安全回退为空数组 @@ -935,6 +939,8 @@ const fetchAppFromStore = async ( currentStatus: "not-installed", }; } catch (e) { + // 组件卸载/详情关闭触发 abort 时静默返回,避免刷 AbortError 日志 + if ((e as Error)?.name === "AbortError") return null; console.warn(`Failed to fetch ${origin} app info for ${pkgname}`, e); return null; } @@ -1398,23 +1404,40 @@ const loadHome = async () => { >[]) : []; for (const l of links) { - const name = (l.Name as string) || (l.name as string) || ""; + // 远程数据不可信,使用 typeof 运行时守卫替代 `as string` 断言, + // 避免非字符串字段(如数字/对象)被注入状态导致下游显示异常。 + const name = + typeof l.Name === "string" + ? l.Name + : typeof l.name === "string" + ? l.name + : ""; if (!name) continue; // 跳过空名称,避免空字符串污染 seenNames 与去重逻辑 if (seenNames.has(name)) continue; // 已由更高优先级来源(spark)占据 // 仅校验 url 必需;远程 homelinks.json 不含 icon 字段(图片由 imgUrl 提供), // 故 icon 不作为硬性校验,缺省为空串以兼容 HomeLink 类型。 - const url = (l.Url as string) || (l.url as string) || ""; + const url = + typeof l.Url === "string" + ? l.Url + : typeof l.url === "string" + ? l.url + : ""; if (!url) continue; - const icon = (l.Icon as string) || (l.icon as string) || ""; + const icon = + typeof l.Icon === "string" + ? l.Icon + : typeof l.icon === "string" + ? l.icon + : ""; seenNames.add(name); // 显式提取已知字段构造,避免通过展开运算符 { ...l } 把远程不可信数据中的未知属性注入响应式状态 const safeLink: HomeLink = { name, url, icon, - more: (l.more as string) || undefined, - imgUrl: (l.imgUrl as string) || undefined, - type: (l.type as string) || undefined, + more: typeof l.more === "string" ? l.more : undefined, + imgUrl: typeof l.imgUrl === "string" ? l.imgUrl : undefined, + type: typeof l.type === "string" ? l.type : undefined, origin: mode, }; homeLinks.value.push(safeLink); diff --git a/src/global/storeConfig.ts b/src/global/storeConfig.ts index f7e23a38..7b1a6a51 100644 --- a/src/global/storeConfig.ts +++ b/src/global/storeConfig.ts @@ -113,9 +113,16 @@ export async function loadPriorityConfig(arch: string): Promise { }, }; } + // 仅打印规则计数而非完整配置,避免潜在敏感信息泄露(诊断用) + const ruleCount = (r: { + pkgnames: string[]; + categories: string[]; + tags: string[]; + }) => r.pkgnames.length + r.categories.length + r.tags.length; console.log( - "[PriorityConfig] 已从服务器加载优先级配置:", - JSON.stringify(dynamicPriorityConfig), + `[PriorityConfig] 已从服务器加载优先级配置: spark ${ruleCount( + dynamicPriorityConfig.sparkPriority, + )} 条, apm ${ruleCount(dynamicPriorityConfig.apmPriority)} 条`, ); } catch (error) { // 获取失败(含 404:服务器无配置文件),默认优先 APM。 diff --git a/src/modules/ranking.ts b/src/modules/ranking.ts index fbac423e..aa71db47 100644 --- a/src/modules/ranking.ts +++ b/src/modules/ranking.ts @@ -83,8 +83,7 @@ export function topUpdatedContributors( recentN: number = RECENT_UPDATE_WINDOW, topN: number = TOP_N, ): ContributorRank[] { - const recent = topByUpdate(apps, origin, recentN).filter( - (a) => a.origin === origin, - ); + // topByUpdate 内部已按 origin 过滤,无需再次 filter + const recent = topByUpdate(apps, origin, recentN); return toRanks(countContributors(recent), topN); }