Initial Commit
This commit is contained in:
commit
1d71a02738
237 changed files with 64293 additions and 0 deletions
833
utils/media-manager.js
Normal file
833
utils/media-manager.js
Normal file
|
|
@ -0,0 +1,833 @@
|
|||
// 媒体文件管理器 - 微信小程序专用
|
||||
// 处理图片、视频、音频、文件的上传、下载、预览、缓存等
|
||||
|
||||
const apiClient = require('./api-client.js');
|
||||
|
||||
/**
|
||||
* 媒体文件管理器
|
||||
* 功能:
|
||||
* 1. 文件选择和上传
|
||||
* 2. 媒体文件预览
|
||||
* 3. 文件下载和缓存
|
||||
* 4. 文件压缩和优化
|
||||
* 5. 云存储管理
|
||||
* 6. 文件类型检测
|
||||
*/
|
||||
class MediaManager {
|
||||
constructor() {
|
||||
this.isInitialized = false;
|
||||
|
||||
// 媒体配置
|
||||
this.mediaConfig = {
|
||||
// 图片配置
|
||||
image: {
|
||||
maxSize: 10 * 1024 * 1024, // 10MB
|
||||
maxCount: 9, // 最多选择9张
|
||||
quality: 80, // 压缩质量
|
||||
formats: ['jpg', 'jpeg', 'png', 'gif', 'webp'],
|
||||
compressWidth: 1080 // 压缩宽度
|
||||
},
|
||||
|
||||
// 视频配置
|
||||
video: {
|
||||
maxSize: 100 * 1024 * 1024, // 100MB
|
||||
maxDuration: 300, // 最长5分钟
|
||||
formats: ['mp4', 'mov', 'avi'],
|
||||
compressQuality: 'medium'
|
||||
},
|
||||
|
||||
// 音频配置
|
||||
audio: {
|
||||
maxSize: 20 * 1024 * 1024, // 20MB
|
||||
maxDuration: 600, // 最长10分钟
|
||||
formats: ['mp3', 'wav', 'aac', 'm4a'],
|
||||
sampleRate: 16000
|
||||
},
|
||||
|
||||
// 文件配置
|
||||
file: {
|
||||
maxSize: 50 * 1024 * 1024, // 50MB
|
||||
allowedTypes: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'zip', 'rar'],
|
||||
maxCount: 5
|
||||
},
|
||||
|
||||
// 缓存配置
|
||||
cache: {
|
||||
maxSize: 200 * 1024 * 1024, // 200MB
|
||||
expireTime: 7 * 24 * 60 * 60 * 1000, // 7天
|
||||
cleanupInterval: 24 * 60 * 60 * 1000 // 24小时清理一次
|
||||
}
|
||||
};
|
||||
|
||||
// 文件缓存
|
||||
this.fileCache = new Map();
|
||||
|
||||
// 上传队列
|
||||
this.uploadQueue = [];
|
||||
|
||||
// 下载队列
|
||||
this.downloadQueue = [];
|
||||
|
||||
// 当前上传任务
|
||||
this.currentUploads = new Map();
|
||||
|
||||
// 缓存统计
|
||||
this.cacheStats = {
|
||||
totalSize: 0,
|
||||
fileCount: 0,
|
||||
lastCleanup: 0
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
// 初始化媒体管理器
|
||||
async init() {
|
||||
if (this.isInitialized) return;
|
||||
|
||||
console.log('📁 初始化媒体文件管理器...');
|
||||
|
||||
try {
|
||||
// 加载文件缓存信息
|
||||
await this.loadCacheInfo();
|
||||
|
||||
// 检查存储权限
|
||||
await this.checkStoragePermission();
|
||||
|
||||
// 启动缓存清理
|
||||
this.startCacheCleanup();
|
||||
|
||||
this.isInitialized = true;
|
||||
console.log('✅ 媒体文件管理器初始化完成');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 媒体文件管理器初始化失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 📷 ===== 图片处理 =====
|
||||
|
||||
// 选择图片
|
||||
async chooseImages(options = {}) {
|
||||
try {
|
||||
const {
|
||||
count = this.mediaConfig.image.maxCount,
|
||||
sizeType = ['compressed', 'original'],
|
||||
sourceType = ['album', 'camera']
|
||||
} = options;
|
||||
|
||||
console.log('📷 选择图片...');
|
||||
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
wx.chooseImage({
|
||||
count: count,
|
||||
sizeType: sizeType,
|
||||
sourceType: sourceType,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
|
||||
// 验证和处理图片
|
||||
const processedImages = await this.processImages(result.tempFilePaths);
|
||||
|
||||
console.log(`📷 选择了 ${processedImages.length} 张图片`);
|
||||
return {
|
||||
success: true,
|
||||
images: processedImages
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 选择图片失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.errMsg || '选择图片失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 处理图片
|
||||
async processImages(tempFilePaths) {
|
||||
const processedImages = [];
|
||||
|
||||
for (const tempPath of tempFilePaths) {
|
||||
try {
|
||||
// 获取图片信息
|
||||
const imageInfo = await this.getImageInfo(tempPath);
|
||||
|
||||
// 检查文件大小
|
||||
if (imageInfo.size > this.mediaConfig.image.maxSize) {
|
||||
console.warn('⚠️ 图片过大,需要压缩:', imageInfo.size);
|
||||
// 压缩图片
|
||||
const compressedPath = await this.compressImage(tempPath);
|
||||
imageInfo.tempFilePath = compressedPath;
|
||||
}
|
||||
|
||||
// 生成缩略图
|
||||
const thumbnailPath = await this.generateThumbnail(imageInfo.tempFilePath);
|
||||
|
||||
processedImages.push({
|
||||
...imageInfo,
|
||||
thumbnailPath: thumbnailPath,
|
||||
type: 'image',
|
||||
status: 'ready'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 处理图片失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return processedImages;
|
||||
}
|
||||
|
||||
// 获取图片信息
|
||||
async getImageInfo(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.getImageInfo({
|
||||
src: src,
|
||||
success: (res) => {
|
||||
// 获取文件大小
|
||||
wx.getFileInfo({
|
||||
filePath: src,
|
||||
success: (fileInfo) => {
|
||||
resolve({
|
||||
...res,
|
||||
size: fileInfo.size,
|
||||
tempFilePath: src
|
||||
});
|
||||
},
|
||||
fail: () => {
|
||||
resolve({
|
||||
...res,
|
||||
size: 0,
|
||||
tempFilePath: src
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 压缩图片
|
||||
async compressImage(src) {
|
||||
try {
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
wx.compressImage({
|
||||
src: src,
|
||||
quality: this.mediaConfig.image.quality,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
|
||||
console.log('📷 图片压缩完成');
|
||||
return result.tempFilePath;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 图片压缩失败:', error);
|
||||
return src; // 压缩失败返回原图
|
||||
}
|
||||
}
|
||||
|
||||
// 生成缩略图
|
||||
async generateThumbnail(src) {
|
||||
try {
|
||||
// 创建canvas生成缩略图
|
||||
const canvas = wx.createOffscreenCanvas({ type: '2d' });
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 设置缩略图尺寸
|
||||
const thumbnailSize = 200;
|
||||
canvas.width = thumbnailSize;
|
||||
canvas.height = thumbnailSize;
|
||||
|
||||
// 加载图片
|
||||
const image = canvas.createImage();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
image.onload = () => {
|
||||
// 计算绘制尺寸
|
||||
const { drawWidth, drawHeight, drawX, drawY } = this.calculateDrawSize(
|
||||
image.width,
|
||||
image.height,
|
||||
thumbnailSize,
|
||||
thumbnailSize
|
||||
);
|
||||
|
||||
// 绘制缩略图
|
||||
ctx.drawImage(image, drawX, drawY, drawWidth, drawHeight);
|
||||
|
||||
// 导出为临时文件
|
||||
wx.canvasToTempFilePath({
|
||||
canvas: canvas,
|
||||
success: (res) => {
|
||||
resolve(res.tempFilePath);
|
||||
},
|
||||
fail: () => {
|
||||
resolve(src); // 生成失败返回原图
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
image.onerror = () => {
|
||||
resolve(src); // 加载失败返回原图
|
||||
};
|
||||
|
||||
image.src = src;
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 生成缩略图失败:', error);
|
||||
return src;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算绘制尺寸
|
||||
calculateDrawSize(imageWidth, imageHeight, canvasWidth, canvasHeight) {
|
||||
const imageRatio = imageWidth / imageHeight;
|
||||
const canvasRatio = canvasWidth / canvasHeight;
|
||||
|
||||
let drawWidth, drawHeight, drawX, drawY;
|
||||
|
||||
if (imageRatio > canvasRatio) {
|
||||
// 图片更宽,以高度为准
|
||||
drawHeight = canvasHeight;
|
||||
drawWidth = drawHeight * imageRatio;
|
||||
drawX = (canvasWidth - drawWidth) / 2;
|
||||
drawY = 0;
|
||||
} else {
|
||||
// 图片更高,以宽度为准
|
||||
drawWidth = canvasWidth;
|
||||
drawHeight = drawWidth / imageRatio;
|
||||
drawX = 0;
|
||||
drawY = (canvasHeight - drawHeight) / 2;
|
||||
}
|
||||
|
||||
return { drawWidth, drawHeight, drawX, drawY };
|
||||
}
|
||||
|
||||
// 🎬 ===== 视频处理 =====
|
||||
|
||||
// 选择视频
|
||||
async chooseVideo(options = {}) {
|
||||
try {
|
||||
const {
|
||||
sourceType = ['album', 'camera'],
|
||||
maxDuration = this.mediaConfig.video.maxDuration,
|
||||
camera = 'back'
|
||||
} = options;
|
||||
|
||||
console.log('🎬 选择视频...');
|
||||
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
wx.chooseVideo({
|
||||
sourceType: sourceType,
|
||||
maxDuration: maxDuration,
|
||||
camera: camera,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
|
||||
// 验证和处理视频
|
||||
const processedVideo = await this.processVideo(result);
|
||||
|
||||
console.log('🎬 选择视频完成');
|
||||
return {
|
||||
success: true,
|
||||
video: processedVideo
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 选择视频失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.errMsg || '选择视频失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 处理视频
|
||||
async processVideo(videoResult) {
|
||||
try {
|
||||
// 检查文件大小
|
||||
if (videoResult.size > this.mediaConfig.video.maxSize) {
|
||||
throw new Error('视频文件过大');
|
||||
}
|
||||
|
||||
// 检查时长
|
||||
if (videoResult.duration > this.mediaConfig.video.maxDuration) {
|
||||
throw new Error('视频时长超出限制');
|
||||
}
|
||||
|
||||
// 生成视频缩略图
|
||||
const thumbnailPath = await this.generateVideoThumbnail(videoResult.tempFilePath);
|
||||
|
||||
return {
|
||||
tempFilePath: videoResult.tempFilePath,
|
||||
duration: videoResult.duration,
|
||||
size: videoResult.size,
|
||||
width: videoResult.width,
|
||||
height: videoResult.height,
|
||||
thumbnailPath: thumbnailPath,
|
||||
type: 'video',
|
||||
status: 'ready'
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 处理视频失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 生成视频缩略图
|
||||
async generateVideoThumbnail(videoPath) {
|
||||
try {
|
||||
// 微信小程序暂不支持视频帧提取,使用默认图标
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('❌ 生成视频缩略图失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 📄 ===== 文件处理 =====
|
||||
|
||||
// 选择文件
|
||||
async chooseFile(options = {}) {
|
||||
try {
|
||||
const {
|
||||
count = this.mediaConfig.file.maxCount,
|
||||
type = 'all'
|
||||
} = options;
|
||||
|
||||
console.log('📄 选择文件...');
|
||||
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
wx.chooseMessageFile({
|
||||
count: count,
|
||||
type: type,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
|
||||
// 验证和处理文件
|
||||
const processedFiles = await this.processFiles(result.tempFiles);
|
||||
|
||||
console.log(`📄 选择了 ${processedFiles.length} 个文件`);
|
||||
return {
|
||||
success: true,
|
||||
files: processedFiles
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 选择文件失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.errMsg || '选择文件失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 处理文件
|
||||
async processFiles(tempFiles) {
|
||||
const processedFiles = [];
|
||||
|
||||
for (const file of tempFiles) {
|
||||
try {
|
||||
// 检查文件大小
|
||||
if (file.size > this.mediaConfig.file.maxSize) {
|
||||
console.warn('⚠️ 文件过大:', file.name, file.size);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查文件类型
|
||||
const fileExtension = this.getFileExtension(file.name);
|
||||
if (!this.isAllowedFileType(fileExtension)) {
|
||||
console.warn('⚠️ 不支持的文件类型:', fileExtension);
|
||||
continue;
|
||||
}
|
||||
|
||||
processedFiles.push({
|
||||
tempFilePath: file.path,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: 'file',
|
||||
extension: fileExtension,
|
||||
status: 'ready'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 处理文件失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return processedFiles;
|
||||
}
|
||||
|
||||
// 获取文件扩展名
|
||||
getFileExtension(fileName) {
|
||||
const lastDotIndex = fileName.lastIndexOf('.');
|
||||
if (lastDotIndex === -1) return '';
|
||||
return fileName.substring(lastDotIndex + 1).toLowerCase();
|
||||
}
|
||||
|
||||
// 检查文件类型是否允许
|
||||
isAllowedFileType(extension) {
|
||||
return this.mediaConfig.file.allowedTypes.includes(extension);
|
||||
}
|
||||
|
||||
// 📤 ===== 文件上传 =====
|
||||
|
||||
// 上传文件
|
||||
async uploadFile(file, options = {}) {
|
||||
try {
|
||||
const {
|
||||
onProgress,
|
||||
onSuccess,
|
||||
onError
|
||||
} = options;
|
||||
|
||||
console.log('📤 开始上传文件:', file.name || 'unknown');
|
||||
|
||||
// 生成上传ID
|
||||
const uploadId = this.generateUploadId();
|
||||
|
||||
// 添加到上传队列
|
||||
const uploadTask = {
|
||||
id: uploadId,
|
||||
file: file,
|
||||
status: 'uploading',
|
||||
progress: 0,
|
||||
onProgress: onProgress,
|
||||
onSuccess: onSuccess,
|
||||
onError: onError
|
||||
};
|
||||
|
||||
this.currentUploads.set(uploadId, uploadTask);
|
||||
|
||||
// 执行上传
|
||||
const result = await this.performUpload(uploadTask);
|
||||
|
||||
// 移除上传任务
|
||||
this.currentUploads.delete(uploadId);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 上传文件失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 执行上传
|
||||
async performUpload(uploadTask) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const uploadTask_wx = wx.uploadFile({
|
||||
url: `${apiClient.baseURL}/api/v1/files/upload`,
|
||||
filePath: uploadTask.file.tempFilePath,
|
||||
name: 'file',
|
||||
header: {
|
||||
'Authorization': `Bearer ${wx.getStorageSync('token')}`
|
||||
},
|
||||
formData: {
|
||||
type: uploadTask.file.type,
|
||||
name: uploadTask.file.name || 'unknown'
|
||||
},
|
||||
success: (res) => {
|
||||
try {
|
||||
const data = JSON.parse(res.data);
|
||||
if (data.success) {
|
||||
console.log('✅ 文件上传成功:', data.data.url);
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
data: {
|
||||
url: data.data.url,
|
||||
fileId: data.data.fileId,
|
||||
fileName: uploadTask.file.name,
|
||||
fileSize: uploadTask.file.size,
|
||||
fileType: uploadTask.file.type
|
||||
}
|
||||
};
|
||||
|
||||
if (uploadTask.onSuccess) {
|
||||
uploadTask.onSuccess(result);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
} else {
|
||||
throw new Error(data.error || '上传失败');
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('❌ 上传请求失败:', error);
|
||||
if (uploadTask.onError) {
|
||||
uploadTask.onError(error);
|
||||
}
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听上传进度
|
||||
uploadTask_wx.onProgressUpdate((res) => {
|
||||
uploadTask.progress = res.progress;
|
||||
|
||||
if (uploadTask.onProgress) {
|
||||
uploadTask.onProgress({
|
||||
progress: res.progress,
|
||||
totalBytesSent: res.totalBytesSent,
|
||||
totalBytesExpectedToSend: res.totalBytesExpectedToSend
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 保存上传任务引用
|
||||
uploadTask.wxTask = uploadTask_wx;
|
||||
});
|
||||
}
|
||||
|
||||
// 取消上传
|
||||
cancelUpload(uploadId) {
|
||||
const uploadTask = this.currentUploads.get(uploadId);
|
||||
if (uploadTask && uploadTask.wxTask) {
|
||||
uploadTask.wxTask.abort();
|
||||
this.currentUploads.delete(uploadId);
|
||||
console.log('📤 取消上传:', uploadId);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成上传ID
|
||||
generateUploadId() {
|
||||
return `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
// 📥 ===== 文件下载和缓存 =====
|
||||
|
||||
// 下载文件
|
||||
async downloadFile(url, options = {}) {
|
||||
try {
|
||||
const {
|
||||
fileName,
|
||||
onProgress,
|
||||
useCache = true
|
||||
} = options;
|
||||
|
||||
console.log('📥 下载文件:', url);
|
||||
|
||||
// 检查缓存
|
||||
if (useCache) {
|
||||
const cachedPath = this.getCachedFilePath(url);
|
||||
if (cachedPath) {
|
||||
console.log('📥 使用缓存文件:', cachedPath);
|
||||
return {
|
||||
success: true,
|
||||
tempFilePath: cachedPath,
|
||||
cached: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 执行下载
|
||||
const result = await this.performDownload(url, { fileName, onProgress });
|
||||
|
||||
// 缓存文件
|
||||
if (result.success && useCache) {
|
||||
this.cacheFile(url, result.tempFilePath);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 下载文件失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 执行下载
|
||||
async performDownload(url, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const downloadTask = wx.downloadFile({
|
||||
url: url,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
console.log('✅ 文件下载成功');
|
||||
resolve({
|
||||
success: true,
|
||||
tempFilePath: res.tempFilePath,
|
||||
cached: false
|
||||
});
|
||||
} else {
|
||||
reject(new Error(`下载失败: ${res.statusCode}`));
|
||||
}
|
||||
},
|
||||
fail: reject
|
||||
});
|
||||
|
||||
// 监听下载进度
|
||||
if (options.onProgress) {
|
||||
downloadTask.onProgressUpdate((res) => {
|
||||
options.onProgress({
|
||||
progress: res.progress,
|
||||
totalBytesWritten: res.totalBytesWritten,
|
||||
totalBytesExpectedToWrite: res.totalBytesExpectedToWrite
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 检查存储权限
|
||||
async checkStoragePermission() {
|
||||
try {
|
||||
const storageInfo = wx.getStorageInfoSync();
|
||||
console.log('📁 存储信息:', storageInfo);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 检查存储权限失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载缓存信息
|
||||
async loadCacheInfo() {
|
||||
try {
|
||||
const cacheInfo = wx.getStorageSync('mediaCacheInfo') || {};
|
||||
this.cacheStats = {
|
||||
totalSize: cacheInfo.totalSize || 0,
|
||||
fileCount: cacheInfo.fileCount || 0,
|
||||
lastCleanup: cacheInfo.lastCleanup || 0
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('❌ 加载缓存信息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存缓存信息
|
||||
async saveCacheInfo() {
|
||||
try {
|
||||
wx.setStorageSync('mediaCacheInfo', this.cacheStats);
|
||||
} catch (error) {
|
||||
console.error('❌ 保存缓存信息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取缓存文件路径
|
||||
getCachedFilePath(url) {
|
||||
const cacheKey = this.generateCacheKey(url);
|
||||
return this.fileCache.get(cacheKey);
|
||||
}
|
||||
|
||||
// 缓存文件
|
||||
cacheFile(url, filePath) {
|
||||
const cacheKey = this.generateCacheKey(url);
|
||||
this.fileCache.set(cacheKey, filePath);
|
||||
|
||||
// 更新缓存统计
|
||||
this.cacheStats.fileCount++;
|
||||
this.saveCacheInfo();
|
||||
}
|
||||
|
||||
// 生成缓存键
|
||||
generateCacheKey(url) {
|
||||
// 使用URL的hash作为缓存键
|
||||
let hash = 0;
|
||||
for (let i = 0; i < url.length; i++) {
|
||||
const char = url.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // 转换为32位整数
|
||||
}
|
||||
return `cache_${Math.abs(hash)}`;
|
||||
}
|
||||
|
||||
// 启动缓存清理
|
||||
startCacheCleanup() {
|
||||
setInterval(() => {
|
||||
this.performCacheCleanup();
|
||||
}, this.mediaConfig.cache.cleanupInterval);
|
||||
}
|
||||
|
||||
// 执行缓存清理
|
||||
performCacheCleanup() {
|
||||
try {
|
||||
console.log('📁 执行缓存清理...');
|
||||
|
||||
const now = Date.now();
|
||||
const expireTime = this.mediaConfig.cache.expireTime;
|
||||
|
||||
// 清理过期缓存
|
||||
for (const [key, filePath] of this.fileCache) {
|
||||
try {
|
||||
const stats = wx.getFileInfo({ filePath });
|
||||
if (now - stats.createTime > expireTime) {
|
||||
this.fileCache.delete(key);
|
||||
// 删除文件
|
||||
wx.removeSavedFile({ filePath });
|
||||
}
|
||||
} catch (error) {
|
||||
// 文件不存在,从缓存中移除
|
||||
this.fileCache.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新清理时间
|
||||
this.cacheStats.lastCleanup = now;
|
||||
this.saveCacheInfo();
|
||||
|
||||
console.log('✅ 缓存清理完成');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 缓存清理失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取媒体管理器状态
|
||||
getStatus() {
|
||||
return {
|
||||
isInitialized: this.isInitialized,
|
||||
uploadCount: this.currentUploads.size,
|
||||
cacheStats: { ...this.cacheStats },
|
||||
config: this.mediaConfig
|
||||
};
|
||||
}
|
||||
|
||||
// 清除所有缓存
|
||||
clearAllCache() {
|
||||
this.fileCache.clear();
|
||||
this.cacheStats = {
|
||||
totalSize: 0,
|
||||
fileCount: 0,
|
||||
lastCleanup: Date.now()
|
||||
};
|
||||
this.saveCacheInfo();
|
||||
console.log('📁 已清除所有媒体缓存');
|
||||
}
|
||||
|
||||
// 重置管理器
|
||||
reset() {
|
||||
// 取消所有上传任务
|
||||
for (const [uploadId] of this.currentUploads) {
|
||||
this.cancelUpload(uploadId);
|
||||
}
|
||||
|
||||
this.clearAllCache();
|
||||
}
|
||||
}
|
||||
|
||||
// 创建全局实例
|
||||
const mediaManager = new MediaManager();
|
||||
|
||||
module.exports = mediaManager;
|
||||
Loading…
Add table
Add a link
Reference in a new issue