Files
WXProgram/miniprogram/utils/avatarCache.ts

209 lines
5.6 KiB
TypeScript
Raw Permalink Normal View History

2025-10-26 13:15:04 +08:00
/**
*
* 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;