From 7cff1c991be5c1c4f6e2dec0e85af21c6985f84f Mon Sep 17 00:00:00 2001 From: Celestials316 Date: Wed, 18 Mar 2026 23:39:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=89=A7=E9=9B=86=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E8=AF=A6=E6=83=85=E4=B8=A2=E5=A4=B1=20(#242)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 变更内容:为 searchEpisodes 增加请求级详情缓存,避免广义搜索结果被全局 100 条上限裁剪后丢失 变更内容:扩展 searchAnime 的搜索缓存结构,在命中缓存时同步回填详情数据 变更内容:抽取 bangumi 详情构建逻辑并复用,保持 searchAnime 和 searchEpisodes 返回结果一致 --- danmu_api/apis/dandan-api.js | 129 ++++++++++++++++++++++------------ danmu_api/configs/globals.js | 1 + danmu_api/utils/cache-util.js | 28 +++++++- 3 files changed, 111 insertions(+), 47 deletions(-) diff --git a/danmu_api/apis/dandan-api.js b/danmu_api/apis/dandan-api.js index 2d8bb2c..0e879aa 100644 --- a/danmu_api/apis/dandan-api.js +++ b/danmu_api/apis/dandan-api.js @@ -232,18 +232,28 @@ export async function searchAnime(url, preferAnimeId = null, preferSource = null queryTitle = simplifiedTitle; } - // 检查搜索缓存 - const cachedResults = getSearchCache(queryTitle); - if (cachedResults !== null) { - return jsonResponse({ - errorCode: 0, - success: true, - errorMessage: "", - animes: cachedResults, - }); + const previousRequestAnimeDetailsMap = globals.requestAnimeDetailsMap; + const requestAnimeDetailsMap = previousRequestAnimeDetailsMap instanceof Map + ? previousRequestAnimeDetailsMap + : new Map(); + + if (!(previousRequestAnimeDetailsMap instanceof Map)) { + globals.requestAnimeDetailsMap = requestAnimeDetailsMap; } - const curAnimes = []; + try { + // 检查搜索缓存 + const cachedResults = getSearchCache(queryTitle, requestAnimeDetailsMap); + if (cachedResults !== null) { + return jsonResponse({ + errorCode: 0, + success: true, + errorMessage: "", + animes: cachedResults, + }); + } + + const curAnimes = []; // 链接弹幕解析 const urlRegex = /^(https?:\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}(:\d+)?(\/[^\s]*)?$/; @@ -479,29 +489,34 @@ export async function searchAnime(url, preferAnimeId = null, preferSource = null curAnimes.push(...validAnimes); } - // 如果有新的anime获取到,则更新本地缓存 - if (globals.localCacheValid && curAnimes.length !== 0) { - await updateLocalCaches(); - } - // 如果有新的anime获取到,则更新redis - if (globals.redisValid && curAnimes.length !== 0) { - await updateRedisCaches(); - } - if (globals.localRedisValid && curAnimes.length !== 0) { - await updateLocalRedisCaches(); - } + // 如果有新的anime获取到,则更新本地缓存 + if (globals.localCacheValid && curAnimes.length !== 0) { + await updateLocalCaches(); + } + // 如果有新的anime获取到,则更新redis + if (globals.redisValid && curAnimes.length !== 0) { + await updateRedisCaches(); + } + if (globals.localRedisValid && curAnimes.length !== 0) { + await updateLocalRedisCaches(); + } - // 缓存搜索结果 - if (curAnimes.length > 0) { - setSearchCache(queryTitle, curAnimes); - } + // 缓存搜索结果 + if (curAnimes.length > 0) { + setSearchCache(queryTitle, curAnimes, requestAnimeDetailsMap); + } - return jsonResponse({ - errorCode: 0, - success: true, - errorMessage: "", - animes: curAnimes, - }); + return jsonResponse({ + errorCode: 0, + success: true, + errorMessage: "", + animes: curAnimes, + }); + } finally { + if (!(previousRequestAnimeDetailsMap instanceof Map)) { + globals.requestAnimeDetailsMap = previousRequestAnimeDetailsMap; + } + } } function filterSameEpisodeTitle(filteredTmpEpisodes) { @@ -1145,8 +1160,17 @@ export async function searchEpisodes(url) { // 先搜索动漫 let searchUrl = new URL(`/search/anime?keyword=${anime}`, url.origin); - const searchRes = await searchAnime(searchUrl); - const searchData = await searchRes.json(); + const previousRequestAnimeDetailsMap = globals.requestAnimeDetailsMap; + const requestAnimeDetailsMap = new Map(); + globals.requestAnimeDetailsMap = requestAnimeDetailsMap; + + let searchData; + try { + const searchRes = await searchAnime(searchUrl); + searchData = await searchRes.json(); + } finally { + globals.requestAnimeDetailsMap = previousRequestAnimeDetailsMap; + } if (!searchData.success || !searchData.animes || searchData.animes.length === 0) { log("info", "No anime found for the given title"); @@ -1163,9 +1187,20 @@ export async function searchEpisodes(url) { // 遍历所有找到的动漫,获取它们的集数信息 for (const animeItem of searchData.animes) { - const bangumiUrl = new URL(`/bangumi/${animeItem.bangumiId}`, url.origin); - const bangumiRes = await getBangumi(bangumiUrl.pathname); - const bangumiData = await bangumiRes.json(); + const detailAnime = + requestAnimeDetailsMap.get(String(animeItem.bangumiId)) || + requestAnimeDetailsMap.get(String(animeItem.animeId)) || + globals.animes.find(a => String(a.bangumiId) === String(animeItem.bangumiId)) || + globals.animes.find(a => String(a.animeId) === String(animeItem.animeId)); + + let bangumiData = null; + if (detailAnime) { + bangumiData = buildBangumiData(detailAnime, animeItem.bangumiId); + } else { + const bangumiUrl = new URL(`/bangumi/${animeItem.bangumiId}`, url.origin); + const bangumiRes = await getBangumi(bangumiUrl.pathname); + bangumiData = await bangumiRes.json(); + } if (bangumiData.success && bangumiData.bangumi && bangumiData.bangumi.episodes) { let filteredEpisodes = bangumiData.bangumi.episodes; @@ -1241,7 +1276,11 @@ export async function getBangumi(path) { 404 ); } - log("info", `Fetched details for anime ID: ${idParam}`); + return jsonResponse(buildBangumiData(anime, idParam)); +} + +function buildBangumiData(anime, idParam = "") { + log("info", `Fetched details for anime ID: ${idParam || anime.bangumiId}`); // 构建 episodes 列表 let episodesList = []; @@ -1265,11 +1304,13 @@ export async function getBangumi(path) { // 如果过滤后没有有效剧集,返回错误 if (episodesList.length === 0) { - log("warn", `[getBangumi] No valid episodes after filtering for anime ID ${idParam}`); - return jsonResponse( - { errorCode: 404, success: false, errorMessage: "No valid episodes after filtering", bangumi: null }, - 404 - ); + log("warn", `[getBangumi] No valid episodes after filtering for anime ID ${idParam || anime.bangumiId}`); + return { + errorCode: 404, + success: false, + errorMessage: "No valid episodes after filtering", + bangumi: null + }; } // 重新排序episodeNumber @@ -1301,12 +1342,12 @@ export async function getBangumi(path) { episodes: episodesList, }); - return jsonResponse({ + return { errorCode: 0, success: true, errorMessage: "", bangumi: bangumi - }); + }; } /** diff --git a/danmu_api/configs/globals.js b/danmu_api/configs/globals.js index 9be5cb6..84b73c7 100644 --- a/danmu_api/configs/globals.js +++ b/danmu_api/configs/globals.js @@ -24,6 +24,7 @@ export const Globals = { episodeNum: 10001, // 全局变量,用于自增 ID logBuffer: [], requestHistory: new Map(), // 记录每个 IP 地址的请求历史 + requestAnimeDetailsMap: null, // 当前请求内的详情缓存,避免搜索结果被全局上限裁剪后丢失 localCacheValid: false, // 本地缓存是否生效 localCacheInitialized: false, // 本地缓存是否已初始化 redisValid: false, // redis是否生效 diff --git a/danmu_api/utils/cache-util.js b/danmu_api/utils/cache-util.js index 1d509d8..ddff7a8 100644 --- a/danmu_api/utils/cache-util.js +++ b/danmu_api/utils/cache-util.js @@ -48,18 +48,34 @@ export function isSearchCacheValid(keyword) { } // 获取搜索缓存 -export function getSearchCache(keyword) { +export function getSearchCache(keyword, detailsMap = null) { if (isSearchCacheValid(keyword)) { log("info", `Using search cache for "${keyword}"`); - return globals.searchCache.get(keyword).results; + const cached = globals.searchCache.get(keyword); + + if (detailsMap instanceof Map && Array.isArray(cached.details)) { + cached.details.forEach(anime => { + detailsMap.set(String(anime.bangumiId), anime); + detailsMap.set(String(anime.animeId), anime); + }); + } + + return cached.results; } return null; } // 设置搜索缓存 -export function setSearchCache(keyword, results) { +export function setSearchCache(keyword, results, detailsMap = null) { + const details = detailsMap instanceof Map + ? Array.from(new Map( + Array.from(detailsMap.values()).map(anime => [String(anime.bangumiId || anime.animeId), anime]) + ).values()) + : []; + globals.searchCache.set(keyword, { results: results, + details: details, timestamp: Date.now() }); @@ -213,6 +229,12 @@ export function addAnime(anime) { // 创建新的 anime 副本 const animeCopy = Anime.fromJson({ ...anime, links: newLinks }); + // 当前请求内额外保留一份详情,避免被全局数量上限裁剪后丢失 + if (globals.requestAnimeDetailsMap instanceof Map) { + globals.requestAnimeDetailsMap.set(String(animeCopy.bangumiId), animeCopy); + globals.requestAnimeDetailsMap.set(String(animeCopy.animeId), animeCopy); + } + // 检查是否已存在相同 animeId 的 anime const existingAnimeIndex = globals.animes.findIndex(a => a.animeId === anime.animeId);