music / src /utils /imageCache.js
ahutchen's picture
feat(component): 新增歌单相关组件和功能
021ee94
// 图像缓存管理器 - 解决跨域问题并实现本地缓存
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