From 05d142b9db64f70ddc21a21456ebba7c75536d16 Mon Sep 17 00:00:00 2001 From: hzj <1304805162@qq.com> Date: Fri, 26 Dec 2025 19:22:24 +0800 Subject: [PATCH] =?UTF-8?q?chore(=E5=9B=BE=E7=89=87=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F):=20=E6=B7=BB=E5=8A=A0=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=B5=81=E7=A8=8B=E7=9A=84=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E5=92=8C=E9=94=99=E8=AF=AFlog=E8=BE=93=E5=87=BA=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/directives/smartImage.js | 2 +- src/main.js | 8 +- src/utils/imageCacheManager.js | 180 +++++++++++++-------------------- src/utils/versionChecker.js | 70 ++++++++----- 4 files changed, 121 insertions(+), 139 deletions(-) diff --git a/src/directives/smartImage.js b/src/directives/smartImage.js index 5374ae5..d1f615a 100644 --- a/src/directives/smartImage.js +++ b/src/directives/smartImage.js @@ -99,7 +99,7 @@ async function processImage(el, url, skipFade = false) { await nextTick() try { - // 统一使用缓存管理器处理所有可缓存资源 + // 目标符合缓存条件 if (imageCacheManager.isBuildAsset(url)) { // 获取缓存的图片 URL(如果是缓存的图片会返回 blob URL,否则返回原始 URL) const imageUrl = await imageCacheManager.getCacheImageUrl(url) diff --git a/src/main.js b/src/main.js index b5f8e43..e00b719 100644 --- a/src/main.js +++ b/src/main.js @@ -15,8 +15,10 @@ import { useLangStore } from '@/stores/lang' // 移动到顶部 // 初始化环境信息 logEnvInfo() -// 版本检查后添加 -await versionChecker.checkVersion() +// 检查版本更新(首先判断执行一次对缓存的操作,最后返回检测结果) +if (await versionChecker.checkVersion()) { + console.log('版本已更新') +} // 初始化权限系统 console.debug('🔐 Permission system initialized') @@ -92,7 +94,7 @@ app.use(i18n) app.mount('#app') -// 定时检查逻辑 +// 定时检查是否更新 if (versionChecker.enableCheck) { setInterval(async () => { const versionResult = await versionChecker.checkServerVersion() diff --git a/src/utils/imageCacheManager.js b/src/utils/imageCacheManager.js index f29f97c..693a1bc 100644 --- a/src/utils/imageCacheManager.js +++ b/src/utils/imageCacheManager.js @@ -6,7 +6,7 @@ class ImageCacheManager { this.cacheVersionKey = 'h5_app_version' this.currentAppVersion = import.meta.env.VITE_APP_VERSION || '1.0.0' this.currentImageVersion = IMAGE_CACHE_VERSION.version // 使用完整版本号 - this.versionType = import.meta.env.VITE_VERSION_TYPE || 'patch' // major, minor, patch + // 移除了 this.versionType = import.meta.env.VITE_VERSION_TYPE || 'patch' this.imageCacheName = `likei-images-v${this.currentImageVersion}` // OSS图片 this.assetCacheName = `likei-assets-v${this.currentImageVersion}` // 本地assets @@ -36,6 +36,7 @@ class ImageCacheManager { return currentMajor > cachedMajor } + // 返回对应的缓存 async getCacheForUrl(url) { // 规范化 URL const normalizedUrl = this.normalizeUrl(url) @@ -113,24 +114,22 @@ class ImageCacheManager { // 检查是否是major版本更新 const isMajorUpdate = this.isMajorVersionUpdate(cachedVersion, this.currentAppVersion) + // major版本更新:清除所有缓存 if (isMajorUpdate) { - // major版本更新:清除所有缓存 - console.log( - `检测到major版本更新,从 ${cachedVersion} 更新到 ${this.currentAppVersion},清除所有缓存` - ) + console.log(`major版本更新:清除所有缓存`) await this.clearAllCaches() - } else { - // 非major版本更新:检查图片版本是否变化 + } + + // 非major版本更新:检查图片版本是否变化 + else { const isImageVersionChanged = this.isVersionChanged( cachedImageVersion || '1.0.0', IMAGE_CACHE_VERSION.version ) + // 图片版本变化:根据配置清除指定类型的缓存 if (isImageVersionChanged) { - // 图片版本变化:根据配置清除指定类型的缓存 - console.log( - `检测到图片版本变化,从 ${cachedImageVersion} 更新到 ${IMAGE_CACHE_VERSION.version},根据配置清除缓存类型: ${IMAGE_CACHE_VERSION.cacheType}` - ) + console.log(`非major版本更新:清除缓存类型: ${IMAGE_CACHE_VERSION.cacheType}`) await this.clearCacheByType(IMAGE_CACHE_VERSION.cacheType) } // 如果图片版本未变化且不是major更新,则不执行任何清除操作 @@ -154,8 +153,8 @@ class ImageCacheManager { await this.clearAllCaches() break default: - // 默认只清除图片缓存 - await this.clearOldImageCaches() + // 默认清除全部图片缓存 + await this.clearAllCaches() break } } @@ -165,12 +164,13 @@ class ImageCacheManager { try { const keys = await cache.keys() - // 如果同时满足数量和大小限制,则无需处理 + // 检查数量限制 if (keys.length <= this.maxCacheItems) { // 快速估算大小检查(基于headers) let estimatedTotalSize = 0 let needDetailedCheck = false + // 计算缓存大小 for (const key of keys) { const response = await cache.match(key) if (response) { @@ -185,12 +185,14 @@ class ImageCacheManager { } } - // 如果估计大小在限制内且不需要详细检查,则直接返回 + // 检查大小限制 + // 无需检查,亦或是缓存数量和大小都符合要求,直接返回 if (!needDetailedCheck && estimatedTotalSize <= this.maxCacheSize) { return } } + // 缓存项超出限制,需要清理 // 获取所有缓存项及其大小信息 const cacheEntries = [] for (const key of keys) { @@ -210,17 +212,14 @@ class ImageCacheManager { } // 计算当前总数和总大小 - let totalItems = cacheEntries.length - let totalSize = cacheEntries.reduce((sum, entry) => sum + entry.size, 0) + let totalItems = cacheEntries.length //当前总数 + let totalSize = cacheEntries.reduce((sum, entry) => sum + entry.size, 0) //总大小 - // 确定需要删除的项数(基于数量限制) - let itemsToRemoveCount = Math.max(0, totalItems - this.maxCacheItems) - - // 确定需要删除的额外项数(基于大小限制) - let sizeToRemove = Math.max(0, totalSize - this.maxCacheSize) + let itemsToRemoveCount = Math.max(0, totalItems - this.maxCacheItems) // 需要删除的项数 + let sizeToRemove = Math.max(0, totalSize - this.maxCacheSize) // 需要删除的项数 + let itemsToDelete = [] //需要删除的项 // 综合两个限制条件,确定实际需要删除的项 - let itemsToDelete = [] if (itemsToRemoveCount > 0 || sizeToRemove > 0) { // 按照最旧的顺序排序(keys已经是按顺序的) // 逐个删除直到满足两个条件 @@ -248,7 +247,7 @@ class ImageCacheManager { } } - // 清除全部旧图片缓存(仅在图片版本更新时调用) + // 清除旧的OSS图片缓存 async clearOldImageCaches() { if (!('caches' in window)) return @@ -259,14 +258,14 @@ class ImageCacheManager { name.startsWith('likei-images-v') && !name.includes(`v${this.currentImageVersion}`) // 排除当前版本的缓存 ) - console.log('正在清除旧图片缓存:', oldImageCaches) + console.log('正在清除旧的OSS图片缓存:', oldImageCaches) await Promise.all(oldImageCaches.map((name) => caches.delete(name))) } catch (error) { - console.warn('清理旧图片缓存失败:', error) + console.warn('清理旧的OSS图片缓存失败:', error) } } - // 清除全部旧资产缓存(仅在图片版本更新时调用) + // 清除旧的本地assets缓存 async clearOldAssetCaches() { if (!('caches' in window)) return @@ -277,14 +276,14 @@ class ImageCacheManager { name.startsWith('likei-assets-v') && !name.includes(`v${this.currentImageVersion}`) // 排除当前版本的缓存 ) - console.log('正在清除旧资产缓存:', oldAssetCaches) + console.log('正在清除旧的本地assets缓存:', oldAssetCaches) await Promise.all(oldAssetCaches.map((name) => caches.delete(name))) } catch (error) { - console.warn('清理旧资产缓存失败:', error) + console.warn('清理旧的本地assets缓存失败:', error) } } - // 清除所有缓存(仅在major版本更新时调用) + // 清除所有图片缓存(OSS图片、本地assets图片) async clearAllCaches() { if (!('caches' in window)) return @@ -294,10 +293,10 @@ class ImageCacheManager { (name) => name.startsWith('likei-images-v') || name.startsWith('likei-assets-v') ) - console.log('正在清除所有缓存:', allCaches) + console.log('正在清除所有图片缓存:', allCaches) await Promise.all(allCaches.map((name) => caches.delete(name))) } catch (error) { - console.warn('清理所有缓存失败:', error) + console.warn('清理所有图片缓存失败:', error) } } @@ -368,7 +367,7 @@ class ImageCacheManager { } } - // 获取缓存的图片 Blob URL + // 获取缓存的图片 Blob URL(如果是缓存的图片会返回 blob URL,否则返回原始 URL) async getCacheImageUrl(url) { if (!('caches' in window)) return url @@ -376,6 +375,7 @@ class ImageCacheManager { // 规范化 URL const normalizedUrl = this.normalizeUrl(url) + // 目标符合缓存条件 if (this.isBuildAsset(normalizedUrl)) { const cache = await this.getCacheForUrl(normalizedUrl) @@ -387,38 +387,40 @@ class ImageCacheManager { }) let cachedResponse = await cache.match(request) + // 2. 如果精确匹配失败,尝试直接使用 URL 字符串匹配 if (!cachedResponse) { - // 2. 如果精确匹配失败,尝试直接使用 URL 字符串匹配 cachedResponse = await cache.match(normalizedUrl) } + // 3. 如果还是没找到,尝试使用 URL 的 origin + pathname(去掉查询参数) if (!cachedResponse) { - // 3. 如果还是没找到,尝试使用 URL 的 origin + pathname(去掉查询参数) const urlObj = new URL(normalizedUrl) const urlWithoutQuery = `${urlObj.origin}${urlObj.pathname}` cachedResponse = await cache.match(urlWithoutQuery) } + // 4. 尝试匹配去掉协议的 URL if (!cachedResponse) { - // 4. 尝试匹配去掉协议的 URL const urlWithoutProtocol = normalizedUrl.replace(/^https?:\/\//, '') cachedResponse = await cache.match(urlWithoutProtocol) } + // 经过上面流程后,匹配成功 if (cachedResponse) { - const blob = await cachedResponse.blob() - return URL.createObjectURL(blob) - } else { - // 缓存未命中,触发预缓存 + const blob = await cachedResponse.blob() // 获取命中缓存对象的blob + return URL.createObjectURL(blob) // 返回创建的 Blob URL + } + // 经过上面流程后,缓存依旧未命中 + else { try { - await this.cacheImage(normalizedUrl) + await this.cacheImage(normalizedUrl) // 触发预缓存 } catch (cacheError) { console.warn(`预缓存失败: ${cacheError}`) } } } - // 如果没有缓存,返回原始 URL - return url + + return url // 不符合缓存条件,直接返回原始 URL } catch (error) { console.warn('获取缓存图片 URL 失败:', error) return url @@ -435,7 +437,7 @@ class ImageCacheManager { // 只从缓存获取本地资源 if (this.isBuildAsset(normalizedUrl)) { - const cache = await this.getCacheForUrl(normalizedUrl) // 使用相同的逻辑选择缓存 + const cache = await this.getCacheForUrl(normalizedUrl) // 选中对应缓存 // 尝试多种匹配方式 let cachedResponse = await cache.match( @@ -471,44 +473,39 @@ class ImageCacheManager { // 1. 请求去重:检查是否有相同请求正在进行 if (this.pendingRequests.has(normalizedSrc)) { - // 返回现有的Promise,避免重复请求 - return this.pendingRequests.get(normalizedSrc) + return this.pendingRequests.get(normalizedSrc) // 返回对应的Promise,避免重复请求 } - // 2. 创建新的加载Promise + // 2. 请求列表中不存在,则创建新的加载Promise const loadPromise = new Promise(async (resolve, reject) => { try { - // 3. 尝试从缓存获取 - const cachedResponse = await this.getCachedImage(normalizedSrc) + const cachedResponse = await this.getCachedImage(normalizedSrc) // 3. 获取对应的缓存图片 + // 4. 缓存命中,处理图片 if (cachedResponse) { - // 4. 缓存命中,处理图片 const blob = await cachedResponse.blob() const imageUrl = URL.createObjectURL(blob) - // 5. 创建Image对象并设置src - const img = new Image() + const img = new Image() // 5. 创建Image对象并设置src + + // 6. 加载成功,清理临时URL并resolve img.onload = () => { - // 6. 加载成功,清理临时URL并resolve - URL.revokeObjectURL(imageUrl) + URL.revokeObjectURL(imageUrl) // 清理临时URL resolve(img) } // 7. 关键:加载失败时的处理 img.onerror = () => { - // 8. 清理临时URL - URL.revokeObjectURL(imageUrl) - // 9. 回退到网络加载 - this.loadImageFromNetwork(normalizedSrc, resolve, reject) + URL.revokeObjectURL(imageUrl) // 8. 清理临时URL + this.loadImageFromNetwork(normalizedSrc, resolve, reject) // 9. 回退到网络加载 } img.src = imageUrl // 10. 开始加载 return } - // 11. 缓存中没有或不是本地资源,从网络加载 - this.loadImageFromNetwork(normalizedSrc, resolve, reject) + this.loadImageFromNetwork(normalizedSrc, resolve, reject) // 11. 缓存中没有或不是本地资源,从网络加载 } catch (error) { - this.loadImageFromNetwork(normalizedSrc, resolve, reject) + this.loadImageFromNetwork(normalizedSrc, resolve, reject) // 12. 网络加载失败,回退到网络加载 } }) @@ -516,11 +513,10 @@ class ImageCacheManager { this.pendingRequests.set(normalizedSrc, loadPromise) try { - const result = await loadPromise + const result = await loadPromise // 等待加载完成 return result } finally { - // 13. 清理记录 - this.pendingRequests.delete(normalizedSrc) + this.pendingRequests.delete(normalizedSrc) // 13. 清理记录 } } @@ -537,23 +533,24 @@ class ImageCacheManager { cache: 'no-store', // 强制不使用 HTTP 缓存 }) + // 加载完成 if (response.ok) { // 克隆响应,一份用于缓存,一份用于图片加载 - const isAsset = this.isBuildAsset(normalizedSrc) - const responseForCache = isAsset ? response.clone() : null + const isAsset = this.isBuildAsset(normalizedSrc) // 检查是否为适合缓存的资源 + const responseForCache = isAsset ? response.clone() : null //复制适合缓存的资源 const responseForImage = response // 原始响应用于图片加载 - // 先缓存响应 + // 适合缓存 if (isAsset) { try { - // 使用 getCacheForUrl 确保使用正确的缓存 - const cache = await this.getCacheForUrl(normalizedSrc) - await cache.put(normalizedSrc, responseForCache) // 使用克隆的响应进行缓存 + const cache = await this.getCacheForUrl(normalizedSrc) // 获取对应的缓存 + await cache.put(normalizedSrc, responseForCache) // 缓存克隆的响应 } catch (cacheError) { console.warn('缓存图片时出错:', cacheError) } } + // 创建Image对象并设置src const img = new Image() img.onload = () => { resolve(img) @@ -562,7 +559,9 @@ class ImageCacheManager { reject(error) } img.src = URL.createObjectURL(await responseForImage.blob()) // 使用原始响应 - } else { + } + // 加载失败 + else { reject(new Error(`HTTP ${response.status}`)) } } catch (error) { @@ -570,7 +569,7 @@ class ImageCacheManager { } } - // 添加辅助方法来获取缓存名称 + // 获取当前版本的缓存名称 getCacheNameByUrl(url) { const normalizedUrl = this.normalizeUrl(url) @@ -585,43 +584,6 @@ class ImageCacheManager { return this.imageCacheName } } - - // 为OSS图片创建缓存的特殊处理 - async cacheImageFromImage(src, img) { - try { - // 检查浏览器是否支持缓存API以及资源是否适合缓存 - if (!('caches' in window) || !this.isBuildAsset(src)) return - - const cache = await this.getCacheForUrl(src) - const cachedResponse = await cache.match(src) - - // 只有当图片尚未缓存时才进行缓存操作 - if (!cachedResponse) { - // 创建canvas元素来处理图片 - const canvas = document.createElement('canvas') - canvas.width = img.width - canvas.height = img.height - const ctx = canvas.getContext('2d') - ctx.drawImage(img, 0, 0) // 将图片绘制到canvas上 - - // 将canvas转换为blob对象 - canvas.toBlob(async (blob) => { - if (blob) { - // 创建一个新的Response对象来模拟网络响应 - const response = new Response(blob, { - headers: { - 'Content-Type': 'image/png', - }, - }) - // 将图片存入缓存 - await cache.put(src, response.clone()) - } - }) - } - } catch (error) { - console.warn('缓存OSS图片失败:', src, error) - } - } } // 导出单例 diff --git a/src/utils/versionChecker.js b/src/utils/versionChecker.js index 6e9c47d..500b2cf 100644 --- a/src/utils/versionChecker.js +++ b/src/utils/versionChecker.js @@ -13,37 +13,48 @@ const PERSISTENT_KEYS = [ export class VersionChecker { constructor() { - this.currentVersion = import.meta.env.VITE_APP_VERSION || Date.now().toString() - this.storageKey = 'h5_app_version' - this.serverVersionKey = 'app_server_version' - this.enableCheck = import.meta.env.VITE_ENABLE_VERSION_CHECK === 'true' + this.currentVersion = import.meta.env.VITE_APP_VERSION || Date.now().toString() //app版本号 + this.storageKey = 'h5_app_version' //本地存储的版本号key + this.serverVersionKey = 'app_server_version' //服务器版本号key + this.enableCheck = import.meta.env.VITE_ENABLE_VERSION_CHECK === 'true' //是否开启检测app版本号 } - // 检查服务器版本 + // 检查版本(开发环境:本地.env文件;生产环境:构建脚本生成的version.json文件) async checkServerVersion() { if (!this.enableCheck) { return false } try { - // 强制从服务器获取最新版本信息,避免缓存 - const response = await fetch(`/version.json?t=${Date.now()}`, { - cache: 'no-cache', - headers: { - 'Cache-Control': 'no-cache', - Pragma: 'no-cache', - }, - }) + let versionInfo //版本更新信息 - if (!response.ok) { - console.warn('无法获取服务器版本信息') - return false + // 开发环境:使用本地.env文件环境变量 + if (import.meta.env.MODE === 'development') { + versionInfo = { + version: import.meta.env.VITE_APP_VERSION, + type: import.meta.env.VITE_VERSION_TYPE || 'patch', + } + } + // 生产环境:从服务器获取version.json文件 + else { + const response = await fetch(`/version.json?t=${Date.now()}`) + if (!response.ok) { + console.warn('无法获取服务器版本信息') + return false + } + + const contentType = response.headers.get('content-type') + if (!contentType || !contentType.includes('application/json')) { + console.warn('服务器返回的不是JSON格式') + return false + } + + versionInfo = await response.json() //获取服务器版本信息 } - const versionInfo = await response.json() - const serverVersion = versionInfo.version - const versionType = versionInfo.type || 'patch' // 获取版本类型 - const storedServerVersion = localStorage.getItem(this.serverVersionKey) + const serverVersion = versionInfo.version //最新生成的服务器版本号 + const versionType = versionInfo.type || 'patch' // 版本更新类型 + const storedServerVersion = localStorage.getItem(this.serverVersionKey) //网页缓存的版本号 // 更新服务器版本信息 localStorage.setItem(this.serverVersionKey, serverVersion) @@ -61,16 +72,17 @@ export class VersionChecker { return { shouldUpdate: false } } catch (error) { - console.warn('版本检查失败:', error) + console.warn('版本检查失败:', error.message) return { shouldUpdate: false } } } - // 检查版本更新(综合方法) + // 初始化网页首次检查版本 async checkVersion() { - // 首先检查服务器版本 + // 获取最新版本信息 const versionResult = await this.checkServerVersion() + // 判断是否需要更新 if (versionResult.shouldUpdate) { await this.handleVersionUpdate(versionResult.versionType) return true @@ -118,10 +130,10 @@ export class VersionChecker { this.forceReload() } - // 添加专门处理 session 数据的方法 + // 清除所有会话存储数据sessionStorage clearSessionData() { try { - // 保存需要保留的数据 + // localStorage和Cache API不受影响 const preservedData = {} PERSISTENT_KEYS.forEach((key) => { const value = localStorage.getItem(key) @@ -142,8 +154,14 @@ export class VersionChecker { } } - // 建议改为 async/await 风格以保持一致性 async clearCache() { + // 清除范围: + // localStorage:所有本地存储数据 + // sessionStorage:所有会话存储数据 + // Cache API:所有缓存(包括非图片缓存) + // Service Worker:注销所有注册 + // 保留数据:保留 PERSISTENT_KEYS 中定义的白名单数据 + try { // 保存需要保留的数据 const preservedData = {}