咱福州软件工程狗实锤了!最近为了毕设焦头烂额——要做个能打的大文件管理系统,还要支持10G上传、断点续传、加密啥的,关键是得兼容IE8这种“古董”浏览器(学校机房那台Win7+IE9的老机器,点个按钮都像在蹦迪)。找了一圈开源代码,不是缺胳膊少腿就是“仅示例”,出了问题连个搭话的人都没有……今天必须把压箱底的方案掏出来,顺便求大神救救孩子!
需求拆解:我要的到底是个啥?
简单说就仨核心:
- 大文件上传:10G起步,断点续传(关浏览器/重启电脑都不怕),支持文件夹(保留层级,比如
/部门/2024报告/数据.xlsx)。 - 加密:传输过程HTTPS,存储到OSS前AES加密(密钥自己管,阿里云也偷不走)。
- 兼容: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目录。
毕设演示注意事项
- 本地测试:用
Win7+IE9虚拟机测试上传流程(断点续传:上传到50%时关闭浏览器,重新打开继续上传)。 - OSS演示:展示加密后的文件在OSS中的存储(密钥单独管理,不展示给用户)。
- 兼容性截图:拍IE8/龙芯浏览器的上传界面,证明“真·全浏览器支持”。
求救!大神在哪里?
现在代码框架搭好了,但有几个坑还没填:
- IE8下Flash上传的跨域问题(
crossdomain.xml配置)。 - 大文件分片合并时的内存溢出(10G文件合并时,Java内存不够怎么办?可能需要流式合并)。
- 加密密钥的安全存储(用户忘记密钥怎么办?需要设计密钥找回功能)。
群里加了个374992201,新人有红包!求大神来群里指导,或者直接甩个DEMO链接!毕设答辩就靠这个了,要是能上线,说不定还能接点外包(群里说推荐项目提20%提成,10万项目提2万,比打工香多了!)
(PS:老师说“能跑起来就行”,但我想做得更完美——毕竟这是大学最后作业了,冲就完事了!)
SQL示例
创建数据库
配置数据库连接
自动下载maven依赖
启动项目
启动成功
访问及测试
默认页面接口定义
在浏览器中访问
数据表中的数据
效果预览
文件上传
文件刷新续传
支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传
文件夹上传
支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。
示例下载
下载完整示例