工程建筑行业如何通过WebUploader+PHP实现BIM模型文件的分片传输?
大文件上传方案设计与实现政府信创环境兼容方案背景作为北京某软件公司的开发人员我负责为政府客户实现一个兼容主流浏览器和信创国产化环境的大文件上传系统。当前需求是支持4GB左右文件的上传后端使用PHP前端使用Vue.js框架。之前尝试的百度WebUploader在国产化环境中存在兼容性问题因此需要重新设计解决方案。技术选型分析方案考虑因素国产化兼容需支持信创环境如麒麟、UOS等操作系统飞腾、鲲鹏等CPU架构浏览器兼容需支持Chrome、Firefox及国产浏览器如360安全浏览器、红芯等开源合规必须提供完整源代码供审查稳定性大文件上传的可靠性和断点续传能力性能4GB文件上传的效率和资源占用最终方案采用基于分片上传断点续传的自定义实现结合以下技术前端Vue.js 原生HTML5 File API Axios后端PHP原生或Laravel框架分片算法固定大小分片 MD5校验进度管理Web Worker处理哈希计算前端实现Vue组件示例1. 安装必要依赖npminstallspark-md5 axios2. 大文件上传组件 (FileUploader.vue)import SparkMD5 from spark-md5 import axios from axios export default { name: FileUploader, data() { return { file: null, chunkSize: 5 * 1024 * 1024, // 5MB每片 uploadProgress: 0, isUploading: false, isPaused: false, fileHash: , worker: null, currentChunk: 0, totalChunks: 0, uploadId: , abortController: null } }, methods: { triggerFileInput() { this.$refs.fileInput.click() }, handleFileChange(e) { const files e.target.files if (files.length 0) return this.file files[0] this.uploadProgress 0 this.calculateFileHash() }, // 使用Web Worker计算文件哈希避免主线程阻塞 calculateFileHash() { this.$emit(hash-progress, 0) this.worker new Worker(/hash-worker.js) this.worker.postMessage({ file: this.file, chunkSize: this.chunkSize }) this.worker.onmessage (e) { const { type, data } e.data if (type progress) { this.$emit(hash-progress, data) } else if (type result) { this.fileHash data this.totalChunks Math.ceil(this.file.size / this.chunkSize) this.worker.terminate() } } }, async startUpload() { if (!this.file || !this.fileHash) return this.isUploading true this.isPaused false this.currentChunk 0 // 1. 初始化上传获取uploadId try { const initRes await this.request({ url: /api/upload/init, method: post, data: { fileName: this.file.name, fileSize: this.file.size, fileHash: this.fileHash, chunkSize: this.chunkSize } }) this.uploadId initRes.data.uploadId // 2. 开始分片上传 await this.uploadChunks() // 3. 合并文件 await this.mergeChunks() this.$emit(upload-success, initRes.data) } catch (error) { console.error(上传失败:, error) this.$emit(upload-error, error) } finally { this.isUploading false } }, async uploadChunks() { return new Promise((resolve, reject) { const uploadNextChunk async () { if (this.currentChunk this.totalChunks) { return resolve() } if (this.isPaused) return const start this.currentChunk * this.chunkSize const end Math.min(start this.chunkSize, this.file.size) const chunk this.file.slice(start, end) const formData new FormData() formData.append(file, chunk) formData.append(chunkNumber, this.currentChunk) formData.append(totalChunks, this.totalChunks) formData.append(uploadId, this.uploadId) formData.append(fileHash, this.fileHash) try { await this.request({ url: /api/upload/chunk, method: post, data: formData, onUploadProgress: (progressEvent) { // 计算整体进度 const chunkProgress Math.round( (progressEvent.loaded * 100) / progressEvent.total ) const totalProgress Math.round( ((this.currentChunk * 100) chunkProgress) / this.totalChunks ) this.uploadProgress totalProgress } }) this.currentChunk this.$emit(chunk-uploaded, this.currentChunk) // 使用setTimeout避免堆栈溢出 setTimeout(uploadNextChunk, 0) } catch (error) { reject(error) } } uploadNextChunk() }) }, async mergeChunks() { await this.request({ url: /api/upload/merge, method: post, data: { uploadId: this.uploadId, fileHash: this.fileHash, fileName: this.file.name, chunkSize: this.chunkSize } }) }, pauseUpload() { this.isPaused true if (this.abortController) { this.abortController.abort() } }, resumeUpload() { this.isPaused false this.uploadChunks() }, request(config) { // 创建新的AbortController用于取消请求 this.abortController new AbortController() return axios({ ...config, signal: this.abortController.signal, headers: { ...config.headers, Authorization: Bearer localStorage.getItem(token) } }).finally(() { this.abortController null }) }, formatFileSize(bytes) { if (bytes 0) return 0 Bytes const k 1024 const sizes [Bytes, KB, MB, GB, TB] const i Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) sizes[i] } }, beforeDestroy() { if (this.worker) { this.worker.terminate() } if (this.abortController) { this.abortController.abort() } } } .progress-container { margin-top: 10px; width: 100%; } progress { width: 80%; height: 20px; }3. Web Worker脚本 (public/hash-worker.js)// 使用SparkMD5计算文件哈希在Web Worker中运行self.importScripts(https://cdn.jsdelivr.net/npm/spark-md53.0.2/spark-md5.min.js)self.onmessagefunction(e){const{file,chunkSize}e.dataconstchunksMath.ceil(file.size/chunkSize)constsparknewSparkMD5.ArrayBuffer()constfileReadernewFileReader()letcurrentChunk0fileReader.onloadfunction(e){spark.append(e.target.result)currentChunk// 报告进度self.postMessage({type:progress,data:Math.floor((currentChunk/chunks)*100)})if(currentChunkchunks){loadNextChunk()}else{consthashspark.end()self.postMessage({type:result,data:hash})}}functionloadNextChunk(){conststartcurrentChunk*chunkSizeconstendMath.min(startchunkSize,file.size)fileReader.readAsArrayBuffer(file.slice(start,end))}loadNextChunk()}后端PHP实现1. 初始化上传接口// api/upload/initpublicfunctioninitUpload(Request$request){$data$request-only([fileName,fileSize,fileHash,chunkSize]);// 验证参数$validatorValidator::make($data,[fileNamerequired|string,fileSizerequired|integer,fileHashrequired|string,chunkSizerequired|integer]);if($validator-fails()){returnresponse()-json([code400,msg参数错误]);}// 生成唯一uploadId$uploadIdmd5(uniqid());// 创建临时目录$tempDirstorage_path(app/uploads/temp/{$uploadId});if(!file_exists($tempDir)){mkdir($tempDir,0755,true);}// 保存上传信息实际项目中应该存入数据库$uploadInfo[upload_id$uploadId,file_name$data[fileName],file_size$data[fileSize],file_hash$data[fileHash],chunk_size$data[chunkSize],total_chunksceil($data[fileSize]/$data[chunkSize]),uploaded_chunks[],created_atnow()];file_put_contents({$tempDir}/upload_info.json,json_encode($uploadInfo));returnresponse()-json([code200,msgsuccess,data[uploadId$uploadId,tempDir$tempDir]]);}2. 分片上传接口// api/upload/chunkpublicfunctionuploadChunk(Request$request){$uploadId$request-input(uploadId);$chunkNumber$request-input(chunkNumber);$fileHash$request-input(fileHash);if(!$request-hasFile(file)||!$uploadId||$chunkNumbernull){returnresponse()-json([code400,msg参数错误]);}$tempDirstorage_path(app/uploads/temp/{$uploadId});if(!file_exists($tempDir)){returnresponse()-json([code404,msg上传会话不存在]);}// 读取上传信息$uploadInfojson_decode(file_get_contents({$tempDir}/upload_info.json),true);// 验证文件哈希if($uploadInfo[file_hash]!$fileHash){returnresponse()-json([code400,msg文件哈希不匹配]);}// 保存分片$chunkFile$request-file(file);$chunkPath{$tempDir}/{$chunkNumber}.part;$chunkFile-move(dirname($chunkPath),basename($chunkPath));// 记录已上传的分片$uploadInfo[uploaded_chunks][]$chunkNumber;file_put_contents({$tempDir}/upload_info.json,json_encode($uploadInfo));returnresponse()-json([code200,msg分片上传成功]);}3. 合并分片接口// api/upload/mergepublicfunctionmergeChunks(Request$request){$data$request-only([uploadId,fileHash,fileName]);$validatorValidator::make($data,[uploadIdrequired|string,fileHashrequired|string,fileNamerequired|string]);if($validator-fails()){returnresponse()-json([code400,msg参数错误]);}$tempDirstorage_path(app/uploads/temp/{$data[uploadId]});if(!file_exists($tempDir)){returnresponse()-json([code404,msg上传会话不存在]);}// 读取上传信息$uploadInfojson_decode(file_get_contents({$tempDir}/upload_info.json),true);// 验证文件哈希if($uploadInfo[file_hash]!$data[fileHash]){returnresponse()-json([code400,msg文件哈希不匹配]);}// 检查是否所有分片都已上传$totalChunks$uploadInfo[total_chunks];$uploadedChunks$uploadInfo[uploaded_chunks];if(count($uploadedChunks)!$totalChunks){returnresponse()-json([code400,msg还有分片未上传完成]);}// 创建最终文件$finalDirstorage_path(app/uploads/final);if(!file_exists($finalDir)){mkdir($finalDir,0755,true);}$finalPath{$finalDir}/{$data[fileHash]}_{$data[fileName]};$fpfopen($finalPath,wb);// 按顺序合并分片for($i0;$i$totalChunks;$i){$chunkPath{$tempDir}/{$i}.part;$chunkContentfile_get_contents($chunkPath);fwrite($fp,$chunkContent);unlink($chunkPath);// 删除分片文件}fclose($fp);// 清理临时目录rmdir($tempDir);returnresponse()-json([code200,msg文件合并成功,data[filePath$finalPath,fileUrlasset(storage/uploads/final/.basename($finalPath))]]);}国产化环境适配说明浏览器兼容使用原生HTML5 File API兼容所有现代浏览器对于不支持的浏览器如旧版IE可添加降级提示信创环境适配前端代码不依赖任何特定浏览器API后端PHP使用原生文件操作函数无系统相关调用测试通过麒麟V10、UOS等国产操作系统飞腾/鲲鹏CPU环境安全考虑文件哈希验证防止篡改分片上传避免内存溢出临时文件及时清理部署注意事项PHP配置确保upload_max_filesize和post_max_size大于分片大小调整max_execution_time避免超时Nginx配置如使用client_max_body_size 100M; client_body_timeout 300s;存储路径权限确保storage/app/uploads目录有写入权限总结本方案通过分片上传和断点续传技术解决了大文件上传的稳定性问题同时完全满足政府客户的国产化兼容和源代码审查要求。前端采用Vue.js原生API实现后端使用纯PHP处理不依赖任何闭源组件确保了代码的完全可控性。将组件复制到项目中示例中已经包含此目录引入组件配置接口地址接口地址分别对应文件初始化文件数据上传文件进度文件上传完毕文件删除文件夹初始化文件夹删除文件列表参考http://www.ncmem.com/doc/view.aspx?ide1f49f3e1d4742e19135e00bd41fa3de处理事件启动测试启动成功效果数据库下载示例点击下载完整示例

相关新闻

机器学习泰斗荣获德国AI创新大奖

机器学习泰斗荣获德国AI创新大奖

本月早些时候,某机构杰出科学家、德国马克斯普朗克智能系统研究所经验推理系主任伯恩哈德舍尔科普夫接受了德国人工智能奖,以表彰他在人工智能领域取得的开创性成就。 机器学习 伯恩哈德舍尔科普夫荣获德国AI创新大奖 某机构杰出科学家及马普所所长获此殊…

2026/5/17 7:23:07 阅读更多 →
实测对比后!用户挚爱的AI论文工具 —— 千笔写作工具

实测对比后!用户挚爱的AI论文工具 —— 千笔写作工具

你是否曾为论文选题发愁,面对空白文档无从下手?是否在反复修改中感到力不从心,又担心查重率过高?论文写作不仅是学术能力的考验,更是时间与精力的拉锯战。而如今,一款专为学生打造的AI论文工具——千笔AI&a…

2026/7/4 11:03:31 阅读更多 →
干货合集:10个AI论文网站测评!研究生毕业论文+科研写作必备工具推荐

干货合集:10个AI论文网站测评!研究生毕业论文+科研写作必备工具推荐

在科研写作日益依赖AI工具的当下,如何选择一款真正能提升效率、保障质量的论文辅助软件,成为研究生和研究人员亟需解决的问题。随着2026年学术写作场景的不断升级,市场上涌现出众多AI论文工具,但功能参差不齐、适配性差异大&#…

2026/7/5 8:53:54 阅读更多 →

最新新闻

H5跳转应用商店兼容性实战:覆盖10+主流安卓市场与iOS的JS代码库

H5跳转应用商店兼容性实战:覆盖10+主流安卓市场与iOS的JS代码库

H5跳转应用商店兼容性实战:覆盖10主流安卓市场与iOS的JS代码库在移动互联网时代,H5页面作为轻量级入口,承担着用户增长和流量分发的重要职责。然而,当需要引导用户从H5页面跳转到原生应用商店时,开发者往往面临设备检测…

2026/7/6 2:43:53 阅读更多 →
MDIO总线驱动开发实战:基于Linux内核4.19的PHY寄存器读写与调试

MDIO总线驱动开发实战:基于Linux内核4.19的PHY寄存器读写与调试

MDIO总线驱动开发实战:基于Linux内核4.19的PHY寄存器读写与调试在嵌入式Linux开发中,网络设备的稳定性和性能往往取决于底层驱动的质量。MDIO总线作为MAC与PHY芯片之间的管理通道,其驱动实现直接影响着网络接口的配置、状态监控和故障排查效率…

2026/7/6 2:37:52 阅读更多 →
力反馈:采集了但没有专门处理

力反馈:采集了但没有专门处理

力数据经历了三重"未使用":Franka 硬件力矩传感器K_F_ext_hat_K (6D)↓ franka_server.py: ROS 回调self.force [:3], self.torque [:3]↓ franka_env.py: _get_obs()"tcp_force": (3,), "tcp_torque": (3,)↓ SERLObsWrapper: 展平…

2026/7/6 2:37:52 阅读更多 →
临界分词的存在性与最优性:从统计临界态到神经语言模型的双语实证检验

临界分词的存在性与最优性:从统计临界态到神经语言模型的双语实证检验

一项关于"自然语言分词是否存在内禀临界点,以及该点是否最优"的可证伪研究。 含 n-gram 统计分析(中/英)与线性 SSM 语言模型(FRSMASH v3.6,~8M 参数)双语验证。摘要 本文把"临界分词"…

2026/7/6 2:37:52 阅读更多 →
WIN11 64位系统编译ameba-rtos-d,260705

WIN11 64位系统编译ameba-rtos-d,260705

这次调试确实经历了相当漫长曲折的过程,帮你做一个完整的问题清单和修改记录,方便你以后归档或者需要在别的电脑上重新配置环境时参考。问题一:32位 Cygwin 检测被拦截现象: Makefile 检测到当前 Cygwin/bash 环境是 64 位&#x…

2026/7/6 2:35:52 阅读更多 →
多人格的记忆,有共用有不共用

多人格的记忆,有共用有不共用

最近听到一个多人格案例,引起我的兴趣。大意是某人考试时切换到考试人格,考完再切换回来。我的兴趣在哪里?在于记忆。主人格切换到后台(暂停),相当于睡了一觉。所以主人格对于副人格的做事经历,…

2026/7/6 2:33:52 阅读更多 →

日新闻

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2与MySQL单元测试兼容性:5个关键SQL语句差异与规避方案1. 单元测试中的数据库兼容性挑战在Java开发领域,单元测试是保证代码质量的重要环节。当应用涉及数据库操作时,测试环境的搭建往往成为开发者的痛点。H2数据库因其轻量级、内存模式和快…

2026/7/6 0:01:17 阅读更多 →
Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘 【免费下载链接】rbtray A fork of RBTray from http://sourceforge.net/p/rbtray/code/. 项目地址: https://gitcode.com/gh_mirrors/rb/rbtray 你是否厌倦了Windows任务栏上密密麻麻的图标&…

2026/7/6 0:01:17 阅读更多 →
Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C 运行时库一键安装终极指南:告别DLL缺失烦恼 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这样的情况:下载了…

2026/7/6 0:05:19 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

月新闻