feat: executeSourceHandlers 并发化执行 handleAnimes (#331)

* feat: executeSourceHandlers 并发化执行 handleAnimes

将 executeSourceHandlers 从串行 await 改为并发 Promise.allSettled 执行:
- 每个源分配独立的 isolatedAnimes 和 isolatedDetailStore 容器
- 所有源的 handleAnimes 并发执行,互不干扰
- 完成后按 SOURCE_ORDER 顺序合并结果,animeId/detailStore 键去重时先到先得
- 单个源失败不影响其他源(Promise.allSettled 容错)

* Update dandan-api.js
This commit is contained in:
Elegy233 authored and GitHub committed 2026-05-31 12:56:06 +08:00
1 parent ad303b1b8e
commit a94256c591
1 file changed
+88 -48
+88 -48
View File
@@ -304,6 +304,7 @@ function checkEpisodeSatisfied(animesList, querySeason, queryEpisode, requestAni
/**
* 执行配置数据源的并发请求与解析逻辑
* 负责将获取的源站数据映射、过滤,并存入目标季度的动漫结果集合中
* 各源并发执行 handleAnimes,完成后按 SOURCE_ORDER 顺序合并结果,确保优先级
* @param {Object} resultData 并发请求的原始返回数据集
* @param {string} queryTitle 搜索关键词
* @param {Array} targetAnimesList 目标存储列表
@@ -315,83 +316,122 @@ function checkEpisodeSatisfied(animesList, querySeason, queryEpisode, requestAni
async function executeSourceHandlers(resultData, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason, preferAnimeId = null, preferSource = null) {
const {
vod: animesVodResults, 360: animes360, tmdb: animesTmdb, douban: animesDouban, renren: animesRenren,
hanjutv: animesHanjutv, bahamut: animesBahamut, dandan: animesDandan, custom: animesCustom,
hanjutv: animesHanjutv, bahamut: animesBahamut, dandan: animesDandan, custom: animesCustom,
tencent: animesTencent, youku: animesYouku, iqiyi: animesIqiyi, imgo: animesImgo, bilibili: animesBilibili,
migu: animesMigu, sohu: animesSohu, leshi: animesLeshi, xigua: animesXigua, maiduidui: animesMaiduidui,
migu: animesMigu, sohu: animesSohu, leshi: animesLeshi, xigua: animesXigua, maiduidui: animesMaiduidui,
aiyifan: animesAiyifan, animeko: animesAnimeko
} = resultData;
// 按顺序处理每个来源的结果
// 源Key到handleAnimes调用Promise的映射(每个源使用独立的curAnimes和detailStore)
const sourceTasks = [];
for (const key of globals.sourceOrderArr) {
const isolatedAnimes = [];
const isolatedDetailStore = new Map();
if (key === '360') {
// 等待处理360来源
await kan360Source.handleAnimes(animes360, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理360来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: kan360Source.handleAnimes(animes360, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'vod') {
// 等待处理Vod来源(遍历所有VOD服务器的结果)
// 处理Vod来源(遍历所有VOD服务器的结果,依次在同一隔离容器中处理)
if (animesVodResults && Array.isArray(animesVodResults)) {
for (const vodResult of animesVodResults) {
if (vodResult && vodResult.list && vodResult.list.length > 0) {
await vodSource.handleAnimes(vodResult.list, queryTitle, targetAnimesList, vodResult.serverName, requestAnimeDetailsMap, targetSeason);
const vodPromise = (async () => {
for (const vodResult of animesVodResults) {
if (vodResult && vodResult.list && vodResult.list.length > 0) {
await vodSource.handleAnimes(vodResult.list, queryTitle, isolatedAnimes, vodResult.serverName, isolatedDetailStore, targetSeason);
}
}
}
})();
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: vodPromise });
}
} else if (key === 'tmdb') {
// 等待处理TMDB来源
await tmdbSource.handleAnimes(animesTmdb, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理TMDB来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: tmdbSource.handleAnimes(animesTmdb, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'douban') {
// 等待处理Douban来源
await doubanSource.handleAnimes(animesDouban, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Douban来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: doubanSource.handleAnimes(animesDouban, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'renren') {
// 等待处理Renren来源
await renrenSource.handleAnimes(animesRenren, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Renren来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: renrenSource.handleAnimes(animesRenren, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'hanjutv') {
// 等待处理Hanjutv来源
await hanjutvSource.handleAnimes(animesHanjutv, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Hanjutv来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: hanjutvSource.handleAnimes(animesHanjutv, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'bahamut') {
// 等待处理Bahamut来源
await bahamutSource.handleAnimes(animesBahamut, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Bahamut来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: bahamutSource.handleAnimes(animesBahamut, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'dandan') {
// 等待处理弹弹play来源
await dandanSource.handleAnimes(animesDandan, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理弹弹play来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: dandanSource.handleAnimes(animesDandan, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'custom') {
// 等待处理自定义弹幕源来源
await customSource.handleAnimes(animesCustom, queryTitle, targetAnimesList);
// 处理自定义弹幕源来源(handleAnimes签名不含detailStore和querySeason)
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: customSource.handleAnimes(animesCustom, queryTitle, isolatedAnimes) });
} else if (key === 'tencent') {
// 等待处理Tencent来源
await tencentSource.handleAnimes(animesTencent, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Tencent来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: tencentSource.handleAnimes(animesTencent, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'youku') {
// 等待处理Youku来源
await youkuSource.handleAnimes(animesYouku, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Youku来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: youkuSource.handleAnimes(animesYouku, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'iqiyi') {
// 等待处理iQiyi来源
await iqiyiSource.handleAnimes(animesIqiyi, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理iQiyi来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: iqiyiSource.handleAnimes(animesIqiyi, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'imgo') {
// 等待处理Mango来源
await mangoSource.handleAnimes(animesImgo, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Mango来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: mangoSource.handleAnimes(animesImgo, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'bilibili') {
// 等待处理Bilibili来源
await bilibiliSource.handleAnimes(animesBilibili, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Bilibili来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: bilibiliSource.handleAnimes(animesBilibili, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'migu') {
// 等待处理Migu来源
await miguSource.handleAnimes(animesMigu, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Migu来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: miguSource.handleAnimes(animesMigu, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'sohu') {
// 等待处理Sohu来源
await sohuSource.handleAnimes(animesSohu, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Sohu来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: sohuSource.handleAnimes(animesSohu, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'leshi') {
// 等待处理Leshi来源
await leshiSource.handleAnimes(animesLeshi, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Leshi来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: leshiSource.handleAnimes(animesLeshi, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'xigua') {
// 等待处理Xigua来源
await xiguaSource.handleAnimes(animesXigua, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Xigua来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: xiguaSource.handleAnimes(animesXigua, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'maiduidui') {
// 等待处理Maiduidui来源
await maiduiduiSource.handleAnimes(animesMaiduidui, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Maiduidui来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: maiduiduiSource.handleAnimes(animesMaiduidui, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'aiyifan') {
// 等待处理Aiyifan来源
await aiyifanSource.handleAnimes(animesAiyifan, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Aiyifan来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: aiyifanSource.handleAnimes(animesAiyifan, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
} else if (key === 'animeko') {
// 等待处理Animeko来源
await animekoSource.handleAnimes(animesAnimeko, queryTitle, targetAnimesList, requestAnimeDetailsMap, targetSeason);
// 处理Animeko来源
sourceTasks.push({ key, animes: isolatedAnimes, detailStore: isolatedDetailStore, promise: animekoSource.handleAnimes(animesAnimeko, queryTitle, isolatedAnimes, isolatedDetailStore, targetSeason) });
}
}
// 并发执行所有源的handleAnimes
const results = await Promise.allSettled(sourceTasks.map(task => task.promise));
// 按SOURCE_ORDER顺序合并各源的独立结果到目标容器
// 先处理的源数据优先保留(animeId去重、detailStore键去重)
const existingAnimeIds = new Set(targetAnimesList.map(a => a.animeId));
for (let i = 0; i < sourceTasks.length; i++) {
if (results[i].status === 'rejected') {
log("error", `[executeSourceHandlers] 源 ${sourceTasks[i].key} 处理失败: ${results[i].reason}`);
continue;
}
const { animes: isolatedAnimes, detailStore: isolatedDetailStore } = sourceTasks[i];
// 合并动漫结果列表(使用 Set 确保 O(1) 检索,优先源先入为主)
for (const anime of isolatedAnimes) {
if (!existingAnimeIds.has(anime.animeId)) {
targetAnimesList.push(anime);
existingAnimeIds.add(anime.animeId);
}
}
// 合并详情缓存(键去重,先到先得)
for (const [key, value] of isolatedDetailStore) {
if (!requestAnimeDetailsMap.has(key)) {
requestAnimeDetailsMap.set(key, value);
}
}
}
}