diff --git a/backend/src/handler/sync_jobs.js b/backend/src/handler/sync_jobs.js index 75e86e5..6c2f530 100644 --- a/backend/src/handler/sync_jobs.js +++ b/backend/src/handler/sync_jobs.js @@ -65,7 +65,7 @@ async function createJob(req, res) { } let meta = {}; - const songId = request.urlJob && request.urlJob.meta.songId ? request.urlJob.meta.songId : ""; + let songId = request.urlJob && request.urlJob.meta.songId ? request.urlJob.meta.songId : ""; logger.info(`[createJob] songId=${songId}`); // 先从 TuneHub 获取歌曲信息,避免显示原始 URL diff --git a/backend/src/service/music_platform/tunehub.js b/backend/src/service/music_platform/tunehub.js index 0f4a565..4ea7d67 100644 --- a/backend/src/service/music_platform/tunehub.js +++ b/backend/src/service/music_platform/tunehub.js @@ -8,16 +8,21 @@ function buildApiUrl(params = {}) { return `${BaseUrl}?${searchParams.toString()}`; } -async function fetchJson(params = {}) { +async function fetchJson(params = {}, attempt = 1) { const url = buildApiUrl(params); - const raw = await asyncHttpsGet(url); + // 重试时增加超时时间 + const timeoutMs = Math.min(30000, 5000 + attempt * 5000); + logger.info(`[TuneHub] Fetching: ${url} (attempt ${attempt}, timeout ${timeoutMs}ms)`); + const raw = await asyncHttpsGet(url, timeoutMs); if (!raw) { + logger.warn(`[TuneHub] No data received for ${url}`); return null; } try { return JSON.parse(raw); } catch (err) { - logger.error(`TuneHub 返回非 JSON: ${url}`); + logger.error(`[TuneHub] Failed to parse JSON from ${url}: ${err.message}`); + logger.error(`[TuneHub] Raw response: ${raw.substring(0, 200)}...`); return null; } } @@ -35,15 +40,33 @@ async function getPlaylistDetail(source, playlistId) { } async function getSongInfo(source, songId) { - const response = await fetchJson({ - source, - id: songId, - type: 'info', - }); - if (!response || response.code !== 200 || !response.data) { - return false; + // 增加重试机制,提高成功率 + const maxRetries = 3; + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + logger.info(`[TuneHub] Attempt ${attempt}/${maxRetries} to get song info: ${source}:${songId}`); + const response = await fetchJson({ + source, + id: songId, + type: 'info', + }, attempt); + if (response && response.code === 200 && response.data) { + logger.info(`[TuneHub] Success: ${source}:${songId}`); + return response.data; + } + logger.warn(`[TuneHub] Failed attempt ${attempt}: code=${response?.code || 'no response'}, data=${!!response?.data}`); + if (attempt < maxRetries) { + await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); + } + } catch (error) { + logger.error(`[TuneHub] Error on attempt ${attempt}: ${error.message}`); + if (attempt < maxRetries) { + await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); + } + } } - return response.data; + logger.error(`[TuneHub] All ${maxRetries} attempts failed for ${source}:${songId}`); + return false; } async function searchSongs(keyword, { diff --git a/backend/src/utils/network.js b/backend/src/utils/network.js index 481fad6..b94fcfb 100644 --- a/backend/src/utils/network.js +++ b/backend/src/utils/network.js @@ -1,7 +1,12 @@ const https = require('https'); -function asyncHttpsGet(url) { +function asyncHttpsGet(url, timeoutMs = 10000) { return new Promise((resolve) => { + const timeout = setTimeout(() => { + console.error(`[asyncHttpsGet] Timeout after ${timeoutMs}ms: ${url}`); + resolve(null); + }, timeoutMs); + https.get(url, res => { let data = ''; @@ -10,10 +15,12 @@ function asyncHttpsGet(url) { }); res.on('end', () => { + clearTimeout(timeout); resolve(data.toString()); }); }).on('error', err => { + clearTimeout(timeout); console.error(err); resolve(null); });