findme-miniprogram-frontend/utils/image-cache-manager.js
2025-12-27 17:16:03 +08:00

296 lines
6.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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.

/**
* 图片缓存管理器
* 实现头像和聊天图片的缓存功能
*/
class ImageCacheManager {
constructor() {
this.cache = new Map();
this.cacheExpiry = new Map();
this.maxCacheSize = 100; // 最大缓存数量
this.cacheExpiryTime = 7 * 24 * 60 * 60 * 1000; // 7天缓存过期时间
this.avatarCacheExpiryTime = 30 * 24 * 60 * 60 * 1000; // 头像30天缓存过期时间
// 初始化缓存
this.initCache();
}
/**
* 初始化缓存,从本地存储加载
*/
initCache() {
try {
const cachedData = wx.getStorageSync('image_cache');
if (cachedData) {
const data = JSON.parse(cachedData);
this.cache = new Map(data.cache || []);
this.cacheExpiry = new Map(data.expiry || []);
}
} catch (error) {
console.error('❌ 初始化图片缓存失败:', error);
}
}
/**
* 保存缓存到本地存储
*/
saveCache() {
try {
const data = {
cache: Array.from(this.cache.entries()),
expiry: Array.from(this.cacheExpiry.entries())
};
wx.setStorageSync('image_cache', JSON.stringify(data));
} catch (error) {
console.error('❌ 保存图片缓存失败:', error);
}
}
/**
* 清理过期缓存
*/
cleanExpiredCache() {
const now = Date.now();
const expiredKeys = [];
for (const [key, expiryTime] of this.cacheExpiry.entries()) {
if (now > expiryTime) {
expiredKeys.push(key);
}
}
expiredKeys.forEach(key => {
this.cache.delete(key);
this.cacheExpiry.delete(key);
});
if (expiredKeys.length > 0) {
this.saveCache();
}
}
/**
* 清理超出最大数量的缓存
*/
cleanExcessCache() {
if (this.cache.size <= this.maxCacheSize) return;
// 按过期时间排序,删除最旧的
const sortedEntries = Array.from(this.cacheExpiry.entries())
.sort((a, b) => a[1] - b[1]);
const excessCount = this.cache.size - this.maxCacheSize;
const keysToDelete = sortedEntries.slice(0, excessCount).map(([key]) => key);
keysToDelete.forEach(key => {
this.cache.delete(key);
this.cacheExpiry.delete(key);
});
this.saveCache();
}
/**
* 生成缓存键
*/
generateCacheKey(url, type = 'image') {
return `${type}_${url}`;
}
/**
* 检查缓存是否存在且有效
*/
isCached(url, type = 'image') {
const key = this.generateCacheKey(url, type);
const expiryTime = this.cacheExpiry.get(key);
if (!expiryTime) return false;
if (Date.now() > expiryTime) {
// 缓存已过期,删除
this.cache.delete(key);
this.cacheExpiry.delete(key);
return false;
}
return this.cache.has(key);
}
/**
* 获取缓存的图片路径
*/
getCachedPath(url, type = 'image') {
const key = this.generateCacheKey(url, type);
return this.cache.get(key);
}
/**
* 缓存图片
*/
async cacheImage(url, type = 'image') {
if (!url || url.startsWith('data:')) {
return url; // 不缓存base64等
}
// 对于本地路径前缀,直接返回
if (url.startsWith('/')) {
return url;
}
// 确保URL是完整的添加https前缀如果缺少
let fullUrl = url;
if (!fullUrl.startsWith('http://') && !fullUrl.startsWith('https://')) {
fullUrl = 'https://' + url;
}
const key = this.generateCacheKey(fullUrl, type);
// 检查是否已缓存
if (this.isCached(fullUrl, type)) {
return this.getCachedPath(fullUrl, type);
}
try {
// 下载图片
const downloadResult = await new Promise((resolve, reject) => {
wx.downloadFile({
url: fullUrl,
timeout: 15000, // 增加超时时间
success: resolve,
fail: reject
});
});
if (downloadResult.statusCode === 200) {
const localPath = downloadResult.tempFilePath;
// 设置缓存过期时间
const expiryTime = type === 'avatar'
? Date.now() + this.avatarCacheExpiryTime
: Date.now() + this.cacheExpiryTime;
// 保存到缓存
this.cache.set(key, localPath);
this.cacheExpiry.set(key, expiryTime);
// 清理过期和超量缓存
this.cleanExpiredCache();
this.cleanExcessCache();
return localPath;
} else {
console.warn('⚠️ 图片下载失败,状态码:', downloadResult.statusCode, 'URL:', fullUrl);
// 如果下载失败返回完整的URL
return fullUrl;
}
} catch (error) {
console.error('❌ 缓存图片失败:', fullUrl, error);
// 在缓存失败时返回完整的URL
return fullUrl;
}
}
/**
* 缓存头像
*/
async cacheAvatar(url) {
return this.cacheImage(url, 'avatar');
}
/**
* 更新头像缓存当头像URL变化时
*/
async updateAvatarCache(oldUrl, newUrl) {
if (oldUrl && oldUrl !== newUrl) {
const oldKey = this.generateCacheKey(oldUrl, 'avatar');
this.cache.delete(oldKey);
this.cacheExpiry.delete(oldKey);
}
if (newUrl) {
return await this.cacheAvatar(newUrl);
}
return newUrl;
}
/**
* 预加载图片(用于聊天中的图片)
*/
async preloadImage(url) {
return this.cacheImage(url, 'chat_image');
}
/**
* 获取缓存统计信息
*/
getCacheStats() {
const now = Date.now();
let avatarCount = 0;
let imageCount = 0;
let expiredCount = 0;
for (const [key, expiryTime] of this.cacheExpiry.entries()) {
if (key.startsWith('avatar_')) {
avatarCount++;
} else if (key.startsWith('image_') || key.startsWith('chat_image_')) {
imageCount++;
}
if (now > expiryTime) {
expiredCount++;
}
}
return {
total: this.cache.size,
avatar: avatarCount,
image: imageCount,
expired: expiredCount,
maxSize: this.maxCacheSize
};
}
/**
* 清空所有缓存
*/
clearAllCache() {
this.cache.clear();
this.cacheExpiry.clear();
this.saveCache();
}
/**
* 清理指定类型的缓存
*/
clearCacheByType(type) {
const keysToDelete = [];
for (const key of this.cache.keys()) {
if (key.startsWith(`${type}_`)) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(key => {
this.cache.delete(key);
this.cacheExpiry.delete(key);
});
this.saveCache();
}
}
// 创建单例实例
const imageCacheManager = new ImageCacheManager();
module.exports = imageCacheManager;