// src/utils/imageCacheManager.js class ImageCacheManager { constructor() { this.cacheVersionKey = 'likei-image-cache-version' this.currentAppVersion = import.meta.env.VITE_APP_VERSION || '1.0.0' this.cacheName = `likei-images-v${this.currentAppVersion}` this.maxCacheItems = 100 // 限制最多缓存50张图片(可根据需要调整) this.maxCacheSize = 100 * 1024 * 1024 // 限制总大小50MB(可根据需要调整) this.init() } async init() { await this.checkVersionAndUpdate() } // 版本检查逻辑 async checkVersionAndUpdate() { const cachedVersion = localStorage.getItem(this.cacheVersionKey) if (!cachedVersion) { localStorage.setItem(this.cacheVersionKey, this.currentAppVersion) console.log('初始化图片缓存版本:', this.currentAppVersion) return } if (cachedVersion !== this.currentAppVersion) { console.log(`图片缓存版本更新: ${cachedVersion} -> ${this.currentAppVersion}`) await this.clearOldCaches() localStorage.setItem(this.cacheVersionKey, this.currentAppVersion) } } // 检查并清理超出限制的缓存 async enforceCacheLimits(cache) { try { const keys = await cache.keys() // 检查数量限制 if (keys.length > this.maxCacheItems) { // 删除最旧的缓存项 const itemsToRemove = keys.length - this.maxCacheItems for (let i = 0; i < itemsToRemove; i++) { await cache.delete(keys[i]) console.log(`已删除超出限制的缓存项: ${keys[i].url}`) } } // 可选:检查大小限制(需要估算) // 这里简化处理,实际项目中可以更精确计算 } catch (error) { console.warn('检查缓存限制时出错:', error) } } async clearOldCaches() { if (!('caches' in window)) return try { const cacheNames = await caches.keys() const oldCaches = cacheNames.filter( (name) => name.startsWith('likei-images-v') && name !== this.cacheName ) await Promise.all(oldCaches.map((name) => caches.delete(name))) console.log('已清理旧版本图片缓存:', oldCaches) } catch (error) { console.warn('清理旧图片缓存失败:', error) } } // 更严格的判断是否为本地构建资源 isBuildAsset(url) { try { // 如果是相对路径,肯定是本地资源 if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) { return true } // 如果是绝对URL,检查是否为本地域名 const currentOrigin = window.location.origin if (url.startsWith(currentOrigin)) { return true } // 检查是否为构建后的资源(包含哈希) if (/\.[a-f0-9]{8,}\./.test(url)) { return true } // 检查是否为本地assets路径 if (url.includes('/src/assets/') || url.includes('/assets/')) { return true } // 新增:允许缓存特定域名下的资源 const ossBaseUrl = 'https://tkm-likei.oss-ap-southeast-1.aliyuncs.com/h5' if (url.startsWith(ossBaseUrl)) { return true } return false } catch (error) { console.warn('判断资源类型时出错:', error) return false } } // 缓存图片 async cacheImage(url) { if (!('caches' in window) || !url) return try { // 只缓存本地构建后的资源,避免缓存外部网络图片 if (this.isBuildAsset(url)) { const cache = await caches.open(this.cacheName) const cachedResponse = await cache.match(url) if (!cachedResponse) { // 先检查并清理超出限制的缓存 // await this.enforceCacheLimits(cache) const response = await fetch(url, { mode: 'cors', credentials: 'omit', }) if (response.ok) { await cache.put(url, response.clone()) console.log(`图片已缓存: ${url}`) } } } } catch (error) { console.warn(`缓存图片失败 ${url}:`, error) } } // 从缓存获取图片 async getCachedImage(url) { if (!('caches' in window)) return null try { // 只从缓存获取本地资源 if (this.isBuildAsset(url)) { const cache = await caches.open(this.cacheName) return await cache.match(url) } return null } catch (error) { console.warn('获取缓存图片失败:', error) return null } } // 智能加载图片 async loadImage(src) { return new Promise(async (resolve, reject) => { try { // 首先尝试从缓存获取(仅限本地资源) const cachedResponse = await this.getCachedImage(src) if (cachedResponse) { const blob = await cachedResponse.blob() const imageUrl = URL.createObjectURL(blob) const img = new Image() img.onload = () => { URL.revokeObjectURL(imageUrl) resolve(img) } img.onerror = () => { URL.revokeObjectURL(imageUrl) this.loadImageFromNetwork(src, resolve, reject) } img.src = imageUrl return } // 缓存中没有或不是本地资源,从网络加载 this.loadImageFromNetwork(src, resolve, reject) } catch (error) { this.loadImageFromNetwork(src, resolve, reject) } }) } async loadImageFromNetwork(src, resolve, reject) { try { // 对于OSS资源,先尝试直接创建Image对象(绕过fetch的CORS问题) if (src.startsWith('https://tkm-likei.oss-ap-southeast-1.aliyuncs.com/h5')) { const img = new Image() img.crossOrigin = 'anonymous' img.onload = () => { resolve(img) // 对于OSS资源,仍然尝试缓存(如果支持) if (this.isBuildAsset(src)) { this.cacheImageFromImage(src, img) } } img.onerror = (error) => { console.warn('图片加载失败:', src, error) reject(error) } img.src = src return } // 对于其他资源,使用fetch const response = await fetch(src, { mode: 'cors', credentials: 'omit', }) if (response.ok) { const img = new Image() img.onload = async () => { resolve(img) // 缓存带有 ETag 的响应 if (this.isBuildAsset(src)) { try { await this.cacheImage(src) // 使用原始的缓存方法 } catch (cacheError) { console.warn('缓存图片时出错:', cacheError) } } } img.onerror = reject img.src = URL.createObjectURL(await response.blob()) } else { reject(new Error(`HTTP ${response.status}`)) } } catch (error) { console.warn('网络加载图片失败:', src, error) reject(error) } } // 为OSS图片创建缓存的特殊处理 async cacheImageFromImage(src, img) { try { if (!('caches' in window) || !this.isBuildAsset(src)) return const cache = await caches.open(this.cacheName) const cachedResponse = await cache.match(src) if (!cachedResponse) { // 创建一个模拟的响应来缓存 const canvas = document.createElement('canvas') canvas.width = img.width canvas.height = img.height const ctx = canvas.getContext('2d') ctx.drawImage(img, 0, 0) canvas.toBlob(async (blob) => { if (blob) { const response = new Response(blob, { headers: { 'Content-Type': 'image/png', }, }) await cache.put(src, response.clone()) console.log(`OSS图片已缓存: ${src}`) } }) } } catch (error) { console.warn('缓存OSS图片失败:', src, error) } } } // 导出单例 export const imageCacheManager = new ImageCacheManager()