209 lines
5.6 KiB
TypeScript
209 lines
5.6 KiB
TypeScript
|
|
/**
|
|||
|
|
* 头像缓存管理工具类
|
|||
|
|
* 实现头像图片的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;
|