/** * 头像缓存管理工具类 * 实现头像图片的KV存储和缓存机制,避免重复下载相同图片 */ class AvatarCache { private static instance: AvatarCache; private cache: Map = new Map(); // KV存储:avatarUrl -> 本地图片路径 private downloadingUrls: Set = new Set(); // 正在下载的URL集合 private constructor() {} public static getInstance(): AvatarCache { if (!AvatarCache.instance) { AvatarCache.instance = new AvatarCache(); } return AvatarCache.instance; } /** * 获取头像图片路径 * @param avatarUrl 头像URL * @returns 本地图片路径或Promise */ public async getAvatarPath(avatarUrl: string): Promise { if (!avatarUrl) { return this.getDefaultAvatarPath(); } // 如果缓存中已存在,直接返回 if (this.cache.has(avatarUrl)) { return this.cache.get(avatarUrl)!; } // 如果正在下载中,等待下载完成 if (this.downloadingUrls.has(avatarUrl)) { return await this.waitForDownload(avatarUrl); } // 开始下载图片 return await this.downloadAndCacheAvatar(avatarUrl); } /** * 获取头像URL(用于存储和传递,不下载图片) * @param userId 用户ID * @param avatarUrl 头像URL * @returns 头像URL */ public getAvatarUrl(avatarUrl: string): string { if (!avatarUrl) { return '/images/user-avatar.png'; } // 如果缓存中已存在,直接返回URL(不下载) if (this.cache.has(avatarUrl)) { return avatarUrl; } // 如果不在缓存中,异步下载但不等待结果 if (!this.downloadingUrls.has(avatarUrl)) { this.downloadAndCacheAvatar(avatarUrl).catch(() => { // 下载失败不影响主流程 }); } return avatarUrl; } /** * 下载并缓存头像图片 * @param avatarUrl 头像URL * @returns 本地图片路径 */ private async downloadAndCacheAvatar(avatarUrl: string): Promise { this.downloadingUrls.add(avatarUrl); try { // 下载图片到临时文件 const tempFilePath = await this.downloadImage(avatarUrl); // 缓存到KV存储 this.cache.set(avatarUrl, tempFilePath); return tempFilePath; } catch (error) { console.error(`下载头像失败: ${avatarUrl}`, error); return this.getDefaultAvatarPath(); } finally { this.downloadingUrls.delete(avatarUrl); } } /** * 下载图片到本地临时文件 * @param avatarUrl 头像URL * @returns 本地临时文件路径 */ private async downloadImage(avatarUrl: string): Promise { return new Promise((resolve, reject) => { wx.downloadFile({ url: avatarUrl, success: (res) => { if (res.statusCode === 200) { resolve(res.tempFilePath); } else { reject(new Error(`下载失败,状态码: ${res.statusCode}`)); } }, fail: (error) => { reject(error); } }); }); } /** * 等待下载完成 * @param avatarUrl 头像URL * @returns 本地图片路径 */ private async waitForDownload(avatarUrl: string): Promise { return new Promise((resolve) => { const checkInterval = setInterval(() => { if (!this.downloadingUrls.has(avatarUrl) && this.cache.has(avatarUrl)) { clearInterval(checkInterval); resolve(this.cache.get(avatarUrl)!); } }, 100); // 设置超时时间 setTimeout(() => { clearInterval(checkInterval); resolve(this.getDefaultAvatarPath()); }, 5000); }); } /** * 获取默认头像路径 * @returns 默认头像路径 */ private getDefaultAvatarPath(): string { return '/images/user-avatar.png'; } /** * 预加载多个头像 * @param avatarUrls 头像URL数组 */ public async preloadAvatars(avatarUrls: string[]): Promise { const uniqueUrls = [...new Set(avatarUrls)]; for (const url of uniqueUrls) { if (url && !this.cache.has(url) && !this.downloadingUrls.has(url)) { // 异步预加载,不等待结果 this.downloadAndCacheAvatar(url).catch(() => { // 预加载失败不影响主流程 }); } } } /** * 预加载多个头像并等待所有加载完成 * @param avatarUrls 头像URL数组 */ public async preloadAvatarsAndWait(avatarUrls: string[]): Promise { const uniqueUrls = [...new Set(avatarUrls)]; const downloadPromises: Promise[] = []; for (const url of uniqueUrls) { if (url && !this.cache.has(url) && !this.downloadingUrls.has(url)) { // 同步预加载,等待结果 downloadPromises.push(this.downloadAndCacheAvatar(url)); } } if (downloadPromises.length > 0) { console.log(`🔄 [AvatarCache] 开始预加载 ${downloadPromises.length} 个头像`); await Promise.allSettled(downloadPromises); console.log(`✅ [AvatarCache] 头像预加载完成,缓存大小: ${this.cache.size}`); } else { console.log('ℹ️ [AvatarCache] 没有需要预加载的头像'); } } /** * 清除缓存 */ public clearCache(): void { this.cache.clear(); } /** * 获取缓存统计信息 */ public getCacheStats(): { size: number; downloading: number } { return { size: this.cache.size, downloading: this.downloadingUrls.size }; } } // 导出单例实例 export const avatarCache = AvatarCache.getInstance(); export default AvatarCache;