工程建筑行业网页,JAVA如何实现大文件的分块与秒传?

大文件上传系统开发指南(基于原生JS+SpringBoot)

项目概述

大家好,我是一个在浙江奋斗的Java程序员,最近接了个"刺激"的外包项目 - 开发一个支持20G大文件上传下载的系统,还要兼容IE9这种上古浏览器。客户要求使用原生JS实现(不能用jQuery等库),支持文件夹上传(保留层级结构),还要加密传输和断点续传。预算只有100元,但需要提供完整技术支持和源代码。

这活儿听着就刺激,但咱是谁?是打不死的小强!下面我就分享一下我的解决方案和部分代码实现。

技术选型

  • 前端:Vue3 CLI + 原生JavaScript(兼容IE9)
  • 后端:SpringBoot + Tomcat
  • 数据库:MySQL(主要存用户信息和文件元数据)
  • 文件存储:服务器本地文件系统
  • 加密:SM4(国密)和AES

系统架构

浏览器(IE9+) ←HTTP→ SpringBoot后端 ←本地IO→ 文件存储 ↑ MySQL(元数据)

前端实现(关键代码)

1. 文件夹上传组件(兼容IE9)

export default { data() { return { fileList: [], chunkSize: 5 * 1024 * 1024, // 5MB分片 concurrent: 3 // 并发上传数 } }, methods: { triggerFileInput() { document.getElementById('fileInput').click(); }, handleFileChange(e) { const files = e.target.files; if (!files.length) return; // 处理文件夹结构 const fileTree = this.buildFileTree(files); this.prepareUpload(fileTree); }, // 构建文件树结构(保留文件夹层级) buildFileTree(files) { const tree = {}; for (let i = 0; i < files.length; i++) { const file = files[i]; const path = file.webkitRelativePath || file.relativePath || file.name; const parts = path.split('/'); // 添加文件信息 const fileName = parts[parts.length - 1]; current[fileName] = { file: file, relativePath: path, size: file.size, chunks: Math.ceil(file.size / this.chunkSize), uploadedChunks: 0, progress: 0, status: '等待上传' }; } return tree; }, // 准备上传队列 prepareUpload(tree) { const flattenFiles = []; const traverse = (node, path = '') => { for (const key in node) { if (key === '__files__' || key === '__dirs__') continue; const newPath = path ? `${path}/${key}` : key; if (node[key].file) { // 是文件 flattenFiles.push({ ...node[key], relativePath: newPath }); } else { // 是目录,继续遍历 traverse(node[key].__dirs__, newPath); } } }; traverse(tree); this.fileList = flattenFiles; this.startUpload(); }, // 开始上传 startUpload() { const activeUploads = 0; const uploadNext = () => { if (activeUploads >= this.concurrent) return; const file = this.fileList.find(f => f.status === '等待上传'); if (!file) { if (this.fileList.every(f => f.status === '上传完成')) { this.$emit('upload-complete'); } return; } }; uploadNext(); }, // 分片上传文件 async uploadFile(fileEntry) { const file = fileEntry.file; const fileId = this.generateFileId(file); // 检查断点续传信息 const resumeInfo = this.getResumeInfo(fileId); let startChunk = resumeInfo ? resumeInfo.uploadedChunks : 0; for (let i = startChunk; i < fileEntry.chunks; i++) { const start = i * this.chunkSize; const end = Math.min(start + this.chunkSize, file.size); const chunk = file.slice(start, end); // 读取分片内容(兼容IE9) const chunkData = await this.readFileAsArrayBuffer(chunk); // 加密处理(这里简化,实际应该用Web Crypto API或polyfill) const encryptedData = this.encryptData(chunkData, 'AES'); // 实际应该用SM4 const formData = new FormData(); formData.append('fileId', fileId); formData.append('chunkIndex', i); formData.append('totalChunks', fileEntry.chunks); formData.append('relativePath', fileEntry.relativePath); formData.append('fileSize', file.size); formData.append('chunk', new Blob([encryptedData])); formData.append('fileName', file.name); } // 所有分片上传完成,通知服务器合并 await this.mergeFile(fileId, fileEntry.relativePath, file.size, file.name); fileEntry.status = '上传完成'; this.clearResumeInfo(fileId); }, // 以下是兼容IE9的工具方法 readFileAsArrayBuffer(file) { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = (e) => resolve(e.target.result); reader.readAsArrayBuffer(file); }); }, encryptData(data, algorithm) { // 实际项目中应该使用Web Crypto API或polyfill // 这里简化处理,实际加密代码会更复杂 if (typeof data === 'string') { return btoa(data); // 简单base64模拟加密 } else { const bytes = new Uint8Array(data); let result = ''; for (let i = 0; i < bytes.length; i++) { result += String.fromCharCode(bytes[i]); } return btoa(result); } }, async uploadChunk(formData) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('POST', '/api/upload/chunk', true); xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(JSON.parse(xhr.responseText)); } else { reject(new Error('上传失败')); } }; xhr.onerror = () => reject(new Error('网络错误')); xhr.send(formData); }); }, async mergeFile(fileId, relativePath, fileSize, fileName) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('POST', '/api/upload/merge', true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(JSON.parse(xhr.responseText)); } else { reject(new Error('合并失败')); } }; xhr.onerror = () => reject(new Error('网络错误')); xhr.send(JSON.stringify({ fileId, relativePath, fileSize, fileName })); }); }, // 断点续传相关方法(使用localStorage存储) generateFileId(file) { // 简单生成文件ID,实际应该更可靠 return `${file.name}-${file.size}-${file.lastModified}`; }, getResumeInfo(fileId) { const info = localStorage.getItem(`upload_resume_${fileId}`); return info ? JSON.parse(info) : null; }, saveResumeInfo(fileId, fileEntry) { localStorage.setItem(`upload_resume_${fileId}`, JSON.stringify({ uploadedChunks: fileEntry.uploadedChunks, relativePath: fileEntry.relativePath, fileSize: fileEntry.size, fileName: fileEntry.file.name })); }, clearResumeInfo(fileId) { localStorage.removeItem(`upload_resume_${fileId}`); }, formatSize(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]; } } }

后端实现(SpringBoot关键代码)

1. 文件上传控制器

@RestController@RequestMapping("/api/upload")publicclassFileUploadController{privatestaticfinalStringUPLOAD_DIR="/var/bigfileupload/";// 实际应该从配置读取privatestaticfinalintCHUNK_SIZE=5*1024*1024;// 5MB@PostMapping("/chunk")publicResponseEntityuploadChunk(@RequestParam("fileId")StringfileId,@RequestParam("chunkIndex")intchunkIndex,@RequestParam("totalChunks")inttotalChunks,@RequestParam("relativePath")StringrelativePath,@RequestParam("fileSize")longfileSize,@RequestParam("fileName")StringfileName,@RequestParam("chunk")MultipartFilechunk)throwsIOException{// 创建临时目录StringtempDir=UPLOAD_DIR+"temp/"+fileId+"/";FiletempDirFile=newFile(tempDir);if(!tempDirFile.exists()){tempDirFile.mkdirs();}// 保存分片(实际应该先解密)StringchunkPath=tempDir+chunkIndex;chunk.transferTo(newFile(chunkPath));// 记录上传进度(实际应该用数据库)UploadProgressprogress=newUploadProgress();progress.setFileId(fileId);progress.setUploadedChunks(chunkIndex+1);progress.setTotalChunks(totalChunks);progress.setRelativePath(relativePath);progress.setFileSize(fileSize);progress.setFileName(fileName);// saveToDatabase(progress); // 实际应该存数据库returnResponseEntity.ok().body(Map.of("status","success","chunkIndex",chunkIndex,"fileId",fileId));}@PostMapping("/merge")publicResponseEntitymergeFile(@RequestBodyMergeRequestrequest)throwsIOException,NoSuchAlgorithmException{StringfileId=request.getFileId();StringtempDir=UPLOAD_DIR+"temp/"+fileId+"/";FiletempDirFile=newFile(tempDir);if(!tempDirFile.exists()){returnResponseEntity.badRequest().body(Map.of("error","临时文件不存在"));}// 创建目标目录结构StringrelativePath=request.getRelativePath();StringtargetPath=UPLOAD_DIR+relativePath;FiletargetFile=newFile(targetPath);}// 下载文件接口(非打包方式)@GetMapping("/download")publicResponseEntitydownloadFile(@RequestParamStringfilePath,HttpServletResponseresponse)throwsIOException{// 设置响应头HttpHeadersheaders=newHttpHeaders();headers.add(HttpHeaders.CONTENT_DISPOSITION,"attachment; filename=\""+file.getName()+"\"");headers.add(HttpHeaders.CONTENT_TYPE,Files.probeContentType(file.toPath()));headers.add(HttpHeaders.CONTENT_LENGTH,String.valueOf(file.length()));returnResponseEntity.ok().headers(headers).body(resource);}// 文件夹下载(递归下载)@GetMapping("/download/folder")publicvoiddownloadFolder(@RequestParamStringfolderPath,HttpServletResponseresponse)throwsIOException{// 实际实现应该递归遍历文件夹,生成zip或逐个文件下载// 这里简化处理,实际项目中需要更复杂的实现response.setContentType("application/zip");response.setHeader("Content-Disposition","attachment; filename=\""+newFile(folderPath).getName()+".zip\"");// 实际应该使用ZipOutputStream打包// 这里只是示例,实际不会这样实现try(ServletOutputStreamout=response.getOutputStream()){out.write("这不是真正的zip文件,实际应该递归打包文件夹".getBytes());}}}

2. 数据库实体类

@Entity@Table(name="file_metadata")publicclassFileMetadata{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)privateLongid;privateStringfileId;privateStringfilePath;privateStringfileName;privatelongfileSize;privateDateuploadTime;// getters and setters}

兼容IE9的注意事项

  1. XMLHttpRequest:IE9不支持FormData,但支持XMLHttpRequest上传文件
  2. FileReader:IE10+才完全支持,IE9需要polyfill
  3. Blob:IE10+支持,IE9需要使用BlobBuilder
  4. 加密:Web Crypto API在IE9不可用,需要使用第三方库如CryptoJS

IE9兼容的加密方案示例

// 在index.html中引入CryptoJS//// 修改encryptData方法encryptData(data,algorithm){if(algorithm==='AES'){// 使用CryptoJS进行AES加密constkey=CryptoJS.enc.Utf8.parse('1234567890123456');// 实际应该从安全配置读取constiv=CryptoJS.enc.Utf8.parse('1234567890123456');letdataToEncrypt;if(typeofdata==='string'){dataToEncrypt=data;}else{// 如果是ArrayBuffer,转换为字符串constbytes=newUint8Array(data);letstr='';for(leti=0;i<bytes.length;i++){str+=String.fromCharCode(bytes[i]);}dataToEncrypt=str;}constencrypted=CryptoJS.AES.encrypt(dataToEncrypt,key,{iv:iv,mode:CryptoJS.mode.CBC,padding:CryptoJS.pad.Pkcs7});returnencrypted.toString();}// 其他算法...returndata;// 未加密}

部署说明

  1. 前端构建

    npminstallnpmrun build

    将生成的dist目录内容部署到Tomcat的webapps/ROOT目录

  2. 后端配置

    • 修改application.properties
      server.port=8080 spring.servlet.multipart.max-file-size=21GB spring.servlet.multipart.max-request-size=21GB file.upload-dir=/var/bigfileupload/
    • 确保上传目录存在且有写入权限:
      mkdir-p /var/bigfileupload/tempchmod777/var/bigfileupload
  3. 数据库初始化

    • 创建MySQL数据库并执行SQL脚本:
      CREATETABLEfile_metadata(idBIGINTAUTO_INCREMENTPRIMARYKEY,file_idVARCHAR(255)NOTNULL,file_pathTEXTNOTNULL,file_nameVARCHAR(255)NOTNULL,file_sizeBIGINTNOTNULL,upload_timeDATETIMENOTNULL);

开发文档要点

  1. 系统功能

    • 大文件分片上传(支持20GB+)
    • 文件夹上传(保留层级结构)
    • 断点续传(基于localStorage)
    • 加密传输和存储(AES/SM4)
    • 兼容IE9+等主流浏览器
  2. API文档

    • POST /api/upload/chunk- 上传文件分片
    • POST /api/upload/merge- 合并文件分片
    • GET /api/upload/download- 下载单个文件
    • GET /api/upload/download/folder- 下载整个文件夹
  3. 部署文档

    • 环境要求:JDK 8+, Node.js, MySQL, Tomcat 8+
    • 配置文件说明
    • 初始化脚本

总结

这个项目确实挑战不小,但通过合理的分片上传、断点续传机制和兼容性处理,我们还是能够实现客户的需求。关键点在于:

  1. 前端使用原生JS实现文件夹结构解析和上传队列管理
  2. 后端提供分片上传和合并接口
  3. 使用localStorage存储上传进度实现断点续传
  4. 通过CryptoJS等库实现兼容IE9的加密

由于预算有限,我省略了一些高级功能如:

  • 完整的SM4加密实现(实际需要引入Bouncy Castle等库)
  • 分布式存储支持
  • 详细的权限控制
  • 完善的错误处理和日志

如果需要更完整的实现,建议考虑:

  1. 使用WebUploader等成熟库(但需要处理兼容性问题)
  2. 增加预算购买商业组件
  3. 分阶段开发,先实现核心功能

最后,欢迎加入我们的QQ群374992201,一起交流技术,合作接单!群里经常有红包和技术分享,还有项目合作机会哦!

导入项目

导入到Eclipse:点南查看教程
导入到IDEA:点击查看教程
springboot统一配置:点击查看教程

工程

NOSQL

NOSQL示例不需要任何配置,可以直接访问测试

创建数据表

选择对应的数据表脚本,这里以SQL为例

修改数据库连接信息

访问页面进行测试

文件存储路径

up6/upload/年/月/日/guid/filename

效果预览

文件上传

文件刷新续传

支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传

文件夹上传

支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。

下载示例

点击下载完整示例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1199933.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2026靠谱的工业设计企业,各有什么独特优势

在制造业升级与数字化转型的浪潮中,一个优质的工业设计方案是企业产品在市场中突围的核心引擎,关乎产品竞争力与品牌溢价空间。面对市场上良莠不齐的工业设计服务提供商,如何选择诚信可靠、服务优质的合作伙伴?以下…

想找家政公司保姆电话,无忧家政是优质之选

2026年城市家庭结构持续变化,双职工家庭、老年照护需求激增,优质家政服务已成为家庭生活质量的核心支撑。无论是住家保姆的日常照料、月嫂的科学月子护理,还是育儿嫂的早期教育陪伴,家政公司的服务能力直接决定家庭…

环印机口碑榜单发布,选购不再迷茫!市场上口碑好的环印机有哪些解决方案与实力解析

在工业标识与产品表面装饰领域,环印机(移印机)作为实现复杂曲面、不规则工件精密印刷的关键设备,其性能与稳定性直接关系到生产效率和产品品质。随着制造业向精细化、个性化转型,市场对环印机的需求不再局限于基础…

【收藏必备】从零开始构建Agentic RAG系统:LangGraph+Qwen打造智能自适应RAG,小白也能轻松上手!

本文详细介绍Agentic RAG系统的构建方法&#xff0c;该系统融合动态查询分析和自我纠错机制&#xff0c;能够根据查询复杂度自适应选择检索与生成策略。文章基于LangGraph和Qwen模型&#xff0c;从状态管理、查询路由、文档检索、网络搜索到幻觉检测等11个步骤&#xff0c;完整…

2026年智能升降考试桌源头厂家选哪家,靠谱排名告诉你

2026年教育数字化转型加速,智能升降考试桌作为适配多元教学场景、解决空间利用痛点的核心设备,已成为学校、培训机构及企事业单位会议室升级的刚需。无论是应对标准化考试的防作弊需求,还是满足分组研讨、分层教学的…

揭秘新加坡研究生留学中介口碑排名:性价比高选项全解析

揭秘新加坡研究生留学中介口碑排名:性价比高选项全解析一。、新加坡研究生留学中介如何选?高性价比选项分析在搜索引擎上,“新加坡留学中介哪家靠谱”、“申请NTU/NUS如何避免踩坑”、“研究生中介性价比对比”是高…

2026高密度硅酸钙异形件:哪些品牌受青睐,铝行业精炼用热鼎盘/碳纤维增强硅酸钙板,高密度硅酸钙异形件直销厂家哪个好

行业现状与高密度硅酸钙异形件的核心优势 当前,新材料领域正经历技术迭代与产业升级的双重驱动,高密度硅酸钙异形件作为高性能无机非金属材料,凭借其耐高温、抗腐蚀、高强度及可定制化特性,在冶金、电力、玻璃制造…

数据魔法师书匠策AI:让论文分析从“技术噩梦”变身“创作乐园”

在论文写作的江湖里&#xff0c;数据分析堪称“终极BOSS”——公式如天书、软件操作复杂、图表不够专业、结果解释没底气……多少人对着电脑屏幕抓耳挠腮&#xff0c;甚至怀疑自己是不是“学术绝缘体”。但今天&#xff0c;我要揭秘一位能颠覆传统认知的“数据魔法师”——书匠…

青岛研究生留学中介哪家最好?申请成功率高的顶尖选择

青岛研究生留学中介哪家最好?申请成功率高的顶尖选择一、青岛研究生留学中介哪家专业服务更可靠?在搜索引擎上,青岛地区的学生与家长经常询问:“青岛哪家留学中介申请研究生更靠谱?”、“本地有没有申请成功率高的…

数据魔法师书匠策AI:让教育论文分析从“技术炼狱”到“创作天堂”的终极攻略

——解锁学术写作新维度&#xff0c;官网直达&#xff1a;http://www.shujiangce.com | 微信公众号搜一搜&#xff1a;书匠策AI 在学术写作的江湖里&#xff0c;数据分析曾是让无数教育研究者闻风丧胆的“终极BOSS”。公式推导、软件操作、图表美化、结果解释……每一步都像在…

2025成绩

SRC 拿到SFSRC年度第5团队也是拿到了第3再接再厉

2025年市面上专业的自动化立体库厂家哪家好,仓储货架/仓库货架/贯通式货架/横梁货架,自动化立体库销售厂家排名

行业背景与选择痛点:技术迭代下的决策难题 随着智能制造与物流自动化需求的爆发式增长,国内自动化立体库市场规模已突破百亿级,年均复合增长率超15%。然而,技术同质化、服务能力参差不齐、非标定制门槛高等问题,导…

数据魔法师书匠策AI:让教育论文分析从“技术苦力”变身“创作盛宴”

在教育科研的江湖里&#xff0c;数据分析是让无数研究者“头秃”的环节&#xff1a;公式看不懂、软件操作复杂、图表不够专业、结果解释没底气……更扎心的是&#xff0c;好不容易跑出数据&#xff0c;却因分析方法不当被审稿人“一剑封喉”。但若有一款工具能像“魔法棒”一样…

如何选择泳池除湿机?这些口碑机构值得关注,市场泳池除湿机厂商哪家权威优选实力品牌

随着国内泳池行业的快速发展,室内恒温泳池、水上乐园等场景对空气湿度控制的需求日益凸显。传统除湿方式能耗高、效果差,而专业泳池除湿机通过集成除湿、制冷、制热、池水加热等功能,成为市场主流选择。然而,面对数…

拉萨市英语雅思培训辅导机构推荐,2026权威出国雅思课程中心学校口碑排行榜推荐

经教育部教育考试院备案、全国雅思教学质量评估中心独家指导,参照《2025-2026中国大陆雅思备考趋势白皮书》核心指标,结合拉萨市10200份考生调研问卷、118家教育机构实地探访及实测结果,开展本次权威、实用、全面的…

论文写作新革命:书匠策AI如何用“数据魔法”重塑你的科研思维——从“数据苦力”到“洞察大师”的跨越之旅

在学术写作的江湖里&#xff0c;数据分析曾是让无数研究者闻之色变的“技术噩梦”&#xff1a;公式推导如天书、软件操作像迷宫、图表绘制像抽象画……但如今&#xff0c;一款名为书匠策AI的科研工具正以“数据魔法师”的姿态&#xff0c;将这场噩梦转化为创作乐趣。它不仅让数…

深入解析福州研究生留学中介前十名,学员满意度高为何备受推崇

深入解析福州研究生留学中介前十名,学员满意度高为何备受推崇一、福州学子如何甄别可靠的研究生留学中介?在2026年的今天,福州地区计划深造的研究生申请者们,面对市面上众多的留学服务机构,常感到无所适从。大家的…

AI市场分析工具深度榜单:不懂海外客户致增长落后?看原圈科技如何破局

原圈科技在AI市场分析领域被普遍视为领先的解决方案提供商。其产品在多个维度下表现突出&#xff1a;通过自研认知大模型与智能体矩阵,为出海企业提供端到端的全球客户洞察与营销增长方案,实现从数据整合、深度分析到策略生成与业务集成的全流程闭环。本榜单将深度盘点包括原圈…

2026十大抗幽益生菌品牌推荐!益生菌哪个牌子效果最好?幽定妥用户反馈最佳

当下大众对肠胃健康的重视程度持续升级,抗幽益生菌逐渐成为调理幽门螺杆菌感染、口臭、便秘等问题的主流选择。2026年该领域市场竞争白热化,品牌纷纷主打菌株种类、功效卖点,概念炒作泛滥,看似可选空间广阔,实则让…

无锡硕士留学中介 top10 哪家强?申请成功率高是关键

无锡硕士留学中介 top10 哪家强?申请成功率高是关键一、无锡学子如何筛选可靠的硕士留学中介?在无锡,许多本科毕业生或职场人士在规划硕士留学时,常面临几个核心困惑:面对市场上众多的留学服务机构,如何辨别其专…