diff --git a/src/renderer/api/musicParser.ts b/src/renderer/api/musicParser.ts index 7d1ad7c..d9fedc1 100644 --- a/src/renderer/api/musicParser.ts +++ b/src/renderer/api/musicParser.ts @@ -160,21 +160,11 @@ export class CacheManager { // 清除URL缓存 await deleteData('music_url_cache', id); console.log(`清除歌曲 ${id} 的URL缓存`); - - // 清除失败缓存 - 需要遍历所有策略 - const strategies = ['custom', 'gdmusic', 'unblockMusic']; - for (const strategy of strategies) { - const cacheKey = `${id}_${strategy}`; - try { - await deleteData('music_failed_cache', cacheKey); - } catch { - // 忽略删除不存在缓存的错误 - } - } - console.log(`清除歌曲 ${id} 的失败缓存`); } catch (error) { - console.error('清除缓存失败:', error); + console.error('清除URL缓存失败:', error); } + // 清除内存失败缓存(覆盖所有策略,含 lxMusic) + CacheManager.clearFailedCache(id); } } @@ -237,7 +227,19 @@ const getGDMusicAudio = async (id: number, data: SongResult): Promise { const filteredSources = sources.filter((source) => source !== 'gdmusic'); console.log(`使用unblockMusic解析,音源:`, filteredSources); - return window.api.unblockMusic(id, cloneDeep(data), cloneDeep(filteredSources)); + // 整体超时兜底:unblock 全链路(IPC → match())本身无超时,底层请求挂起时会 + // 导致渲染进程无限等待、起播/切歌长时间无响应。超时解析为 null 以走降级, + // 且不抛异常(避免触发 RetryHelper 的多次重试放大等待时间)。 + const UNBLOCK_TIMEOUT = 15000; + return Promise.race([ + window.api.unblockMusic(id, cloneDeep(data), cloneDeep(filteredSources)), + new Promise((resolve) => { + setTimeout(() => { + console.warn(`unblockMusic 解析超时(${UNBLOCK_TIMEOUT}ms),放弃等待`); + resolve(null); + }, UNBLOCK_TIMEOUT); + }) + ]); }; /** diff --git a/src/renderer/api/parseFromCustomApi.ts b/src/renderer/api/parseFromCustomApi.ts index c61d45c..64b17fe 100644 --- a/src/renderer/api/parseFromCustomApi.ts +++ b/src/renderer/api/parseFromCustomApi.ts @@ -72,7 +72,9 @@ export const parseFromCustomApi = async ( response = await axios.post(plugin.apiUrl, finalParams, { timeout }); } else { // 默认为 GET - const finalUrl = `${plugin.apiUrl}?${new URLSearchParams(finalParams).toString()}`; + // apiUrl 本身可能已带查询串(如 xxx/api.php?type=url),需按情况选择 ? 或 & + const separator = plugin.apiUrl.includes('?') ? '&' : '?'; + const finalUrl = `${plugin.apiUrl}${separator}${new URLSearchParams(finalParams).toString()}`; console.log('自定义API:发送 GET 请求到:', finalUrl); response = await axios.get(finalUrl, { timeout }); } @@ -83,11 +85,20 @@ export const parseFromCustomApi = async ( if (musicUrl && typeof musicUrl === 'string') { console.log('自定义API:成功获取URL!'); // 5. 组装成应用所需的标准格式并返回 + // quality 是 'standard'/'higher'/'exhigh'/'lossless'/'hires' 等字符串, + // 直接 parseInt 会得到 NaN,这里映射为对应码率(bps) + const QUALITY_BITRATE: Record = { + standard: 128000, + higher: 192000, + exhigh: 320000, + lossless: 999000, + hires: 1900000 + }; return { data: { data: { url: musicUrl, - br: parseInt(quality) * 1000, + br: QUALITY_BITRATE[quality] ?? 320000, size: 0, md5: '', platform: plugin.name.toLowerCase().replace(/\s/g, ''), diff --git a/src/renderer/utils/request.ts b/src/renderer/utils/request.ts index bc7eb8e..3cc68bb 100644 --- a/src/renderer/utils/request.ts +++ b/src/renderer/utils/request.ts @@ -89,13 +89,13 @@ request.interceptors.response.use( return Promise.reject(error); } - // 处理 301 状态码 - if (error.response?.status === 301 && config.params.noLogin !== true) { + // 处理 301 状态码:登录态失效,重试同一请求无意义,清除登录信息后直接拒绝 + if (error.response?.status === 301 && config.params?.noLogin !== true) { // 使用 store mutation 清除用户信息 const userStore = useUserStore(); userStore.handleLogout(); - console.log(`301 状态码,清除登录信息后重试第 ${config.retryCount} 次`); - config.retryCount = 3; + console.log('301 状态码:登录态已失效,已清除登录信息,不再重试'); + return Promise.reject(error); } // 检查是否还可以重试