// 图像缓存管理器 - 解决跨域问题并实现本地缓存 class ImageCacheManager { constructor() { this.cache = new Map() this.cacheName = 'music-images-v1' this.maxCacheSize = 100 // 最多缓存100张图片 this.cacheExpiry = 7 * 24 * 60 * 60 * 1000 // 7天过期 this.init() } async init() { // 清理过期的缓存 await this.cleanupExpiredCache() } // 获取图片的缓存键 getCacheKey(url) { return `img_${this.hashUrl(url)}` } // 简单的URL哈希函数 hashUrl(url) { let hash = 0 for (let i = 0; i < url.length; i++) { const char = url.charCodeAt(i) hash = ((hash << 5) - hash) + char hash = hash & hash // 转换为32位整数 } return Math.abs(hash).toString(36) } // 将图片转换为base64并缓存 - 改进跨域处理 async cacheImage(url, metadata = {}) { try { // 检查URL是否为空或无效 if (!url || typeof url !== 'string') { return this.getDefaultImage() } const cacheKey = this.getCacheKey(url) // 检查内存缓存 if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey).dataUrl } // 检查localStorage缓存 const cached = localStorage.getItem(cacheKey) if (cached) { try { const cachedData = JSON.parse(cached) if (Date.now() - cachedData.timestamp < this.cacheExpiry) { this.cache.set(cacheKey, cachedData) return cachedData.dataUrl } } catch (e) { // 缓存数据损坏,删除 localStorage.removeItem(cacheKey) } } // 直接使用原始URL,不使用代理 let finalUrl = url // 使用Canvas方式转换图片 const dataUrl = await this.convertToDataUrl(finalUrl) // 保存到缓存 const cacheData = { dataUrl, timestamp: Date.now(), originalUrl: url, metadata } this.cache.set(cacheKey, cacheData) // 保存到localStorage try { if (this.cache.size <= this.maxCacheSize) { localStorage.setItem(cacheKey, JSON.stringify(cacheData)) } } catch (e) { // localStorage空间不足,清理一些旧缓存 await this.cleanupOldCache() try { localStorage.setItem(cacheKey, JSON.stringify(cacheData)) } catch (e) { console.warn('无法保存图片到localStorage:', e) } } return dataUrl } catch (error) { console.error('缓存图片失败:', error) return this.getDefaultImage() } } // 检查是否为跨域URL isCrossOrigin(url) { try { const urlObj = new URL(url, window.location.href) return urlObj.origin !== window.location.origin } catch (e) { // 如果URL解析失败,假设为跨域 return true } } // 将图片转换为DataUrl async convertToDataUrl(url) { return new Promise((resolve, reject) => { const img = new Image() img.crossOrigin = 'anonymous' img.onload = () => { try { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') canvas.width = img.naturalWidth canvas.height = img.naturalHeight ctx.drawImage(img, 0, 0) const result = canvas.toDataURL('image/jpeg', 0.8) resolve(result) } catch (error) { console.warn('Canvas转换失败,使用原始URL:', error) resolve(url) } } img.onerror = () => { console.warn('图片加载失败,使用默认图片') resolve(this.getDefaultImage()) } // 设置超时 setTimeout(() => { console.warn('图片加载超时,使用默认图片') resolve(this.getDefaultImage()) }, 10000) img.src = url }) } // 获取默认图片 - 返回 null,让组件使用 fas fa-music 图标 getDefaultImage() { return null } // 获取缓存的图片 async getCachedImage(url) { const cacheKey = this.getCacheKey(url) // 检查内存缓存 if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey).dataUrl } // 检查localStorage缓存 const cached = localStorage.getItem(cacheKey) if (cached) { try { const cachedData = JSON.parse(cached) if (Date.now() - cachedData.timestamp < this.cacheExpiry) { this.cache.set(cacheKey, cachedData) return cachedData.dataUrl } else { // 缓存过期,删除 localStorage.removeItem(cacheKey) } } catch (e) { localStorage.removeItem(cacheKey) } } return null } // 预加载图片 async preloadImage(url, metadata = {}) { return this.cacheImage(url, metadata) } // 清理过期缓存 async cleanupExpiredCache() { const keys = Object.keys(localStorage) const now = Date.now() keys.forEach(key => { if (key.startsWith('img_')) { try { const cached = JSON.parse(localStorage.getItem(key)) if (now - cached.timestamp > this.cacheExpiry) { localStorage.removeItem(key) } } catch (e) { localStorage.removeItem(key) } } }) } // 清理旧缓存(释放空间) async cleanupOldCache() { const keys = Object.keys(localStorage) const imageKeys = keys.filter(key => key.startsWith('img_')) // 按时间戳排序,删除最旧的一半 const cached = imageKeys.map(key => { try { const data = JSON.parse(localStorage.getItem(key)) return { key, timestamp: data.timestamp } } catch (e) { return { key, timestamp: 0 } } }).sort((a, b) => a.timestamp - b.timestamp) const toDelete = cached.slice(0, Math.floor(cached.length / 2)) toDelete.forEach(item => { localStorage.removeItem(item.key) this.cache.delete(item.key) }) } // 清空所有缓存 async clearCache() { const keys = Object.keys(localStorage) keys.forEach(key => { if (key.startsWith('img_')) { localStorage.removeItem(key) } }) this.cache.clear() } // 获取缓存统计信息 getCacheStats() { const keys = Object.keys(localStorage) const imageKeys = keys.filter(key => key.startsWith('img_')) let totalSize = 0 imageKeys.forEach(key => { totalSize += localStorage.getItem(key).length }) return { totalImages: imageKeys.length, memoryCache: this.cache.size, totalSize: (totalSize / 1024 / 1024).toFixed(2) + 'MB' } } } // 创建全局实例 export const imageCacheManager = new ImageCacheManager() // 导出便捷方法 export const cacheImage = (url, metadata) => imageCacheManager.cacheImage(url, metadata) export const getCachedImage = (url) => imageCacheManager.getCachedImage(url) export const preloadImage = (url, metadata) => imageCacheManager.preloadImage(url, metadata) export const clearImageCache = () => imageCacheManager.clearCache() export const getImageCacheStats = () => imageCacheManager.getCacheStats() export default imageCacheManager