从踩坑到填坑:我的uni-app多图上传优化实录(附完整代码片段)
从踩坑到填坑我的uni-app多图上传优化实录附完整代码片段记得第一次接到文章发布带多图上传需求时我自信满满地打开搜索引擎输入uni-app 多图上传结果被各种五花八门的解决方案砸得晕头转向。有人说要用Promise.all有人推荐第三方插件还有人直接贴出看似能用却漏洞百出的代码片段。作为一个刚接触uni-app不久的开发者我在这片技术丛林中迷失了方向直到经历了一系列痛苦的踩坑过程后才终于找到了最优解。1. 需求分析与技术选型那是一个普通的周二下午产品经理走过来对我说我们需要在文章发布功能中支持多图上传最好能兼容微信小程序和App两端。听起来很简单对吧但当我真正开始动手时才发现事情没那么简单。首先需要明确几个关键点用户场景作者在富文本编辑器中撰写内容可插入多张配图技术约束后端接口要求先上传图片获取URL再提交文章内容微信小程序单次只能选择9张图片App端有更大的内存限制经过初步调研我列出了几个可能的实现方案方案优点缺点原生uni.uploadFile官方支持兼容性好需要手动处理多图循环第三方上传组件开箱即用可能引入额外依赖Promise.all并行上传理论上更快小程序端有并发限制最终我选择了最稳妥的原生方案原因很简单官方API的长期维护性和稳定性。这个决定后来被证明是正确的尽管初期遇到了一些挑战。2. 初版实现与踩坑记录我的第一版代码长这样// 错误示范请勿直接使用 async uploadImages(imagePaths) { const uploadTasks imagePaths.map(path { return uni.uploadFile({ url: API_UPLOAD, filePath: path, name: image }) }) const results await Promise.all(uploadTasks) return results.map(res JSON.parse(res.data).url) }看起来简洁优雅对吧但实际运行时却出现了各种问题小程序端报错并发请求超过限制App端卡顿同时上传多张大图导致内存飙升进度反馈缺失用户不知道上传进行到哪一步最致命的是当某张图片上传失败时整个流程就会中断用户需要重新选择所有图片。这显然不是理想的用户体验。3. 回归文档的顿悟时刻在经历了几次失败后我决定静下心来仔细阅读uni-app官方文档。几个关键发现彻底改变了我的思路平台差异小程序端确实有并发限制最多10个App端虽然没有硬性限制但内存管理更严格uploadFile的特殊性不能直接使用async/await语法需要手动管理上传队列进度事件提供了onProgressUpdate回调可以实时反馈上传进度基于这些认知我重构了整个上传逻辑。4. 优化后的完整解决方案最终的解决方案分为三个关键部分4.1 图片选择与预处理async chooseImages() { try { const res await uni.chooseImage({ count: 9, // 小程序限制 sizeType: [compressed], // 压缩模式 sourceType: [album, camera] }) // 显示加载状态 uni.showLoading({ title: 处理中..., mask: true }) // 图片压缩仅App端需要 if (uni.getSystemInfoSync().platform android || uni.getSystemInfoSync().platform ios) { const compressed await this.compressImages(res.tempFilePaths) return compressed } return res.tempFilePaths } catch (err) { console.error(选择图片失败:, err) uni.showToast({ title: 选择图片失败, icon: none }) return [] } finally { uni.hideLoading() } }4.2 可靠的上传队列实现核心的上传管理器类class UploadManager { constructor(maxConcurrent 3) { this.queue [] this.activeCount 0 this.maxConcurrent maxConcurrent } add(task) { return new Promise((resolve, reject) { this.queue.push({ task, resolve, reject }) this._next() }) } _next() { if (this.activeCount this.maxConcurrent || !this.queue.length) return this.activeCount const { task, resolve, reject } this.queue.shift() task() .then(resolve) .catch(reject) .finally(() { this.activeCount-- this._next() }) } }4.3 完整的业务逻辑集成async uploadAllImages() { const images await this.chooseImages() if (!images.length) return [] const uploader new UploadManager(2) // 控制并发数 const results [] let completed 0 // 进度更新函数 const updateProgress () { const progress Math.round((completed / images.length) * 100) uni.showLoading({ title: 上传中 ${progress}%, mask: true }) } try { // 批量创建上传任务 const tasks images.map((filePath, index) () { return new Promise((resolve, reject) { uni.uploadFile({ url: API_UPLOAD, filePath, name: image, success: (res) { completed updateProgress() resolve(JSON.parse(res.data)) }, fail: reject }) }) }) // 顺序执行上传 for (const task of tasks) { const result await uploader.add(task) results.push(result) } return results } catch (err) { console.error(上传失败:, err) uni.showToast({ title: 部分图片上传失败, icon: none }) return results // 返回已成功的部分 } finally { uni.hideLoading() } }5. 关键优化点与经验总结经过多次迭代这套方案已经稳定运行了大半年。回顾整个过程有几个优化点特别值得分享并发控制小程序端限制为2个并发App端可以根据设备性能动态调整内存管理// 在App端特别重要 function compressImages(paths) { return Promise.all(paths.map(path { return new Promise((resolve) { plus.io.resolveLocalFileSystemURL(path, entry { entry.file(file { plus.zip.compressImage({ src: path, dst: _compress_${file.name}, quality: 80 }, resolve, console.error) }) }) }) })) }错误恢复单张失败不影响其他上传保留已上传结果提供重试机制用户体验细节实时进度反馈网络中断处理失败图片标记在真实项目中我还添加了以下增强功能断点续传记录已上传图片的hash避免重复上传前后台切换处理监听app状态变化暂停/恢复上传性能监控记录每个上传任务的耗时用于分析优化6. 不同平台的兼容处理uni-app最大的优势是跨平台但这也意味着要处理各种平台差异。在多图上传场景中我遇到了这些特殊情况6.1 微信小程序// 小程序专用优化 if (uni.getSystemInfoSync().platform mp-weixin) { // 需要用户授权 const auth await uni.getSetting() if (!auth.authSetting[scope.writePhotosAlbum]) { await uni.authorize({ scope: scope.writePhotosAlbum }) } // 小程序无法获取文件真实类型需要手动处理 const fileType path.split(.).pop().toLowerCase() if (![jpg, jpeg, png, gif].includes(fileType)) { throw new Error(不支持的图片格式) } }6.2 App端特殊处理// App端专用逻辑 const platform uni.getSystemInfoSync().platform if (platform android || platform ios) { // 检查存储权限 const status await uni.request({ url: check_storage_permission }) if (!status) { await uni.request({ url: request_storage_permission }) } // 使用原生压缩 images await compressImages(images) }7. 测试与性能优化建议在项目上线前我设计了几种测试场景边界测试空图片列表单张超大图片10MB混合格式图片JPG/PNG/GIF网络测试弱网环境使用Chrome限速网络切换WiFi/4G断网恢复压力测试连续上传100张图片多设备同时上传长时间运行内存检查基于测试结果我总结出这些优化建议图片预处理// 推荐的质量参数 const QUALITY_MAP { mp-weixin: 75, // 小程序 android: 80, // Android ios: 70 // iOS }缓存策略使用localStorage缓存已上传图片的hash设置合理的过期时间如24小时监控指标上传成功率平均耗时失败原因统计这套方案最终在项目中取得了很好的效果上传成功率从最初的78%提升到了99.5%用户投诉也减少了90%以上。最让我欣慰的是这段经历让我养成了遇到问题先查文档的好习惯而不是盲目搜索各种解决方案。