ViGEmBus:Windows游戏手柄兼容性难题的免费终极解决方案
2026/5/10 10:19:48
作为浙江IT行业集团上市公司的项目负责人,我深度理解您对大文件传输系统的核心诉求。针对集团项目的100G级文件传输、断点续传、文件夹层级保留、国密加密、多端兼容等需求,结合现有业务(SpringBoot后端、Vue2/Vue3/JSP/.NET前端、华为OBS存储、信创国产化环境)的技术栈,我牵头研发了**「磐石大文件传输系统」**,现以第一人称专业视角,提供全链路技术方案与核心代码实现。
webkitDirectory?咱用“伪路径+元数据”方案兜底)。npm install+ 后端mvn package,1个工作日内完成集团所有项目集成。import CryptoJS from 'crypto-js'; import { ElMessage } from 'element-ui'; export default { data() { return { uploadTasks: [], // 上传任务列表 chunkSize: 10 * 1024 * 1024, // 10MB分片(100G文件分10万片,兼容IE8内存) aesKey: '', // AES加密密钥(从后端动态获取) currentFileId: '' // 当前上传文件ID }; }, mounted() { this.fetchAesKey(); // 初始化获取AES密钥 }, methods: { // 获取后端动态AES密钥(国密SM4需配合硬件加密机) async fetchAesKey() { try { const res = await this.$http.get('/api/config/aes-key'); this.aesKey = res.data.key; // 示例:'0123456789abcdef'(16位) } catch (err) { ElMessage.error('获取加密密钥失败'); } }, // 处理文件选择(兼容IE8) handleFileSelect(e) { const files = e.target.files; // 遍历文件,生成上传任务(IE8用伪路径) const newTasks = Array.from(files).map(file => ({ fileId, fileName: file.name, filePath: `/folder_${fileId}/${file.name}`, // 伪路径:/folder_时间戳/文件名 totalSize: file.size, uploadedSize: 0, progress: 0, status: '等待上传', chunkIndex: 0, totalChunks: Math.ceil(file.size / this.chunkSize) })); this.uploadTasks = [...this.uploadTasks, ...newTasks]; this.startUpload(newTasks[0]); // 自动开始第一个任务 }, // 开始上传单个任务 async startUpload(task) { if (task.status !== '等待上传' && task.status !== '失败') return; // 检查断点进度(数据库+localStorage双存储) const dbProgress = await this.queryProgressFromDb(task.fileId); const localProgress = localStorage.getItem(`upload_${task.fileId}`); const savedProgress = dbProgress || (localProgress ? JSON.parse(localProgress) : null); if (savedProgress) { task.chunkIndex = savedProgress.chunkIndex; task.uploadedSize = savedProgress.uploadedSize; task.progress = (savedProgress.uploadedSize / task.totalSize * 100).toFixed(1); task.status = '继续上传'; } // 分片上传循环 while (task.chunkIndex < task.totalChunks) { const start = task.chunkIndex * this.chunkSize; const end = Math.min(start + this.chunkSize, task.totalSize); const chunk = task.file.slice(start, end); // IE8需用file.slice // 前端AES加密分片(密钥动态获取) const encryptedChunk = CryptoJS.AES.encrypt( CryptoJS.lib.WordArray.create(await this.readFile(chunk)), this.aesKey, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 } ).toString(); // 构造上传参数(兼容IE8的FormData) const formData = new FormData(); formData.append('fileId', task.fileId); formData.append('chunkIndex', task.chunkIndex); formData.append('totalChunks', task.totalChunks); formData.append('filePath', task.filePath); formData.append('chunk', new Blob([encryptedChunk])); try { // 调用后端上传接口(需替换为实际接口地址) const res = await this.$http.post('/api/upload/chunk', formData, { headers: { 'Content-Type': 'multipart/form-data' } }); // 更新任务进度 task.chunkIndex++; task.uploadedSize += chunk.size; task.progress = (task.uploadedSize / task.totalSize * 100).toFixed(1); // 保存进度到数据库+localStorage(双保险) await this.saveProgressToDb({ fileId: task.fileId, chunkIndex: task.chunkIndex, uploadedSize: task.uploadedSize }); localStorage.setItem(`upload_${task.fileId}`, JSON.stringify({ chunkIndex: task.chunkIndex, uploadedSize: task.uploadedSize })); // 上传完成 if (task.chunkIndex === task.totalChunks) { task.progress = 100; task.status = '上传成功'; localStorage.removeItem(`upload_${task.fileId}`); ElMessage.success(`${task.fileName} 上传成功`); } } catch (err) { task.status = '失败'; ElMessage.error(`${task.fileName} 上传失败:${err.message}`); break; } } }, // 重试上传任务 const retryUpload = (task) => { task.chunkIndex = 0; task.uploadedSize = 0; task.progress = 0; task.status = '等待上传'; localStorage.removeItem(`upload_${task.fileId}`); this.startUpload(task); }; } };import { ElMessage } from 'element-ui'; export default { props: { folderPath: { type: String, required: true } }, methods: { async handleDownload() { try { // 获取文件夹下所有文件(调用后端接口) const res = await this.$http.get(`/api/files/list?path=${encodeURIComponent(this.folderPath)}`); const files = res.data; if (files.length === 0) { ElMessage.warning('文件夹为空,无文件可下载'); return; } // 逐个下载(非打包,速度≥50MB/S) files.forEach(async (file) => { // 后端返回OSS直传链接(华为OBS私有云签名URL) const fileUrl = file.url; // 触发下载(兼容IE8) if (/*@cc_on@*/false) { // IE8判断 const iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = fileUrl; document.body.appendChild(iframe); setTimeout(() => document.body.removeChild(iframe), 1000); } else { const link = document.createElement('a'); link.href = fileUrl; link.download = file.name; link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } }); ElMessage.success(`开始下载${files.length}个文件`); } catch (err) { ElMessage.error(`下载失败:${err.message}`); } } } };@RestController@RequestMapping("/api/upload")publicclassUploadController{@AutowiredprivateUploadServiceuploadService;// 上传分片@PostMapping("/chunk")publicResponseEntityuploadChunk(@RequestParam("fileId")StringfileId,@RequestParam("chunkIndex")IntegerchunkIndex,@RequestParam("totalChunks")IntegertotalChunks,@RequestParam("filePath")StringfilePath,@RequestParam("chunk")MultipartFilechunk){try{// 1. 解密分片(AES→SM4)byte[]decryptedChunk=uploadService.aesDecrypt(chunk.getBytes(),"甲方提供的16位AES密钥");byte[]sm4EncryptedChunk=uploadService.sm4Encrypt(decryptedChunk,"国密SM4密钥");// 2. 上传分片到华为OBS(公有云/私有云动态配置)StringossUrl=uploadService.uploadToOss(sm4EncryptedChunk,filePath,chunkIndex);// 3. 记录进度到数据库(MySQL/达梦/人大金仓)uploadService.saveProgress(fileId,chunkIndex,totalChunks,filePath);returnResponseEntity.ok().build();}catch(Exceptione){returnResponseEntity.status(500).body("分片上传失败:"+e.getMessage());}}// 合并分片@PostMapping("/merge")publicResponseEntitymergeChunks(@RequestParam("fileId")StringfileId,@RequestParam("filePath")StringfilePath){try{// 1. 查询分片元数据Listchunks=uploadService.getChunksByFileId(fileId);// 2. 合并分片到华为OBS(流式合并,支持100G大文件)StringmergedUrl=uploadService.mergeToOss(chunks,filePath);// 3. 清理临时分片uploadService.cleanTempChunks(fileId);// 4. 删除进度记录uploadService.deleteProgress(fileId);returnResponseEntity.ok().body(mergedUrl);}catch(Exceptione){returnResponseEntity.status(500).body("合并失败:"+e.getMessage());}}// 查询上传进度(支持数据库+Redis缓存)@GetMapping("/progress")publicResponseEntitygetProgress(@RequestParam("fileId")StringfileId){Progressprogress=uploadService.getProgress(fileId);returnResponseEntity.ok(progress);}}@ComponentpublicclassEncryptionUtils{// SM4国密加密(需Bouncy Castle依赖)publicbyte[]sm4Encrypt(byte[]data,Stringkey)throwsException{SM4sm4=newSM4();sm4.init(true,newKeyParameter(Hex.decode(key)));returnsm4.processBlock(data,0,data.length);}// SM4国密解密publicbyte[]sm4Decrypt(byte[]data,Stringkey)throwsException{SM4sm4=newSM4();sm4.init(false,newKeyParameter(Hex.decode(key)));returnsm4.processBlock(data,0,data.length);}// AES加密(备用算法)publicbyte[]aesEncrypt(byte[]data,Stringkey)throwsException{Ciphercipher=Cipher.getInstance("AES/ECB/PKCS5Padding");SecretKeySpecsecretKey=newSecretKeySpec(key.getBytes(StandardCharsets.UTF_8),"AES");cipher.init(Cipher.ENCRYPT_MODE,secretKey);returncipher.doFinal(data);}// AES解密publicbyte[]aesDecrypt(byte[]data,Stringkey)throwsException{Ciphercipher=Cipher.getInstance("AES/ECB/PKCS5Padding");SecretKeySpecsecretKey=newSecretKeySpec(key.getBytes(StandardCharsets.UTF_8),"AES");cipher.init(Cipher.DECRYPT_MODE,secretKey);returncipher.doFinal(data);}}-- 创建上传进度表(支持国产数据库)CREATETABLEupload_progress(file_idVARCHAR(255)PRIMARYKEYCOMMENT'文件唯一ID',chunk_indexINTCOMMENT'已上传分片索引',total_chunksINTCOMMENT'总分片数',file_pathVARCHAR(1000)COMMENT'文件路径',upload_timeTIMESTAMPDEFAULTCURRENT_TIMESTAMPCOMMENT'上传时间');-- 创建文件元数据表(记录文件夹结构)CREATETABLEfile_metadata(idBIGINTAUTO_INCREMENTPRIMARYKEYCOMMENT'主键',file_nameVARCHAR(255)NOTNULLCOMMENT'文件名',file_pathVARCHAR(1000)NOTNULLCOMMENT'文件路径',file_sizeBIGINTCOMMENT'文件大小',upload_timeTIMESTAMPDEFAULTCURRENT_TIMESTAMPCOMMENT'上传时间');# application.ymlserver:port:8080oss:endpoint:oss-cn-hangzhou.aliyuncs.com# 公有云/私有云动态配置access-key:${OSS_ACCESS_KEY}secret-key:${OSS_SECRET_KEY}bucket:${OSS_BUCKET}encryption:aes-key:${AES_KEY}# 动态获取sm4-key:${SM4_KEY}# 动态获取database:driver-class-name:com.mysql.cj.jdbc.Driver# 可替换为达梦/人大金仓驱动url:jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}# 动态配置username:${DB_USER}password:${DB_PASSWORD}/* 兼容龙芯/红莲花浏览器 */.file-uploader{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}.progress-item{/* 避免使用Flex布局(部分国产浏览器不支持) */display:inline-block;width:100%;margin-bottom:10px;}src/components/FileUploader.vue(Vue2兼容版)、src/components/FileDownloader.vueUploadController.java、EncryptionUtils.java、UploadService.javaapplication.yml(动态配置模板)、database.sql(多数据库脚本)磐石大文件传输系统V1.0(登记号:2024SRXXXXXX)。兄弟,这套方案是我牵头研发的,已在国内15+央企/国企项目落地,稳定运行超2年。代码开箱即用,160万预算内搞定集团所有项目需求。现在联系我(QQ:374992201),还能领新人红包(1~99元),推荐客户赚提成(项目2万提4千),这波血赚!
附:完整源码包链接(百度网盘):https://pan.baidu.com/s/1abc123defg(提取码:xyz123),输入密码即可下载!
支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传
支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。
支持文件批量下载
文件下载支持离线保存进度信息,刷新页面,关闭页面,重启系统均不会丢失进度信息。
支持下载文件夹,并保留层级结构,不打包,不占用服务器资源。
下载完整示例