52088886美国:一段跨越山海的浪漫奇遇

核心内容摘要

点亮奇迹,绽放无限——花火272278小樱368776!,遇见属于你的闪耀时刻
豪门深院的温柔羁绊:探秘《家有三嫂白莹雪琳》背后的情感旋涡

丁香五月亚洲

大文件上传方案探索从WebUploader到自定义分片上传的实践作为一名前端开发工程师最近遇到了一个颇具挑战性的需求需要在Vue项目中实现4GB左右大文件的稳定上传且要兼容Chrome、Firefox、Edge等主流浏览器后端使用PHP接收。

此前我们采用了百度开源的WebUploader组件但在实际使用中遇到了几个难以解决的问题分片上传过程中偶尔会出现断点续传失效的情况对新版浏览器的兼容性不够理想缺乏官方技术支持社区活跃度下降自定义UI的灵活性不足方案选型思考经过技术调研我评估了以下几个主流方案Plupload功能全面但文档不够友好对Vue集成支持一般Uppy现代感强但体积较大学习曲线较陡Resumable.js专注分片上传但UI较为基础自定义实现基于XMLHttpRequest/Fetch API实现核心分片逻辑最终决定采用自定义分片上传方案主要基于以下考虑完全控制上传流程可以针对业务需求深度优化减少第三方依赖降低维护成本与Vue生态无缝集成核心实现思路

前端分片策略// 文件分片工具函数constchunkFile(file,chunkSize5*1024*

{constchunks[]letcurrent0while(currentfile.size){chunks.push({file:file.slice(current,currentchunkSize),chunkIndex:chunks.length,totalChunks:Math.ceil(file.size/chunkSize),fileName:file.name,fileSize:file.size,fileType:file.type,fileLastModified:file.lastModified,identifier:generateFileIdentifier(file)// 生成唯一标识用于断点续传})currentchunkSize}returnchunks}// 生成文件唯一标识基于文件内容constgenerateFileIdentifier(file){returnnewPromise((resolve){constreadernewFileReader()reader.onload(e){constarrnewUint8Array(e.target.result)consthashArrayArray.from(arr).map(bb.toString(

.padStart(2,

)resolve(hashArray.join().substring(0,

)}reader.readAsArrayBuffer(file.slice(0,1024*

)// 取前1MB计算哈希})}

Vue组件实现export default { data() { return { file: null, chunks: [], uploadStatus: idle, // idle, uploading, paused, completed, error progress: 0, error: null, currentChunk: 0, abortController: null } }, methods: { async handleFileChange(e) { this.file e.target.files[0] if (!this.file) return // 生成文件标识简化版实际项目应使用更可靠的算法 const identifier await this.generateSimpleIdentifier(this.file) // 检查服务器是否有未完成的上传记录 const res await this.checkUploadStatus(identifier) if (res.exists) { if (confirm(检测到未完成的上传是否继续)) { this.currentChunk res.uploadedChunks } else { // 清除服务器记录实际项目应实现 } } this.chunks this.chunkFile(this.file) this.progress Math.round((this.currentChunk / this.chunks.length) *

}, async startUpload() { if (!this.file) return this.uploadStatus uploading this.error null this.abortController new AbortController() try { for (let i this.currentChunk; i this.chunks.length; i) { if (this.uploadStatus ! uploading) break // 处理暂停情况 const chunk this.chunks[i] const formData new FormData() formData.append(file, chunk.file) formData.append(chunkIndex, chunk.chunkIndex) formData.append(totalChunks, chunk.totalChunks) formData.append(fileName, chunk.fileName) formData.append(fileSize, chunk.fileSize) formData.append(fileType, chunk.fileType) formData.append(identifier, chunk.identifier) await this.uploadChunk(formData) this.currentChunk i 1 this.progress Math.round(((i

/ this.chunks.length) *

} if (this.uploadStatus uploading) { await this.mergeChunks(this.chunks[0].identifier, this.chunks[0].fileName) this.uploadStatus completed this.$emit(upload-complete) } } catch (err) { console.error(上传失败:, err) this.error err.message || 上传过程中出现错误 this.uploadStatus error } }, async uploadChunk(formData) { return fetch(/api/upload-chunk.php, { method: POST, body: formData, signal: this.abortController.signal }) }, async mergeChunks(identifier, fileName) { return fetch(/api/merge-chunks.php, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ identifier, fileName }) }) }, // 简化版标识生成实际项目应使用更可靠的算法 generateSimpleIdentifier(file) { return ${file.name}-${file.size}-${file.lastModified} }, async checkUploadStatus(identifier) { // 实际项目应实现与后端的交互 return { exists: false, uploadedChunks: 0 } }, pauseUpload() { if (this.uploadStatus uploading) { this.abortController.abort() this.uploadStatus paused } }, resumeUpload() { if (this.uploadStatus paused) { this.startUpload() } } } }

PHP后端实现要点$chunkIndex,totalChunks$totalChunks,fileName$fileName,uploadedtime()]));echojson_encode([statussuccess]);}else{http_response_code(

;echojson_encode([statuserror,messageFailed to save chunk]);}// merge-chunks.php - 合并分片header(Content-Type: application/json);$uploadDir/path/to/upload/dir/;$tempDir$uploadDir.temp/;$datajson_decode(file_get_contents(php://input),true);$identifier$data[identifier]??;$fileName$data[fileName]??;// 检查标识符和文件名if(empty($identifier)||empty($fileName)){http_response_code(

;echojson_encode([statuserror,messageInvalid parameters]);exit;}// 检查上传状态文件$statusFile$tempDir.$identifier..upload;if(!file_exists($statusFile)){http_response_code(

