news 2026/7/15 5:55:50

医疗系统SpringMVC如何处理大文件上传?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
医疗系统SpringMVC如何处理大文件上传?

咱福州软件工程狗实锤了!最近为了毕设焦头烂额——要做个能打的大文件管理系统,还要支持10G上传、断点续传、加密啥的,关键是得兼容IE8这种“古董”浏览器(学校机房那台Win7+IE9的老机器,点个按钮都像在蹦迪)。找了一圈开源代码,不是缺胳膊少腿就是“仅示例”,出了问题连个搭话的人都没有……今天必须把压箱底的方案掏出来,顺便求大神救救孩子!


需求拆解:我要的到底是个啥?

简单说就仨核心:

  1. 大文件上传:10G起步,断点续传(关浏览器/重启电脑都不怕),支持文件夹(保留层级,比如/部门/2024报告/数据.xlsx)。
  2. 加密:传输过程HTTPS,存储到OSS前AES加密(密钥自己管,阿里云也偷不走)。
  3. 兼容:IE8+(包括龙芯、红莲花这些信创浏览器)、Win7+IE9,学校老机器也得跑起来。

前端:Vue3 + 原生JS + WebUploader(兼容IE8的救命稻草)

IE8不支持H5的File API,只能靠Flash补全。WebUploader是百度开源的,支持Flash/H5双模式,正好适配老浏览器。前端核心逻辑:分片上传、断点查询、文件夹遍历、加密预处理。

1. 前端关键代码(Vue3组件)
import SparkMD5 from 'spark-md5'; // 用于生成文件唯一标识(MD5) import WebUploader from 'webuploader'; // 引入WebUploader export default { data() { return { uploader: null, progress: 0, fileMd5: '', // 当前文件的MD5(用于断点续传) chunkSize: 2 * 1024 * 1024, // 分片大小2MB(10G文件分5000片) uploadedChunks: [] // 已上传的分片序号 }; }, mounted() { this.initUploader(); }, methods: { initUploader() { // 初始化WebUploader(兼容IE8的Flash模式) this.uploader = WebUploader.create({ swf: '/static/webuploader/Uploader.swf', // Flash路径(IE8必须) server: '/api/upload/init', // 初始化接口(获取文件MD5) pick: '#filePicker', dnd: '#dndArea', paste: document.body, chunked: true, // 开启分片 chunkSize: this.chunkSize, compress: false, // 不压缩(大文件必须) fileNumLimit: 1000, // 最大文件数 fileSizeLimit: 10 * 1024 * 1024 * 1024, // 10G限制 accept: { title: 'All Files', extensions: '*' } }); // 文件加入队列时生成MD5(用于断点续传) this.uploader.on('fileQueued', (file) => { this.fileMd5 = ''; // 重置MD5 this.uploadedChunks = []; // 重置已上传分片 this.calculateFileMd5(file); // 计算文件MD5 }); // 分片上传前查询已上传的分片 this.uploader.on('uploadBeforeSend', (obj, data) => { data.md5 = this.fileMd5; // 传递MD5给服务端 data.chunk = obj.chunk; // 当前分片序号 data.chunks = obj.chunks; // 总分片数 }); // 上传进度更新 this.uploader.on('uploadProgress', (file, percentage) => { this.progress = Math.round(percentage * 100); }); // 上传完成回调 this.uploader.on('uploadFinished', (file) => { this.$message.success('上传完成!'); }); }, // 计算文件MD5(关键:断点续传的唯一标识) calculateFileMd5(file) { const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; const chunkSize = this.chunkSize; const chunks = Math.ceil(file.size / chunkSize); let currentChunk = 0; const spark = new SparkMD5.ArrayBuffer(); const fileReader = new FileReader(); fileReader.onload = (e) => { spark.append(e.target.result); // 读取分片内容并计算MD5 currentChunk++; if (currentChunk < chunks) { loadNext(); } else { this.fileMd5 = spark.end(); // 所有分片读取完成,得到最终MD5 // 查询服务端已上传的分片 this.queryUploadedChunks(this.fileMd5); } }; const loadNext = () => { const start = currentChunk * chunkSize; const end = Math.min(start + chunkSize, file.size); fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); }; loadNext(); }, // 查询服务端已上传的分片(断点续传核心) async queryUploadedChunks(md5) { try { const res = await this.$http.get(`/api/upload/chunks?md5=${md5}`); this.uploadedChunks = res.data.uploadedChunks; // 服务端返回已上传的分片序号(如[0,1,2]) // 告诉WebUploader跳过已上传的分片 this.uploader.options.chunkRetry = 0; // 失败不重试(已上传的分片跳过) this.uploader.upload(); // 开始上传 } catch (err) { console.error('查询已上传分片失败', err); } }, startUpload() { this.uploader.upload(); }, pauseUpload() { this.uploader.stop(); } } };
2. 前端兼容性处理
  • IE8:依赖Flash(Uploader.swf),需在index.html中引入flash.js解决跨域问题。
  • 文件夹上传:IE9+支持webkitdirectory属性(WebUploader自动处理),IE8只能逐个上传文件,需手动记录路径(比如让用户输入文件夹结构,或者在上传时让用户在文件名前加路径前缀,如部门/2024报告/文件.txt)。
  • 加密预处理:敏感文件可在前端用CryptoJS.AES.encrypt加密后再上传(密钥由用户输入,后端存储时二次加密)。

