WebUploader分块上传在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/1140832.shtml

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

相关文章

Hunyuan翻译系统搭建全流程:从镜像拉取到服务上线

Hunyuan翻译系统搭建全流程&#xff1a;从镜像拉取到服务上线 1. 引言&#xff1a;腾讯开源的HY-MT1.5翻译大模型 随着全球化进程加速&#xff0c;高质量、低延迟的机器翻译需求日益增长。传统云翻译服务虽功能成熟&#xff0c;但在数据隐私、响应速度和定制化方面存在局限。为…

腾讯HY-MT1.5开源协议解读:商用是否合规?律师建议

腾讯HY-MT1.5开源协议解读&#xff1a;商用是否合规&#xff1f;律师建议 1. 引言&#xff1a;腾讯开源翻译模型的技术背景与行业意义 近年来&#xff0c;随着大模型在自然语言处理领域的广泛应用&#xff0c;机器翻译技术正从“通用可用”向“精准可控”演进。在此背景下&…

HY-MT1.5教育行业应用案例:少数民族语言教学系统搭建

HY-MT1.5教育行业应用案例&#xff1a;少数民族语言教学系统搭建 随着人工智能技术在教育领域的深入渗透&#xff0c;多语言翻译模型正成为推动教育公平与文化传承的重要工具。尤其在少数民族地区&#xff0c;语言障碍长期制约着优质教育资源的普及与双向文化交流的深化。传统…

HY-MT1.5-7B翻译优化实战:上下文感知+术语干预部署完整指南

HY-MT1.5-7B翻译优化实战&#xff1a;上下文感知术语干预部署完整指南 1. 引言&#xff1a;腾讯开源的混元翻译大模型新标杆 随着全球化进程加速&#xff0c;高质量、低延迟的机器翻译需求日益增长。传统商业翻译API虽具备一定能力&#xff0c;但在定制化、隐私保护和边缘部署…

HY-MT1.5如何接入现有系统?API接口调用代码实例详解

HY-MT1.5如何接入现有系统&#xff1f;API接口调用代码实例详解 1. 引言&#xff1a;腾讯开源的混元翻译大模型HY-MT1.5 随着全球化业务的加速推进&#xff0c;高质量、低延迟的机器翻译能力成为企业出海、内容本地化和跨语言沟通的核心基础设施。然而&#xff0c;依赖第三方商…

Hunyuan翻译模型能替代谷歌吗?真实场景对比测试

Hunyuan翻译模型能替代谷歌吗&#xff1f;真实场景对比测试 在大模型驱动的AI时代&#xff0c;机器翻译正从“可用”迈向“精准、可干预、可部署”的新阶段。腾讯近期开源的混元翻译模型HY-MT1.5系列&#xff0c;凭借其对多语言、边缘部署和上下文感知能力的支持&#xff0c;迅…

大姨妈来了,搞笑高级版说法合集

1、本月“亲戚”到访&#xff0c;本人开启七天躺平模式。2、她带着痛感突袭&#xff0c;我的腰和肚子集体罢工。3、与姨妈的N次博弈&#xff0c;这次依旧是我输得彻底。4、女生的成长仪式感&#xff0c;每月一次“流血修行”。5、刀割般的痛都弱爆了&#xff0c;姨妈痛才是满级…

开源翻译模型哪家强?HY-MT1.5与阿里通义千问对比评测

开源翻译模型哪家强&#xff1f;HY-MT1.5与阿里通义千问对比评测 在多语言交流日益频繁的今天&#xff0c;高质量的机器翻译模型成为跨语言沟通的核心基础设施。近年来&#xff0c;国内大厂纷纷布局开源翻译模型生态&#xff0c;其中腾讯混元团队推出的 HY-MT1.5 系列和阿里通…

HY-MT1.5-7B媒体融合应用:短视频跨语言内容生产实战

HY-MT1.5-7B媒体融合应用&#xff1a;短视频跨语言内容生产实战 1. 引言&#xff1a;AI翻译如何重塑短视频全球化生产 随着全球短视频平台的迅猛发展&#xff0c;内容创作者面临一个核心挑战&#xff1a;如何高效跨越语言与文化壁垒&#xff0c;实现本地化精准传播。传统人工…

²⁰²⁶申请霸占你的微信置顶!

快乐发电站 &#x1f33c;&#x13212;&#x133f8; ⌇&#x1d5e1;&#x1d5f6;&#x1d5f0;&#x1d5f2; 成年人主打一个“佛系躺平”&#xff0c; 不内耗&#xff0c;不抬杠&#xff0c;主打一个随缘。 ♡⃝ &#x1d47a;&#x1d489;&#x1d482;&#x1d49…

