// COS上传工具类 const COS = require('./cos-wx-sdk-v5.min.js') const app = getApp() /** * COS上传工具类 * 注意:需要先保存后台数据,再上传文件,避免孤立的COS资源 */ class CosUtil { constructor() { this.cos = null this.initCOS() } /** * 初始化COS实例 */ initCOS() { const cosConfig = app.globalData.cos if (!cosConfig) { console.error('COS配置未找到') return } this.cos = new COS({ SecretId: cosConfig.secretId, SecretKey: cosConfig.secretKey, // 可选,设置请求域名 Domain: `${cosConfig.bucket}.cos.${cosConfig.region}.myqcloud.com`, // 可选,设置上传时计算 md5 UploadCheckContentMd5: true }) console.log('COS实例初始化成功') } /** * 生成文件路径(按日期分目录,与后台保持一致) * @param {string} fileName 文件名 * @param {string} fileType 文件类型 avatar/image/video/attachment * @returns {string} 文件路径 */ generateFilePath(fileName, fileType = 'image') { const now = new Date() const dateDir = now.getFullYear() + String(now.getMonth() + 1).padStart(2, '0') + String(now.getDate()).padStart(2, '0') // 根据文件类型添加前缀 let prefix = '' switch (fileType) { case 'avatar': prefix = 'avatars/' break case 'video': prefix = 'videos/' break case 'attachment': prefix = 'attachments/' break default: prefix = 'images/' } return `${prefix}${dateDir}/${fileName}` } /** * 生成唯一文件名 * @param {string} originalName 原始文件名 * @returns {string} 唯一文件名 */ generateUniqueFileName(originalName) { const timestamp = Date.now() const random = Math.random().toString(36).substring(2, 8) const extension = originalName.substring(originalName.lastIndexOf('.')) return `${timestamp}_${random}${extension}` } /** * 上传文件到COS * @param {string} filePath 本地文件路径 * @param {string} fileType 文件类型 avatar/image/video/attachment * @param {string} originalName 原始文件名 * @param {function} onProgress 进度回调 * @returns {Promise} 返回上传结果 */ uploadFile(filePath, fileType = 'image', originalName = '', onProgress = null) { return new Promise((resolve, reject) => { if (!this.cos) { reject(new Error('COS未初始化')) return } const cosConfig = app.globalData.cos const uniqueFileName = this.generateUniqueFileName(originalName || 'file.jpg') const key = this.generateFilePath(uniqueFileName, fileType) console.log('开始上传文件到COS:', { filePath, fileType, key, bucket: cosConfig.bucket }) this.cos.uploadFile({ Bucket: cosConfig.bucket, Region: cosConfig.region, Key: key, FilePath: filePath, onProgress: (progressData) => { const percent = Math.round(progressData.percent * 100) console.log('上传进度:', percent + '%') if (onProgress && typeof onProgress === 'function') { onProgress(percent) } } }, (err, data) => { if (err) { console.error('COS上传失败:', err) reject(err) } else { console.log('COS上传成功:', data) resolve({ key: key, url: `https://${data.Location}`, etag: data.ETag, fileName: uniqueFileName, originalName: originalName, fileType: fileType }) } }) }) } /** * 上传头像 * @param {string} filePath 本地文件路径 * @param {string} originalName 原始文件名 * @param {function} onProgress 进度回调 * @returns {Promise} 返回上传结果 */ uploadAvatar(filePath, originalName = '', onProgress = null) { return this.uploadFile(filePath, 'avatar', originalName, onProgress) } /** * 上传图片 * @param {string} filePath 本地文件路径 * @param {string} originalName 原始文件名 * @param {function} onProgress 进度回调 * @returns {Promise} 返回上传结果 */ uploadImage(filePath, originalName = '', onProgress = null) { return this.uploadFile(filePath, 'image', originalName, onProgress) } /** * 上传视频 * @param {string} filePath 本地文件路径 * @param {string} originalName 原始文件名 * @param {function} onProgress 进度回调 * @returns {Promise} 返回上传结果 */ uploadVideo(filePath, originalName = '', onProgress = null) { return this.uploadFile(filePath, 'video', originalName, onProgress) } /** * 上传附件 * @param {string} filePath 本地文件路径 * @param {string} originalName 原始文件名 * @param {function} onProgress 进度回调 * @returns {Promise} 返回上传结果 */ uploadAttachment(filePath, originalName = '', onProgress = null) { return this.uploadFile(filePath, 'attachment', originalName, onProgress) } /** * 选择并上传图片 * @param {object} options 选择图片选项 * @param {string} fileType 文件类型 * @param {function} onProgress 进度回调 * @returns {Promise} 返回上传结果 */ chooseAndUploadImage(options = {}, fileType = 'image', onProgress = null) { return new Promise((resolve, reject) => { wx.chooseImage({ count: options.count || 1, sizeType: options.sizeType || ['original', 'compressed'], sourceType: options.sourceType || ['album', 'camera'], success: (res) => { const tempFilePaths = res.tempFilePaths if (tempFilePaths.length === 1) { // 单个文件上传 const filePath = tempFilePaths[0] const originalName = `image_${Date.now()}.jpg` this.uploadFile(filePath, fileType, originalName, onProgress) .then(resolve) .catch(reject) } else { // 多个文件上传 const uploadPromises = tempFilePaths.map((filePath, index) => { const originalName = `image_${Date.now()}_${index}.jpg` return this.uploadFile(filePath, fileType, originalName, onProgress) }) Promise.all(uploadPromises) .then(resolve) .catch(reject) } }, fail: reject }) }) } /** * 选择并上传视频 * @param {object} options 选择视频选项 * @param {function} onProgress 进度回调 * @returns {Promise} 返回上传结果 */ chooseAndUploadVideo(options = {}, onProgress = null) { return new Promise((resolve, reject) => { wx.chooseVideo({ sourceType: options.sourceType || ['album', 'camera'], maxDuration: options.maxDuration || 60, camera: options.camera || 'back', success: (res) => { const originalName = `video_${Date.now()}.mp4` this.uploadVideo(res.tempFilePath, originalName, onProgress) .then(resolve) .catch(reject) }, fail: reject }) }) } /** * 删除COS文件 * @param {string} key 文件key * @returns {Promise} 返回删除结果 */ deleteFile(key) { return new Promise((resolve, reject) => { if (!this.cos) { reject(new Error('COS未初始化')) return } const cosConfig = app.globalData.cos this.cos.deleteObject({ Bucket: cosConfig.bucket, Region: cosConfig.region, Key: key }, (err, data) => { if (err) { console.error('COS删除文件失败:', err) reject(err) } else { console.log('COS删除文件成功:', data) resolve(data) } }) }) } /** * 获取文件访问URL * @param {string} key 文件key * @returns {string} 文件访问URL */ getFileUrl(key) { const cosConfig = app.globalData.cos return `https://${cosConfig.bucket}.cos.${cosConfig.region}.myqcloud.com/${key}` } /** * 批量上传文件 * @param {Array} files 文件列表 [{filePath, fileType, originalName}] * @param {function} onProgress 进度回调 * @returns {Promise} 返回上传结果列表 */ batchUpload(files, onProgress = null) { const uploadPromises = files.map((file, index) => { const progressCallback = onProgress ? (percent) => { onProgress(index, percent, files.length) } : null return this.uploadFile(file.filePath, file.fileType, file.originalName, progressCallback) }) return Promise.all(uploadPromises) } } // 创建单例实例 const cosUtil = new CosUtil() module.exports = cosUtil