;echojson_encode([statuserror,messageUpload not found]);exit;}$statusjson_decode(file_get_contents($statusFile),true);$totalChunks$status[totalChunks]??0;// 合并文件$finalPath$uploadDir.$fileName;if($fpfopen($finalPath,wb)){for($i0;$i$totalChunks;$i){$chunkPath$tempDir.$identifier._.$i;if(!file_exists($chunkPath)){fclose($fp);unlink($finalPath);// 删除已创建的部分文件http_response_code(

;echojson_encode([statuserror,messageMissing chunk .$i]);exit;}$contentfile_get_contents($chunkPath);fwrite($fp,$content);unlink($chunkPath);// 删除已合并的分片}fclose($fp);// 删除状态文件unlink($statusFile);echojson_encode([statussuccess,path$finalPath]);}else{http_response_code(

;echojson_encode([statuserror,messageFailed to create final file]);}?方案优势与改进点优势完全可控从分片策略到上传逻辑完全自主实现深度优化可以根据网络状况动态调整分片大小良好兼容基于标准Web API实现兼容所有现代浏览器断点续传通过文件标识实现可靠的断点续传进度可视化精确计算上传进度可改进方向并发上传当前实现是顺序上传可优化为并发上传提高速度文件校验增加MD5/SHA校验确保文件完整性更可靠的标识生成当前简化版标识可能存在冲突风险服务端清理实现自动清理未完成上传的临时文件拖拽上传增强用户体验支持拖放文件上传实施建议渐进式实现先实现基本分片上传再逐步添加断点续传、并发上传等功能充分测试在不同网络环境和浏览器下进行全面测试监控上报添加上传失败监控和错误上报机制性能优化根据实际测试结果调整分片大小和并发数通过这种自定义实现方式我们成功解决了WebUploader带来的各种问题同时获得了更好的性能和更灵活的控制能力。

目前该方案已在我们项目中稳定运行数月处理了数百个4GB文件的上传未出现重大故障。

将组件复制到项目中示例中已经包含此目录引入组件配置接口地址接口地址分别对应文件初始化文件数据上传文件进度文件上传完毕文件删除文件夹初始化文件夹删除文件列表参考http://www.ncmem.com/doc/view.aspx?ide1f49f3e1d4742e19135e00bd41fa3de处理事件启动测试启动成功效果数据库效果预览文件上传文件刷新续传支持离线保存文件进度在关闭浏览器刷新浏览器后进行不丢失仍然能够继续上传文件夹上传支持上传文件夹并保留层级结构同样支持进度信息离线保存刷新页面关闭页面重启系统不丢失上传进度。

批量下载支持文件批量下载下载续传文件下载支持离线保存进度信息刷新页面关闭页面重启系统均不会丢失进度信息。

文件夹下载支持下载文件夹并保留层级结构不打包不占用服务器资源。

下载示例点击下载完整示例

十八模1.1.6下载安装-十八模1.1.6下载安装应用

百度百家号客服电话人工服务

123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123