271 lines
7.9 KiB
JavaScript
271 lines
7.9 KiB
JavaScript
// 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()
|