aslan-h5/src/utils/imageCacheManager.js

271 lines
7.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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()