NVSHMEM 是什么

NVSHMEM&#xff08;NVIDIA Shared Memory&#xff09;是NVIDIA开发的一种基于OpenSHMEM规范的并行编程接口&#xff0c;专为NVIDIA GPU集群提供高效且可扩展的通信能力。它通过创建跨越多个GPU内存的全局地址空间&#xff0c;实现细粒度的GPU发起的数据传输和同步操作。 核心特…

中小企业AI部署新选择:HY-MT1.5多语言翻译落地指南

中小企业AI部署新选择&#xff1a;HY-MT1.5多语言翻译落地指南 随着全球化进程加速&#xff0c;中小企业对高效、低成本的多语言翻译解决方案需求日益增长。传统商业翻译API成本高、数据隐私风险大&#xff0c;而自研模型又面临技术门槛和算力资源限制。在此背景下&#xff0c…

学霸同款2026 MBA论文神器TOP10:开题报告文献综述全测评

学霸同款2026 MBA论文神器TOP10&#xff1a;开题报告文献综述全测评 推荐2&#xff1a;「Grammarly」&#xff08;学术版&#xff09;——英文论文润色标杆&#xff08;推荐指数&#xff1a;★★★★☆&#xff09; "对于需要撰写高质量英文论文的MBA学生而言&#xff0c;…

Hunyuan-HY-MT1.5优化教程:通过量化压缩实现更低显存占用

Hunyuan-HY-MT1.5优化教程&#xff1a;通过量化压缩实现更低显存占用 1. 引言 随着多语言交流需求的不断增长&#xff0c;高质量、低延迟的翻译模型成为智能应用的核心组件。腾讯开源的混元翻译大模型 HY-MT1.5 系列&#xff0c;凭借其在多语言互译、边缘部署和功能增强方面的…

为什么HY-MT1.5-7B部署总失败?术语干预功能开启实战教程揭秘

为什么HY-MT1.5-7B部署总失败&#xff1f;术语干预功能开启实战教程揭秘 近年来&#xff0c;随着多语言交流需求的激增&#xff0c;高质量翻译模型成为AI应用落地的关键组件。腾讯开源的混元翻译大模型 HY-MT1.5 系列&#xff0c;凭借其在多语言互译、民族语言支持和专业场景优…

为什么HY-MT1.5-7B部署总失败?术语干预功能开启实战教程揭秘

为什么HY-MT1.5-7B部署总失败&#xff1f;术语干预功能开启实战教程揭秘 近年来&#xff0c;随着多语言交流需求的激增&#xff0c;高质量翻译模型成为AI应用落地的关键组件。腾讯开源的混元翻译大模型 HY-MT1.5 系列&#xff0c;凭借其在多语言互译、民族语言支持和专业场景优…

为什么HY-MT1.5-7B部署总失败?术语干预功能开启实战教程揭秘

为什么HY-MT1.5-7B部署总失败&#xff1f;术语干预功能开启实战教程揭秘 近年来&#xff0c;随着多语言交流需求的激增&#xff0c;高质量翻译模型成为AI应用落地的关键组件。腾讯开源的混元翻译大模型 HY-MT1.5 系列&#xff0c;凭借其在多语言互译、民族语言支持和专业场景优…

[特殊字符][特殊字符][特殊字符][特殊字符],微信个签该换搞笑版啦

在摸鱼间隙灵感爆发&#x1fae7; 干饭不积极&#xff0c;思想有问题&#x1f4ab; 摸鱼第一名&#xff0c;打工过得去✨ 奶茶喝不够&#xff0c;快乐没尽头&#x1f337; 体重别上涨&#xff0c;钱包要膨胀&#x1f31f; 上班盼下班&#xff0c;周末不孤单&#x1f33f; 脱发别…

开源跑腿系统源码整体架构解析:从下单到配送的完整流程

跑腿业务看似简单&#xff0c;但真正落到系统层面&#xff0c;会涉及下单、计价、调度、接单、配送、结算等一整套闭环逻辑。本文结合一套典型的开源跑腿系统源码&#xff0c;从整体架构入手&#xff0c;完整拆解“从用户下单到骑手配送完成”的核心流程&#xff0c;并通过代码…

开源跑腿系统源码整体架构解析:从下单到配送的完整流程

跑腿业务看似简单&#xff0c;但真正落到系统层面&#xff0c;会涉及下单、计价、调度、接单、配送、结算等一整套闭环逻辑。本文结合一套典型的开源跑腿系统源码&#xff0c;从整体架构入手&#xff0c;完整拆解“从用户下单到骑手配送完成”的核心流程&#xff0c;并通过代码…