fix: searchCache/commentCache 写入时清扫过期条目并限制大小上限 (#345)

cache-util.js:
- 新增 sweepExpiredCache(),写入前按 TTL 遍历删除过期条目
- setSearchCache / setCommentCache 写入后检查 Map.size,超过 500 则移除最早条目
  作为 TTL 清扫后的安全兜底

globals.js:
- getConfig() Proxy 实例懒初始化后缓存复用,不再每次新建

cookie-util.js:
- handleQRCheck 增加 finally 块,检查完成后清理 qrLoginSessions
This commit is contained in:
Elegy233 authored and GitHub committed 2026-06-20 13:33:10 +08:00
1 parent 91e563ab5c
commit d02e84f71f
3 files changed
+52 -3

No files matched your search

+6 -2
View File
@@ -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;
},
};
+39
View File
@@ -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)`);
}
+7 -1
View File
@@ -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);
}
}
}