修复剧集搜索详情丢失 (#242)
变更内容:为 searchEpisodes 增加请求级详情缓存,避免广义搜索结果被全局 100 条上限裁剪后丢失 变更内容:扩展 searchAnime 的搜索缓存结构,在命中缓存时同步回填详情数据 变更内容:抽取 bangumi 详情构建逻辑并复用,保持 searchAnime 和 searchEpisodes 返回结果一致
This commit is contained in:
1 parent
faa570273f
commit
7cff1c991b
3 files changed
+111
-47
No files matched your search
@@ -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
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,7 @@ export const Globals = {
|
||||
episodeNum: 10001, // 全局变量,用于自增 ID
|
||||
logBuffer: [],
|
||||
requestHistory: new Map(), // 记录每个 IP 地址的请求历史
|
||||
requestAnimeDetailsMap: null, // 当前请求内的详情缓存,避免搜索结果被全局上限裁剪后丢失
|
||||
localCacheValid: false, // 本地缓存是否生效
|
||||
localCacheInitialized: false, // 本地缓存是否已初始化
|
||||
redisValid: false, // redis是否生效
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in new issue
Block a user