Java老哥外包救星:原生JS大文件上传全栈方案(IE9兼容+20G断点续传)
兄弟,作为甘肃接外包的Java程序员,我太懂你现在的处境了——客户要20G大文件上传,还要文件夹层级保留、IE9兼容、加密传输,预算还卡得死死的。网上找的代码全是“文件上传半成品”,文件夹功能要么丢层级,要么IE9直接崩。别慌!我熬了半个月啃下的原生JS+SpringBoot全栈方案,今天全盘托出,保证你能直接给客户演示,验收时被夸“这钱花得值”!
一、方案核心(专治外包项目的“奇葩需求”)
1. 功能全覆盖(客户看了直点头)
- 20G级大文件传输:分片上传(10MB/片),断点续传(
localStorage+MySQL双存储进度,关浏览器/重启电脑不丢)。 - 文件夹层级保留:递归遍历文件树(前端生成相对路径),后端按
/父文件夹/子文件路径存储(IE9用“伪路径+元数据”方案兜底)。 - 加密传输+存储:传输层HTTPS+AES-256(密钥动态生成),存储层SM4(国密算法,符合客户保密要求)。
- 非打包下载:流式传输逐个文件(几万文件也不卡),支持“文件夹结构树”展示(避免服务器内存爆炸)。
- 全浏览器兼容:IE9(XHR2+File API补丁)→ Edge/Chrome/Firefox → macOS/Linux/CentOS(信创环境)。
2. 预算友好(0商业授权费)
- 原生JS实现:0商业库,用
crypto-js(AES)+spark-md5(文件哈希),代码直接嵌入Vue3项目。 - 轻量级依赖:仅需Vue3、axios、crypto-js,无额外费用。
- 本地存储适配:文件直接存服务器(Tomcat部署路径可配置),无需OSS,代码动态适配Windows/Linux。
3. 客户要的“铁证”全给齐
- 完整源码包(前端+后端+SQL脚本),导入就能跑。
- 部署文档(Tomcat配置+MySQL连接+文件路径设置),手把手教客户运维。
- 7*24小时支持:群里200+Java/前端大佬互助(QQ群:374992201),遇到坑直接甩日志截图,老炮儿带你改。
二、前端核心代码(Vue3兼容版,附详细注释)
1. 文件夹上传组件(兼容IE9+所有主流浏览器)
// 兼容IE9的polyfill(必须引入!) import 'es6-promise/auto'; // 补Promise import 'whatwg-fetch'; // 补fetch import Blob from 'blob-polyfill'; // 补Blob(IE9不支持slice) if (!window.console) window.console = { log: () => {}, error: () => {} }; // 补console // 依赖库(需手动安装:npm install crypto-js axios spark-md5) import CryptoJS from 'crypto-js'; import axios from 'axios'; import SparkMD5 from 'spark-md5'; export default { data() { return { uploadTasks: [], // 上传任务列表(核心数据) chunkSize: 10 * 1024 * 1024, // 10MB分片(20G文件分2000片,平衡速度与内存) aesKey: '', // AES密钥(从后端动态获取) currentTaskId: '', // 当前上传任务的ID isUploading: false // 全局上传状态锁 }; }, mounted() { this.initAesKey(); // 初始化AES密钥(首次加载时生成) this.checkResumeTasks(); // 启动时检查本地是否有未完成的任务 }, methods: { /** * 检查本地是否有未完成的上传任务(启动时自动执行) */ checkResumeTasks() { // 遍历localStorage所有key(实际项目中需优化,这里简化) for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key.startsWith('upload_')) { const taskData = JSON.parse(localStorage.getItem(key)); this.uploadTasks.push(taskData); } } if (this.uploadTasks.length > 0) { this.$message.warning('检测到未完成的上传任务,是否继续?'); } } } };三、后端核心代码(SpringBoot,附关键逻辑)
1. 分片上传接口(ChunkController.java)
// src/main/java/com/example/uploader/controller/ChunkController.java@RestController@RequestMapping("/api/upload")publicclassChunkController{@Value("${upload.chunk.size}")privatelongchunkSize;@AutowiredprivateUploadServiceuploadService;/** * 分片上传接口 */@PostMapping("/chunk")publicResponseEntityuploadChunk(@RequestParam("taskId")StringtaskId,@RequestParam("chunkIndex")intchunkIndex,@RequestParam("totalChunks")inttotalChunks,@RequestParam("filePath")StringfilePath,@RequestParam("chunk")MultipartFilechunk){try{// 1. 解密分片(AES-256)byte[]encryptedData=chunk.getBytes();StringaesKey=uploadService.getAesKeyFromKms();// 从KMS获取动态密钥byte[]decryptedData=uploadService.aesDecrypt(encryptedData,aesKey);// 2. 创建存储目录(兼容Linux/Windows)StringstoragePath=uploadService.getStoragePath()+filePath;Filedir=newFile(storagePath);if(!dir.exists()){dir.mkdirs();// 递归创建目录}// 3. 保存分片到服务器(非打包)StringchunkPath=storagePath+File.separator+chunkIndex;FilechunkFile=newFile(chunkPath);FileUtils.writeByteArrayToFile(chunkFile,decryptedData);// 使用Apache Commons IO// 4. 记录进度到MySQL(断点续传关键)UploadProgressprogress=newUploadProgress();progress.setTaskId(taskId);progress.setFilePath(filePath);progress.setChunkIndex(chunkIndex);progress.setTotalChunks(totalChunks);progress.setUploadedSize(decryptedData.length);progress.setStatus("UPLOADING");uploadService.saveOrUpdateProgress(progress);returnResponseEntity.ok().body(Result.success("分片上传成功"));}catch(Exceptione){e.printStackTrace();returnResponseEntity.status(500).body(Result.error("上传失败:"+e.getMessage()));}}}2. 断点续传进度服务(UploadService.java)
// src/main/java/com/example/uploader/service/UploadService.java@ServicepublicclassUploadService{@AutowiredprivateUploadProgressMapperprogressMapper;@Value("${upload.storage.path}")privateStringstoragePath;@Value("${encryption.aes.key}")privateStringaesKey;/** * 保存或更新上传进度(支持MySQL) */publicvoidsaveOrUpdateProgress(UploadProgressprogress){UploadProgressexisting=progressMapper.selectByTaskIdAndFilePathAndChunkIndex(progress.getTaskId(),progress.getFilePath(),progress.getChunkIndex());if(existing!=null){progress.setId(existing.getId());progressMapper.updateById(progress);}else{progressMapper.insert(progress);}}}3. 下载接口(DownloadController.java)
// src/main/java/com/example/uploader/controller/DownloadController.java@RestController@RequestMapping("/api/download")publicclassDownloadController{@AutowiredprivateUploadServiceuploadService;/** * 非打包下载文件夹(流式传输) */@GetMapping("/folder")publicvoiddownloadFolder(@RequestParam("taskId")StringtaskId,@RequestParam("filePath")StringfilePath,HttpServletResponseresponse)throwsIOException{// 1. 验证下载权限(根据业务ID校验)if(!uploadService.validateDownloadPermission(taskId)){response.sendError(403,"无下载权限");return;}// 2. 获取文件夹下所有文件列表(从数据库查询)ListfileList=uploadService.getFileListByPath(filePath);// 3. 设置响应头(非打包)response.setContentType("application/octet-stream");response.setHeader("Content-Disposition","attachment; filename=\""+filePath+"\"");// 4. 流式传输每个文件(避免内存溢出)for(FileInfofile:fileList){InputStreamfileStream=newFileInputStream(file.getPhysicalPath());IOUtils.copy(fileStream,response.getOutputStream());response.getOutputStream().flush();}response.getOutputStream().close();}}四、数据库脚本(MySQL)
-- 创建上传进度表(记录分片上传状态)CREATETABLEIFNOTEXISTSupload_progress(idINTUNSIGNEDAUTO_INCREMENTPRIMARYKEY,task_idVARCHAR(255)NOTNULLCOMMENT'任务ID(如upload_1620000000_abc123)',file_pathVARCHAR(1000)NOTNULLCOMMENT'文件存储路径(如/upload_1620000000/folder_123/file.txt)',chunk_indexINTUNSIGNEDNOTNULLCOMMENT'当前分片索引(0开始)',total_chunksINTUNSIGNEDNOTNULLCOMMENT'总分片数',uploaded_sizeBIGINTUNSIGNEDNOTNULLCOMMENT'已上传大小(字节)',statusVARCHAR(50)NOTNULLDEFAULT'PENDING'COMMENT'状态:PENDING/RESUMING/UPLOADING/FAILED/SUCCESS',create_timeTIMESTAMPDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',update_timeTIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间')ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;-- 唯一约束(防止同一任务同一分片重复记录)CREATEUNIQUEINDEXUQ_Task_File_ChunkONupload_progress(task_id,file_path,chunk_index);五、部署与兼容性调试(客户最关心的)
1. IE9兼容性(客户老机器必过)
- File API补丁:引入
Blob.js(https://github.com/eligrey/Blob.js),解决File.slice不支持问题(代码中已预留位置,需在index.html中引入)。 - FormData兼容:IE9不支持
FormData,代码中已用iframe模拟上传(无需额外处理,前端自动降级)。 - localStorage容量:IE9的
localStorage容量限制为5MB,大文件进度需分块存储(代码中已用taskId分key存储)。
2. 大文件分片(20G传输关键)
- 分片大小:选10MB是因为IE9内存限制,太大可能导致浏览器崩溃;太小会增加请求次数(实际可根据客户网络调整)。
- 断点续传:前端用
localStorage缓存已上传的分片索引和大小,后端用MySQL记录,双重保障(客户重启电脑也能续传)。
3. 文件夹层级保留(客户核心需求)
- 路径生成:现代浏览器用
file.webkitRelativePath获取相对路径;IE9用随机生成的文件夹名兜底(需用户手动输入文件夹名,这里简化为随机字符串)。 - 后端存储:后端按
filePath字段创建目录结构(如E:/uploads/upload_1620000000/folder_123/file.txt),确保层级不变。
六、预算与合作模式(客户最关心的)
1. 预算控制(100元以内)
- 源码一次性交付:提供完整前端Vue3组件、后端SpringBoot代码、数据库脚本,无后续授权费。
- 免费技术支持:7*24小时远程协助(故障排查、版本升级、兼容性调试)。
2. 合作材料(满足客户采购要求)
- 项目证明:提供过往外包项目合同(含项目名称、金额、验收报告)。
- 技术资质:Java开发工具授权(IntelliJ IDEA)、服务器配置文档(Tomcat+MySQL)。
兄弟,这套方案你拿给客户演示,保证验收时客户拍大腿说“这钱花得值”!有问题直接甩日志到群里(QQ群:374992201),老炮儿我24小时在线帮你改。记住:不会就查文档,卡壳就问群友——咱Java程序员,接外包就是要“稳准狠”!
导入项目
导入到Eclipse:点南查看教程
导入到IDEA:点击查看教程
springboot统一配置:点击查看教程
工程
NOSQL
NOSQL示例不需要任何配置,可以直接访问测试
创建数据表
选择对应的数据表脚本,这里以SQL为例
修改数据库连接信息
访问页面进行测试
文件存储路径
up6/upload/年/月/日/guid/filename
效果预览
文件上传
文件刷新续传
支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传
文件夹上传
支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。
下载示例
点击下载完整示例