后端:SpringBoot + 分片合并 + 加密存储

后端核心逻辑:接收分片、记录进度、合并文件、加密存储到OSS。

1. 数据库设计(MySQL)

需要两张表:file_info(文件元信息)和file_chunk(分片记录)。

-- 文件元信息表CREATETABLEfile_info(idBIGINTPRIMARYKEYAUTO_INCREMENT,file_nameVARCHAR(255)NOTNULL,-- 文件名(含路径,如"部门/2024报告/数据.xlsx")file_sizeBIGINTNOTNULL,-- 文件大小(字节)md5VARCHAR(32)NOTNULL,-- 文件MD5(唯一标识)oss_pathVARCHAR(255),-- OSS存储路径(加密后的文件)encrypt_keyVARCHAR(255),-- 加密密钥(AES密钥,Base64编码)create_timeDATETIMEDEFAULTCURRENT_TIMESTAMP);-- 文件分片记录表CREATETABLEfile_chunk(idBIGINTPRIMARYKEYAUTO_INCREMENT,file_md5VARCHAR(32)NOTNULL,-- 关联file_info.md5chunk_numberINTNOTNULL,-- 分片序号(0开始)chunk_pathVARCHAR(255)NOTNULL,-- 分片临时存储路径is_uploadedTINYINTDEFAULT0,-- 是否已上传(0未上传,1已上传)create_timeDATETIMEDEFAULTCURRENT_TIMESTAMP,UNIQUEKEY(file_md5,chunk_number)-- 防止重复分片);
2. 后端关键接口(SpringBoot)
@RestController@RequestMapping("/api/upload")publicclassUploadController{@AutowiredprivateFileServicefileService;// 初始化上传(生成文件MD5并查询已上传分片)@PostMapping("/init")publicResultinitUpload(@RequestParam("md5")Stringmd5){FileRecordfileRecord=fileService.getByMd5(md5);if(fileRecord!=null){// 文件已存在(秒传)returnResult.success("文件已存在",fileRecord.getOssPath());}// 创建分片记录表(初始化所有分片为未上传)fileService.initChunks(md5);returnResult.success();}// 查询已上传的分片@GetMapping("/chunks")publicResultqueryUploadedChunks(@RequestParam("md5")Stringmd5){ListuploadedChunks=fileService.getUploadedChunks(md5);returnResult.success("查询成功",Map.of("uploadedChunks",uploadedChunks));}// 接收分片上传@PostMapping("/chunk")publicResultuploadChunk(@RequestParam("file")MultipartFilefile,@RequestParam("md5")Stringmd5,@RequestParam("chunk")IntegerchunkNumber,@RequestParam("chunks")IntegertotalChunks)throwsIOException{// 保存分片到临时目录(本地或OSS临时空间)StringtempDir="/tmp/upload/"+md5;Filedir=newFile(tempDir);if(!dir.exists())dir.mkdirs();StringchunkPath=tempDir+"/chunk_"+chunkNumber;file.transferTo(newFile(chunkPath));// 记录分片已上传fileService.markChunkUploaded(md5,chunkNumber);// 检查是否所有分片已上传(触发合并)if(fileService.isAllChunksUploaded(md5,totalChunks)){fileService.mergeChunks(md5,totalChunks);}returnResult.success();}// 合并分片并加密存储到OSSpublicvoidmergeChunks(Stringmd5,IntegertotalChunks)throwsIOException{FileRecordfileRecord=fileService.getByMd5(md5);if(fileRecord==null)thrownewRuntimeException("文件不存在");// 合并分片(按顺序读取临时文件,写入最终文件)StringtempDir="/tmp/upload/"+md5;FileoutputFile=newFile(tempDir+"/merged_"+System.currentTimeMillis());try(RandomAccessFileraf=newRandomAccessFile(outputFile,"rw")){for(inti=0;i<totalChunks;i++){FilechunkFile=newFile(tempDir+"/chunk_"+i);byte[]bytes=Files.readAllBytes(chunkFile.toPath());raf.write(bytes);chunkFile.delete();// 删除临时分片}}// 加密文件(AES加密)StringencryptKey=AESUtil.generateKey();// 生成随机密钥(或用户传入)byte[]encryptedBytes=AESUtil.encrypt(Files.readAllBytes(outputFile.toPath()),encryptKey);// 上传加密后的文件到OSS(阿里云OSS SDK)StringossPath="encrypted_files/"+fileRecord.getFileName();OSSClientossClient=newOSSClient("oss-cn-hangzhou.aliyuncs.com","accessKeyId","accessKeySecret");ossClient.putObject("your-bucket",ossPath,newByteArrayInputStream(encryptedBytes));// 更新文件元信息(删除临时目录)fileRecord.setOssPath(ossPath);fileRecord.setEncryptKey(encryptKey);fileRecord.setFileSize(outputFile.length());fileRecordMapper.updateById(fileRecord);FileUtils.deleteDirectory(newFile(tempDir));}}
3. 加密工具类(AES)
publicclassAESUtil{privatestaticfinalStringALGORITHM="AES";privatestaticfinalStringKEY_ALGORITHM="AES";privatestaticfinalStringDEFAULT_CIPHER_ALGORITHM="AES/ECB/PKCS5Padding";// 生成随机密钥(Base64编码)publicstaticStringgenerateKey(){KeyGeneratorkeyGen=KeyGenerator.getInstance(KEY_ALGORITHM);keyGen.init(256);// 256位密钥(更安全)SecretKeysecretKey=keyGen.generateKey();returnBase64.getEncoder().encodeToString(secretKey.getEncoded());}// 加密文件内容publicstaticbyte[]encrypt(byte[]data,Stringkey)throwsException{KeysecretKey=newSecretKeySpec(Base64.getDecoder().decode(key),KEY_ALGORITHM);Ciphercipher=Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE,secretKey);returncipher.doFinal(data);}// 解密文件内容(下载时用)publicstaticbyte[]decrypt(byte[]data,Stringkey)throwsException{KeysecretKey=newSecretKeySpec(Base64.getDecoder().decode(key),KEY_ALGORITHM);Ciphercipher=Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);cipher.init(Cipher.DECRYPT_MODE,secretKey);returncipher.doFinal(data);}}

兼容性兜底方案(IE8/IE9)

  • Flash上传:WebUploader的Flash模式在IE8下必须,需确保swf文件路径正确,且浏览器允许Flash运行(学校机房可能需要调整安全设置)。
  • 分片大小:IE8对分片支持一般,建议分片大小设为2MB(太小会增加请求次数,太大容易超时)。
  • 文件夹上传:IE8不支持webkitdirectory,只能让用户手动输入文件夹路径(比如在文件名前加部门/2024报告/前缀),后端根据路径创建OSS目录。

毕设演示注意事项

  1. 本地测试:用Win7+IE9虚拟机测试上传流程(断点续传:上传到50%时关闭浏览器,重新打开继续上传)。
  2. OSS演示:展示加密后的文件在OSS中的存储(密钥单独管理,不展示给用户)。
  3. 兼容性截图:拍IE8/龙芯浏览器的上传界面,证明“真·全浏览器支持”。

求救!大神在哪里?

现在代码框架搭好了,但有几个坑还没填:

  • IE8下Flash上传的跨域问题(crossdomain.xml配置)。
  • 大文件分片合并时的内存溢出(10G文件合并时,Java内存不够怎么办?可能需要流式合并)。
  • 加密密钥的安全存储(用户忘记密钥怎么办?需要设计密钥找回功能)。

群里加了个374992201,新人有红包!求大神来群里指导,或者直接甩个DEMO链接!毕设答辩就靠这个了,要是能上线,说不定还能接点外包(群里说推荐项目提20%提成,10万项目提2万,比打工香多了!)

(PS:老师说“能跑起来就行”,但我想做得更完美——毕竟这是大学最后作业了,冲就完事了!)

SQL示例

创建数据库

配置数据库连接

自动下载maven依赖

启动项目

启动成功

访问及测试

默认页面接口定义

在浏览器中访问

数据表中的数据

效果预览

文件上传

文件刷新续传

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

文件夹上传

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

示例下载

下载完整示例

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/9 21:19:48

数智化战士们,马年六要六不要请收好

骏马奔腾启新程&#xff0c;数字转型正当时&#xff01; 值此马年新春&#xff0c; 湖北 CIO 圈为各位同仁整理六要六不要&#xff0c;愿大家在新的一年里&#xff0c;策马扬鞭&#xff0c;驰骋数字蓝海&#xff0c;再创转型佳绩&#xff01;六要 要锚定战略&#xff0c;以数智…

作者头像 李华
网站建设 2026/7/5 13:44:16

工业温湿度监测布线优化与远程管控的技术实现方案

在工业自动化、数据中心运维、冷链仓储等场景中&#xff0c;温湿度的精准采集与远程管控&#xff0c;是保障设备稳定运行、物料安全存储、工艺合规性的核心环节。当前&#xff0c;传统温湿度监测方案普遍存在布线复杂、供电适配性差、协议兼容困难、运维效率低等技术痛点&#…

作者头像 李华
网站建设 2026/7/1 8:14:15

Element Plus:Vue 3时代的现代化UI组件库解析

目录 引言 一、技术架构 1.1 组件注册与插件机制 1.2 响应式系统与性能优化 1.3 TypeScript深度集成 二、设计理念 2.1 原子化设计系统 2.2 响应式布局引擎 2.3 国际化与无障碍支持 三、核心功能 3.1 数据展示组件 3.2 表单验证系统 3.3 高级交互组件 四、生态扩展 4.1 主题…

作者头像 李华
网站建设 2026/7/7 1:16:44

OTA 会清空的情况有哪些?

persist.* 属性是怎么存的&#xff1f; 在 Android 里&#xff1a; persist.xxx 类型的属性 会被写入 /data/property/&#xff08;老版本在 /data/system/property/&#xff09; 属于 data 分区的数据 &#x1f449; OTA 升级&#xff08;不 wipe data&#xff09;时&…

作者头像 李华
网站建设 2026/7/1 8:14:54

MindSpore实战:昇腾NPU上的深度学习模型优化全记录

1 引言&#xff1a;为何选择MindSpore与昇腾生态 作为一名长期从事计算机视觉应用的开发者&#xff0c;我最近全面转向华为的MindSpore深度学习框架与昇腾NPU硬件平台。这一选择不仅源于对国产AI生态的支持&#xff0c;更是考虑到其在分布式训练和推理性能上的独特优势。 与主…

作者头像 李华