|
|
|
|
|
class ImageCacheManager { |
|
|
constructor() { |
|
|
this.cache = new Map() |
|
|
this.cacheName = 'music-images-v1' |
|
|
this.maxCacheSize = 100 |
|
|
this.cacheExpiry = 7 * 24 * 60 * 60 * 1000 |
|
|
this.init() |
|
|
} |
|
|
|
|
|
async init() { |
|
|
|
|
|
await this.cleanupExpiredCache() |
|
|
} |
|
|
|
|
|
|
|
|
getCacheKey(url) { |
|
|
return `img_${this.hashUrl(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 |
|
|
} |
|
|
return Math.abs(hash).toString(36) |
|
|
} |
|
|
|
|
|
|
|
|
async cacheImage(url, metadata = {}) { |
|
|
try { |
|
|
|
|
|
if (!url || typeof url !== 'string') { |
|
|
return this.getDefaultImage() |
|
|
} |
|
|
|
|
|
const cacheKey = this.getCacheKey(url) |
|
|
|
|
|
|
|
|
if (this.cache.has(cacheKey)) { |
|
|
return this.cache.get(cacheKey).dataUrl |
|
|
} |
|
|
|
|
|
|
|
|
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) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let finalUrl = url |
|
|
|
|
|
|
|
|
const dataUrl = await this.convertToDataUrl(finalUrl) |
|
|
|
|
|
|
|
|
const cacheData = { |
|
|
dataUrl, |
|
|
timestamp: Date.now(), |
|
|
originalUrl: url, |
|
|
metadata |
|
|
} |
|
|
|
|
|
this.cache.set(cacheKey, cacheData) |
|
|
|
|
|
|
|
|
try { |
|
|
if (this.cache.size <= this.maxCacheSize) { |
|
|
localStorage.setItem(cacheKey, JSON.stringify(cacheData)) |
|
|
} |
|
|
} catch (e) { |
|
|
|
|
|
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() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
isCrossOrigin(url) { |
|
|
try { |
|
|
const urlObj = new URL(url, window.location.href) |
|
|
return urlObj.origin !== window.location.origin |
|
|
} catch (e) { |
|
|
|
|
|
return true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
}) |
|
|
} |
|
|
|
|
|
|
|
|
getDefaultImage() { |
|
|
return null |
|
|
} |
|
|
|
|
|
|
|
|
async getCachedImage(url) { |
|
|
const cacheKey = this.getCacheKey(url) |
|
|
|
|
|
|
|
|
if (this.cache.has(cacheKey)) { |
|
|
return this.cache.get(cacheKey).dataUrl |
|
|
} |
|
|
|
|
|
|
|
|
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 |