Files
WXProgram/miniprogram/utils/avatarCache.ts
2025-10-26 13:15:04 +08:00

209 lines
5.6 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

/**
* 头像缓存管理工具类
* 实现头像图片的KV存储和缓存机制避免重复下载相同图片
*/
class AvatarCache {
private static instance: AvatarCache;
private cache: Map<string, string> = new Map(); // KV存储avatarUrl -> 本地图片路径
private downloadingUrls: Set<string> = new Set(); // 正在下载的URL集合
private constructor() {}
public static getInstance(): AvatarCache {
if (!AvatarCache.instance) {
AvatarCache.instance = new AvatarCache();
}
return AvatarCache.instance;
}
/**
* 获取头像图片路径
* @param avatarUrl 头像URL
* @returns 本地图片路径或Promise<string>
*/
public async getAvatarPath(avatarUrl: string): Promise<string> {
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<string> {
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<string> {
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<string> {
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<void> {
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<void> {
const uniqueUrls = [...new Set(avatarUrls)];
const downloadPromises: Promise<string>[] = [];
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;