From d02e84f71fa6fad5230c5a6f311f05bb83645350 Mon Sep 17 00:00:00 2001 From: Elegy233 <82034747+wan0ge@users.noreply.github.com> Date: Sat, 20 Jun 2026 13:33:10 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20searchCache/commentCache=20=E5=86=99?= =?UTF-8?q?=E5=85=A5=E6=97=B6=E6=B8=85=E6=89=AB=E8=BF=87=E6=9C=9F=E6=9D=A1?= =?UTF-8?q?=E7=9B=AE=E5=B9=B6=E9=99=90=E5=88=B6=E5=A4=A7=E5=B0=8F=E4=B8=8A?= =?UTF-8?q?=E9=99=90=20(#345)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cache-util.js: - 新增 sweepExpiredCache(),写入前按 TTL 遍历删除过期条目 - setSearchCache / setCommentCache 写入后检查 Map.size,超过 500 则移除最早条目 作为 TTL 清扫后的安全兜底 globals.js: - getConfig() Proxy 实例懒初始化后缓存复用,不再每次新建 cookie-util.js: - handleQRCheck 增加 finally 块,检查完成后清理 qrLoginSessions --- danmu_api/configs/globals.js | 8 +++++-- danmu_api/utils/cache-util.js | 39 ++++++++++++++++++++++++++++++++++ danmu_api/utils/cookie-util.js | 8 ++++++- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/danmu_api/configs/globals.js b/danmu_api/configs/globals.js index 1b66879..42d2808 100644 --- a/danmu_api/configs/globals.js +++ b/danmu_api/configs/globals.js @@ -152,12 +152,14 @@ export const Globals = { */ /** * 获取全局配置对象(单例,可修改) + * 使用 Proxy 保持接口兼容性,实例懒初始化后缓存复用避免每次新建 * @returns {Object} 全局配置对象本身 */ getConfig() { - // 使用 Proxy 保持接口兼容性 + if (this._configProxy) return this._configProxy; + const self = this; - return new Proxy({}, { + this._configProxy = new Proxy({}, { get(target, prop) { // 优先返回 envs 中的属性(保持原有的平铺效果) if (prop in self.envs) { @@ -186,6 +188,8 @@ export const Globals = { return true; } }); + + return this._configProxy; }, }; diff --git a/danmu_api/utils/cache-util.js b/danmu_api/utils/cache-util.js index 9b6d5af..f5c7208 100644 --- a/danmu_api/utils/cache-util.js +++ b/danmu_api/utils/cache-util.js @@ -104,6 +104,25 @@ function getActiveSearchCacheEntries() { return activeEntries; } +// 清除 Map 中所有已超过 TTL 的过期条目 +// 使用 Map 自身的迭代器遍历并删除,不依赖 getActiveSearchCacheEntries 的返回值 +function sweepExpiredCache(cacheMap, cacheMinutes, cacheName) { + const now = Date.now(); + let sweptCount = 0; + + for (const [key, entry] of cacheMap.entries()) { + const ageMinutes = (now - entry.timestamp) / (1000 * 60); + if (ageMinutes > cacheMinutes) { + cacheMap.delete(key); + sweptCount++; + } + } + + if (sweptCount > 0) { + log("info", `[Cache] ${cacheName} TTL 扫描完毕,已移除 ${sweptCount} 个过期条目,剩余 ${cacheMap.size} 个`); + } +} + function matchesAnimeId(anime, idStr) { return String(anime?.animeId) === idStr || String(anime?.bangumiId) === idStr; } @@ -279,12 +298,22 @@ export function getSearchCache(keyword, detailsMap = null) { export function setSearchCache(keyword, results, detailsMap = null) { const details = collectUniqueAnimeDetails(detailsMap); + // 写入前先清理所有过期条目 + sweepExpiredCache(globals.searchCache, globals.searchCacheMinutes, 'searchCache'); + globals.searchCache.set(keyword, { results: results, details: details, timestamp: Date.now() }); + // TTL 清理后仍未降回上限,则按插入顺序移除最早条目作为安全兜底 + if (globals.searchCache.size > 500) { + const oldestKey = globals.searchCache.keys().next().value; + globals.searchCache.delete(oldestKey); + log("info", `[Cache] searchCache TTL清理后仍达上限,已移除最早条目: ${oldestKey}`); + } + log("info", `[Cache] Cached search results for "${keyword}" (${results.length} animes)`); } @@ -319,11 +348,21 @@ export function getCommentCache(videoUrl) { // 设置弹幕缓存 export function setCommentCache(videoUrl, comments) { + // 写入前先清理所有过期条目 + sweepExpiredCache(globals.commentCache, globals.commentCacheMinutes, 'commentCache'); + globals.commentCache.set(videoUrl, { comments: comments, timestamp: Date.now() }); + // TTL 清理后仍未降回上限,则按插入顺序移除最早条目作为安全兜底 + if (globals.commentCache.size > 500) { + const oldestKey = globals.commentCache.keys().next().value; + globals.commentCache.delete(oldestKey); + log("info", `[Cache] commentCache TTL清理后仍达上限,已移除最早条目: ${oldestKey}`); + } + log("info", `[Cache] Cached comments for "${videoUrl}" (${comments.length} comments)`); } diff --git a/danmu_api/utils/cookie-util.js b/danmu_api/utils/cookie-util.js index ff68d51..66146b4 100644 --- a/danmu_api/utils/cookie-util.js +++ b/danmu_api/utils/cookie-util.js @@ -327,9 +327,10 @@ function extractLoginCookieFromResponse(response) { * 检查二维码扫描状态 */ export async function handleQRCheck(request) { + let qrcodeKey = null; try { const body = await request.json(); - const qrcodeKey = body.qrcodeKey || body.qrcode_key; + qrcodeKey = body.qrcodeKey || body.qrcode_key; if (!qrcodeKey) { return jsonResponse({ success: false, message: '缺少qrcodeKey参数' }, 400); @@ -389,6 +390,11 @@ export async function handleQRCheck(request) { return jsonResponse(result); } catch (error) { return jsonResponse({ success: false, message: error.message }, 500); + } finally { + // 检查完成后清理会话记录,防止长期运行后内存累积 + if (qrcodeKey) { + qrLoginSessions.delete(qrcodeKey); + } } }