新闻排版设计用什么软件,怎么优化一个网站关键词,浙江省建筑诚信平台查询系统,虚拟主机安装wordpress大文件上传系统开发指南#xff08;基于原生JSSpringBoot#xff09;
项目概述
大家好#xff0c;我是一个在浙江奋斗的Java程序员#xff0c;最近接了个刺激的外包项目 - 开发一个支持20G大文件上传下载的系统#xff0c;还要兼容IE9这种上古浏览器。客户要…大文件上传系统开发指南基于原生JSSpringBoot项目概述大家好我是一个在浙江奋斗的Java程序员最近接了个刺激的外包项目 - 开发一个支持20G大文件上传下载的系统还要兼容IE9这种上古浏览器。客户要求使用原生JS实现不能用jQuery等库支持文件夹上传保留层级结构还要加密传输和断点续传。预算只有100元但需要提供完整技术支持和源代码。这活儿听着就刺激但咱是谁是打不死的小强下面我就分享一下我的解决方案和部分代码实现。技术选型前端Vue3 CLI 原生JavaScript兼容IE9后端SpringBoot Tomcat数据库MySQL主要存用户信息和文件元数据文件存储服务器本地文件系统加密SM4国密和AES系统架构浏览器(IE9) ←HTTP→ SpringBoot后端 ←本地IO→ 文件存储 ↑ MySQL元数据前端实现关键代码1. 文件夹上传组件兼容IE9export 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. 文件上传控制器RestControllerRequestMapping(/api/upload)publicclassFileUploadController{privatestaticfinalStringUPLOAD_DIR/var/bigfileupload/;// 实际应该从配置读取privatestaticfinalintCHUNK_SIZE5*1024*1024;// 5MBPostMapping(/chunk)publicResponseEntityuploadChunk(RequestParam(fileId)StringfileId,RequestParam(chunkIndex)intchunkIndex,RequestParam(totalChunks)inttotalChunks,RequestParam(relativePath)StringrelativePath,RequestParam(fileSize)longfileSize,RequestParam(fileName)StringfileName,RequestParam(chunk)MultipartFilechunk)throwsIOException{// 创建临时目录StringtempDirUPLOAD_DIRtemp/fileId/;FiletempDirFilenewFile(tempDir);if(!tempDirFile.exists()){tempDirFile.mkdirs();}// 保存分片实际应该先解密StringchunkPathtempDirchunkIndex;chunk.transferTo(newFile(chunkPath));// 记录上传进度实际应该用数据库UploadProgressprogressnewUploadProgress();progress.setFileId(fileId);progress.setUploadedChunks(chunkIndex1);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{StringfileIdrequest.getFileId();StringtempDirUPLOAD_DIRtemp/fileId/;FiletempDirFilenewFile(tempDir);if(!tempDirFile.exists()){returnResponseEntity.badRequest().body(Map.of(error,临时文件不存在));}// 创建目标目录结构StringrelativePathrequest.getRelativePath();StringtargetPathUPLOAD_DIRrelativePath;FiletargetFilenewFile(targetPath);}// 下载文件接口非打包方式GetMapping(/download)publicResponseEntitydownloadFile(RequestParamStringfilePath,HttpServletResponseresponse)throwsIOException{// 设置响应头HttpHeadersheadersnewHttpHeaders();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(ServletOutputStreamoutresponse.getOutputStream()){out.write(这不是真正的zip文件实际应该递归打包文件夹.getBytes());}}}2. 数据库实体类EntityTable(namefile_metadata)publicclassFileMetadata{IdGeneratedValue(strategyGenerationType.IDENTITY)privateLongid;privateStringfileId;privateStringfilePath;privateStringfileName;privatelongfileSize;privateDateuploadTime;// getters and setters}兼容IE9的注意事项XMLHttpRequestIE9不支持FormData但支持XMLHttpRequest上传文件FileReaderIE10才完全支持IE9需要polyfillBlobIE10支持IE9需要使用BlobBuilder加密Web Crypto API在IE9不可用需要使用第三方库如CryptoJSIE9兼容的加密方案示例// 在index.html中引入CryptoJS//// 修改encryptData方法encryptData(data,algorithm){if(algorithmAES){// 使用CryptoJS进行AES加密constkeyCryptoJS.enc.Utf8.parse(1234567890123456);// 实际应该从安全配置读取constivCryptoJS.enc.Utf8.parse(1234567890123456);letdataToEncrypt;if(typeofdatastring){dataToEncryptdata;}else{// 如果是ArrayBuffer转换为字符串constbytesnewUint8Array(data);letstr;for(leti0;ibytes.length;i){strString.fromCharCode(bytes[i]);}dataToEncryptstr;}constencryptedCryptoJS.AES.encrypt(dataToEncrypt,key,{iv:iv,mode:CryptoJS.mode.CBC,padding:CryptoJS.pad.Pkcs7});returnencrypted.toString();}// 其他算法...returndata;// 未加密}部署说明前端构建npminstallnpmrun build将生成的dist目录内容部署到Tomcat的webapps/ROOT目录后端配置修改application.propertiesserver.port8080 spring.servlet.multipart.max-file-size21GB spring.servlet.multipart.max-request-size21GB file.upload-dir/var/bigfileupload/确保上传目录存在且有写入权限mkdir-p /var/bigfileupload/tempchmod777/var/bigfileupload数据库初始化创建MySQL数据库并执行SQL脚本CREATETABLEfile_metadata(idBIGINTAUTO_INCREMENTPRIMARYKEY,file_idVARCHAR(255)NOTNULL,file_pathTEXTNOTNULL,file_nameVARCHAR(255)NOTNULL,file_sizeBIGINTNOTNULL,upload_timeDATETIMENOTNULL);开发文档要点系统功能大文件分片上传支持20GB文件夹上传保留层级结构断点续传基于localStorage加密传输和存储AES/SM4兼容IE9等主流浏览器API文档POST /api/upload/chunk- 上传文件分片POST /api/upload/merge- 合并文件分片GET /api/upload/download- 下载单个文件GET /api/upload/download/folder- 下载整个文件夹部署文档环境要求JDK 8, Node.js, MySQL, Tomcat 8配置文件说明初始化脚本总结这个项目确实挑战不小但通过合理的分片上传、断点续传机制和兼容性处理我们还是能够实现客户的需求。关键点在于前端使用原生JS实现文件夹结构解析和上传队列管理后端提供分片上传和合并接口使用localStorage存储上传进度实现断点续传通过CryptoJS等库实现兼容IE9的加密由于预算有限我省略了一些高级功能如完整的SM4加密实现实际需要引入Bouncy Castle等库分布式存储支持详细的权限控制完善的错误处理和日志如果需要更完整的实现建议考虑使用WebUploader等成熟库但需要处理兼容性问题增加预算购买商业组件分阶段开发先实现核心功能最后欢迎加入我们的QQ群374992201一起交流技术合作接单群里经常有红包和技术分享还有项目合作机会哦导入项目导入到Eclipse点南查看教程导入到IDEA点击查看教程springboot统一配置点击查看教程工程NOSQLNOSQL示例不需要任何配置可以直接访问测试创建数据表选择对应的数据表脚本这里以SQL为例修改数据库连接信息访问页面进行测试文件存储路径up6/upload/年/月/日/guid/filename效果预览文件上传文件刷新续传支持离线保存文件进度在关闭浏览器刷新浏览器后进行不丢失仍然能够继续上传文件夹上传支持上传文件夹并保留层级结构同样支持进度信息离线保存刷新页面关闭页面重启系统不丢失上传进度。下载示例点击下载完整示例