import { cloneDeep } from 'lodash'; import { musicDB } from '@/hooks/MusicHook'; import { SongSourceConfigManager } from '@/services/SongSourceConfigManager'; import { useSettingsStore } from '@/store'; import type { SongResult } from '@/types/music'; import { isElectron } from '@/utils'; import requestMusic from '@/utils/request_music'; import type { ParsedMusicResult } from './gdmusic'; import { parseFromGDMusic } from './gdmusic'; import { LxMusicStrategy } from './lxMusicStrategy'; import { parseFromCustomApi } from './parseFromCustomApi'; const { saveData, getData, deleteData } = musicDB; /** * 音乐解析结果接口 */ export interface MusicParseResult { data: { code: number; message: string; data?: { url: string; [key: string]: any; }; }; } /** * 缓存配置 */ const CACHE_CONFIG = { // 音乐URL缓存时间:30分钟 MUSIC_URL_CACHE_TIME: 30 * 60 * 1000, // 失败缓存时间:1分钟(减少到 1 分钟以便更快恢复) FAILED_CACHE_TIME: 1 * 60 * 1000, // 重试配置 MAX_RETRY_COUNT: 2, RETRY_DELAY: 1000 }; /** * 内存失败缓存(替代 IndexedDB,更轻量且应用重启后自动失效) */ const failedCacheMap = new Map(); /** * 缓存管理器 */ export class CacheManager { /** * 获取缓存的音乐URL */ static async getCachedMusicUrl( id: number, musicSources?: string[] ): Promise { try { const cached = await getData('music_url_cache', id); if ( cached?.createTime && Date.now() - cached.createTime < CACHE_CONFIG.MUSIC_URL_CACHE_TIME ) { // 检查缓存的音源配置是否与当前配置一致 const cachedSources = cached.musicSources || []; const currentSources = musicSources || []; // 如果音源配置不一致,清除缓存 if (JSON.stringify(cachedSources.sort()) !== JSON.stringify(currentSources.sort())) { console.log(`音源配置已变更,清除歌曲 ${id} 的缓存`); await deleteData('music_url_cache', id); return null; } console.log(`使用缓存的音乐URL: ${id}`); return cached.data; } // 清理过期缓存 if (cached) { await deleteData('music_url_cache', id); } } catch (error) { console.warn('获取缓存失败:', error); } return null; } /** * 缓存音乐URL */ static async setCachedMusicUrl( id: number, result: MusicParseResult, musicSources?: string[] ): Promise { try { // 深度克隆数据,确保可以被 IndexedDB 存储 await saveData('music_url_cache', { id, data: cloneDeep(result), musicSources: cloneDeep(musicSources || []), createTime: Date.now() }); console.log(`缓存音乐URL成功: ${id}`); } catch (error) { console.error('缓存音乐URL失败:', error); } } /** * 检查是否在失败缓存期内(使用内存缓存) */ static isInFailedCache(id: number, strategyName: string): boolean { const cacheKey = `${id}_${strategyName}`; const cachedTime = failedCacheMap.get(cacheKey); if (cachedTime && Date.now() - cachedTime < CACHE_CONFIG.FAILED_CACHE_TIME) { console.log(`策略 ${strategyName} 在失败缓存期内,跳过`); return true; } // 清理过期缓存 if (cachedTime) { failedCacheMap.delete(cacheKey); } return false; } /** * 添加失败缓存(使用内存缓存) */ static addFailedCache(id: number, strategyName: string): void { const cacheKey = `${id}_${strategyName}`; failedCacheMap.set(cacheKey, Date.now()); console.log( `添加失败缓存成功: ${strategyName} (缓存时间: ${CACHE_CONFIG.FAILED_CACHE_TIME / 1000}秒)` ); } /** * 清除指定歌曲的失败缓存 */ static clearFailedCache(id: number): void { const keysToDelete: string[] = []; failedCacheMap.forEach((_, key) => { if (key.startsWith(`${id}_`)) { keysToDelete.push(key); } }); keysToDelete.forEach((key) => failedCacheMap.delete(key)); if (keysToDelete.length > 0) { console.log(`清除歌曲 ${id} 的失败缓存: ${keysToDelete.length} 项`); } } /** * 清除指定歌曲的所有缓存 */ static async clearMusicCache(id: number): Promise { try { // 清除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); } } } /** * 重试工具 */ class RetryHelper { /** * 带重试的异步执行 */ static async withRetry( fn: () => Promise, maxRetries = CACHE_CONFIG.MAX_RETRY_COUNT, delay = CACHE_CONFIG.RETRY_DELAY ): Promise { let lastError: Error; for (let i = 0; i <= maxRetries; i++) { try { return await fn(); } catch (error) { lastError = error as Error; if (i < maxRetries) { console.log(`重试第 ${i + 1} 次,延迟 ${delay}ms`); await new Promise((resolve) => setTimeout(resolve, delay)); delay *= 2; // 指数退避 } } } throw lastError!; } } /** * 从GD音乐台获取音频URL * @param id 歌曲ID * @param data 歌曲数据 * @returns 解析结果,失败时返回null */ const getGDMusicAudio = async (id: number, data: SongResult): Promise => { try { const gdResult = await parseFromGDMusic(id, data, '999'); if (gdResult) { return gdResult; } } catch (error) { console.error('GD音乐台解析失败:', error); } return null; }; /** * 使用unblockMusic解析音频URL * @param id 歌曲ID * @param data 歌曲数据 * @param sources 音源列表 * @returns 解析结果 */ const getUnblockMusicAudio = (id: number, data: SongResult, sources: any[]) => { const filteredSources = sources.filter((source) => source !== 'gdmusic'); console.log(`使用unblockMusic解析,音源:`, filteredSources); return window.api.unblockMusic(id, cloneDeep(data), cloneDeep(filteredSources)); }; /** * 统一的解析结果适配器 */ const adaptParseResult = (result: any): MusicParseResult | null => { if (!result) return null; // 如果已经是标准格式 if (result.data?.code !== undefined && result.data?.message !== undefined) { return result; } // 适配GD音乐台的返回格式 if (result.data?.data?.url) { return { data: { code: 200, message: 'success', data: { url: result.data.data.url, ...result.data.data } } }; } // 适配其他格式 if (result.url) { return { data: { code: 200, message: 'success', data: { url: result.url, ...result } } }; } return null; }; /** * 音源解析策略接口 */ interface MusicSourceStrategy { name: string; priority: number; canHandle: (sources: string[], settingsStore?: any) => boolean; parse: ( id: number, data: SongResult, quality?: string, sources?: string[] ) => Promise; } /** * 自定义API解析策略 */ class CustomApiStrategy implements MusicSourceStrategy { name = 'custom'; priority = 1; canHandle(sources: string[], settingsStore?: any): boolean { return sources.includes('custom') && Boolean(settingsStore?.setData?.customApiPlugin); } async parse(id: number, data: SongResult, quality = 'higher'): Promise { // 检查失败缓存 if (CacheManager.isInFailedCache(id, this.name)) { return null; } try { console.log('尝试使用自定义API解析...'); const result = await RetryHelper.withRetry(async () => { return await parseFromCustomApi(id, data, quality); }); const adaptedResult = adaptParseResult(result); if (adaptedResult?.data?.data?.url) { console.log('自定义API解析成功'); return adaptedResult; } // 解析失败,添加失败缓存 CacheManager.addFailedCache(id, this.name); return null; } catch (error) { console.error('自定义API解析失败:', error); CacheManager.addFailedCache(id, this.name); return null; } } } /** * GD音乐台解析策略 */ class GDMusicStrategy implements MusicSourceStrategy { name = 'gdmusic'; priority = 3; canHandle(sources: string[]): boolean { return sources.includes('gdmusic'); } async parse(id: number, data: SongResult): Promise { // 检查失败缓存 if (CacheManager.isInFailedCache(id, this.name)) { return null; } try { console.log('尝试使用GD音乐台解析...'); const result = await RetryHelper.withRetry(async () => { return await getGDMusicAudio(id, data); }); const adaptedResult = adaptParseResult(result); if (adaptedResult?.data?.data?.url) { console.log('GD音乐台解析成功'); return adaptedResult; } // 解析失败,添加失败缓存 CacheManager.addFailedCache(id, this.name); return null; } catch (error) { console.error('GD音乐台解析失败:', error); CacheManager.addFailedCache(id, this.name); return null; } } } /** * UnblockMusic解析策略 */ class UnblockMusicStrategy implements MusicSourceStrategy { name = 'unblockMusic'; priority = 4; canHandle(sources: string[]): boolean { const unblockSources = sources.filter((source) => !['custom', 'gdmusic'].includes(source)); return unblockSources.length > 0; } async parse( id: number, data: SongResult, _quality?: string, sources?: string[] ): Promise { // 检查失败缓存 if (CacheManager.isInFailedCache(id, this.name)) { return null; } try { const unblockSources = (sources || []).filter( (source) => !['custom', 'gdmusic'].includes(source) ); console.log('尝试使用UnblockMusic解析:', unblockSources); const result = await RetryHelper.withRetry(async () => { return await getUnblockMusicAudio(id, data, unblockSources); }); const adaptedResult = adaptParseResult(result); if (adaptedResult?.data?.data?.url) { console.log('UnblockMusic解析成功'); return adaptedResult; } // 解析失败,添加失败缓存 CacheManager.addFailedCache(id, this.name); return null; } catch (error) { console.error('UnblockMusic解析失败:', error); CacheManager.addFailedCache(id, this.name); return null; } } } /** * 音源策略工厂 */ class MusicSourceStrategyFactory { private static strategies: MusicSourceStrategy[] = [ new LxMusicStrategy(), new CustomApiStrategy(), new GDMusicStrategy(), new UnblockMusicStrategy() ]; /** * 获取可用的解析策略 * @param sources 音源列表 * @param settingsStore 设置存储 * @returns 排序后的可用策略列表 */ static getAvailableStrategies(sources: string[], settingsStore?: any): MusicSourceStrategy[] { return this.strategies .filter((strategy) => strategy.canHandle(sources, settingsStore)) .sort((a, b) => a.priority - b.priority); } } /** * 获取音源配置 * @param id 歌曲ID * @param settingsStore 设置存储 * @returns 音源列表和音质设置 */ const getMusicConfig = (id: number, settingsStore?: any) => { let musicSources: string[] = []; let quality = 'higher'; try { // 尝试获取歌曲自定义音源(使用 SongSourceConfigManager) const songConfig = SongSourceConfigManager.getConfig(id); if (songConfig && songConfig.sources.length > 0) { musicSources = songConfig.sources; console.log(`使用歌曲 ${id} 自定义音源:`, musicSources); } // 如果没有自定义音源,使用全局设置 if (musicSources.length === 0) { musicSources = settingsStore?.setData?.enabledMusicSources || []; console.log('使用全局音源设置:', musicSources); } quality = settingsStore?.setData?.musicQuality || 'higher'; } catch (error) { console.error('读取音源配置失败,使用默认配置:', error); musicSources = []; quality = 'higher'; } return { musicSources, quality }; }; /** * 音乐解析器主类 */ export class MusicParser { /** * 解析音乐URL * @param id 歌曲ID * @param data 歌曲数据 * @returns 解析结果 */ static async parseMusic(id: number, data: SongResult): Promise { const startTime = performance.now(); try { // 非Electron环境直接使用API请求 if (!isElectron) { console.log('非Electron环境,使用API请求'); return await requestMusic.get('/music', { params: { id } }); } // 获取设置存储 let settingsStore: any; try { settingsStore = useSettingsStore(); } catch (error) { console.error('无法获取设置存储,使用后备方案:', error); return await requestMusic.get('/music', { params: { id } }); } // 获取音源配置 const { musicSources, quality } = getMusicConfig(id, settingsStore); // 检查缓存(传入音源配置用于验证缓存有效性) console.log(`检查歌曲 ${id} 的缓存...`); const cachedResult = await CacheManager.getCachedMusicUrl(id, musicSources); if (cachedResult) { const endTime = performance.now(); console.log(`✅ 命中缓存,歌曲 ${id},耗时: ${(endTime - startTime).toFixed(2)}ms`); return cachedResult; } console.log(`❌ 未命中缓存,歌曲 ${id},开始解析...`); // 检查音乐解析功能是否启用 if (!settingsStore?.setData?.enableMusicUnblock) { console.log('音乐解析功能已禁用'); return { data: { code: 404, message: '音乐解析功能已禁用', data: undefined } }; } if (musicSources.length === 0) { console.warn('没有配置可用的音源,使用后备方案'); return await requestMusic.get('/music', { params: { id } }); } // 获取可用的解析策略 const availableStrategies = MusicSourceStrategyFactory.getAvailableStrategies( musicSources, settingsStore ); if (availableStrategies.length === 0) { console.warn('没有可用的解析策略,使用后备方案'); return await requestMusic.get('/music', { params: { id } }); } console.log( `开始解析歌曲 ${id},可用策略:`, availableStrategies.map((s) => s.name) ); // 按优先级依次尝试解析策略 for (const strategy of availableStrategies) { try { const result = await strategy.parse(id, data, quality, musicSources); if (result?.data?.data?.url) { const endTime = performance.now(); console.log( `解析成功,使用策略: ${strategy.name},耗时: ${(endTime - startTime).toFixed(2)}ms` ); // 缓存成功结果(包含音源配置) await CacheManager.setCachedMusicUrl(id, result, musicSources); return result; } console.log(`策略 ${strategy.name} 解析失败,继续尝试下一个策略`); } catch (error) { console.error(`策略 ${strategy.name} 解析异常:`, error); // 继续尝试下一个策略 } } console.warn('所有解析策略都失败了,使用后备方案'); } catch (error) { console.error('MusicParser.parseMusic 执行异常,使用后备方案:', error); } // 后备方案:使用API请求 try { console.log('使用后备方案:API请求'); const result = await requestMusic.get('/music', { params: { id } }); // 如果后备方案成功,也进行缓存 if (result?.data?.data?.url) { console.log('后备方案成功,缓存结果'); await CacheManager.setCachedMusicUrl(id, result, []); } return result; } catch (apiError) { console.error('API请求也失败了:', apiError); const endTime = performance.now(); console.log(`总耗时: ${(endTime - startTime).toFixed(2)}ms`); return { data: { code: 500, message: '所有解析方式都失败了', data: undefined } }; } } }