Vue.js项目里如何实现大文件的秒传和断点续传?

前端老炮的20G文件夹上传大冒险(附部分代码)

各位前端同仁们,我是老张,一个在辽宁苦哈哈写代码的"前端民工"。最近接了个活,客户要求用原生JS实现20G文件夹上传下载,还要支持IE9!这简直是要了我这把老骨头的命啊!不过为了那100块预算(划掉),为了技术理想,我决定拼了!

项目背景(哭诉版)

客户要求:

  • 20G大文件上传(这是要上传整个硬盘吗?)
  • 文件夹上传保留层级结构(1000个分类的文件,这是要搞文件管理系统吗?)
  • 支持SM4/AES加密(这是要搞国家机密吗?)
  • 断点续传(用户重启电脑都不能丢进度,这是要我做操作系统吗?)
  • 兼容IE9(现在还有人用IE9?哦,是Windows 7用户,好吧,我理解)
  • 预算100元以内(这连买杯咖啡都不够啊!)
  • 7*24小时技术支持(这是要把我当24小时客服吗?)
  • 3年免费维护(这是要签终身合同吗?)

技术选型(妥协版)

经过深思熟虑,我决定:

  • 前端:Vue3 + 原生JS(因为客户说不能用WebUploader)
  • 后端:SpringBoot(但我不写后端代码,哈哈)
  • 加密:crypto-js(因为IE9不支持Web Crypto API)
  • 文件分片:自己实现(因为找不到现成的IE9兼容方案)

部分前端代码(救命版)

以下是文件夹上传的核心代码,其他功能你们自己实现吧(逃):




    
    
    20G文件夹上传神器(IE9兼容版)
    
    
        body { font-family: 'Microsoft YaHei', sans-serif; margin: 20px; }
        .progress-container { width: 100%; background: #f0f0f0; margin: 10px 0; }
        .progress-bar { height: 20px; background: #4CAF50; width: 0%; text-align: center; line-height: 20px; color: white; }
        .file-list { margin-top: 20px; border: 1px solid #ddd; padding: 10px; max-height: 300px; overflow-y: auto; }
    


    20G文件夹上传神器(IE9兼容版)
    
    
        
        选择文件夹
        开始上传
        暂停上传
    
    
    
        0%
    
    
    
        文件列表:
        
    

    
        // 全局变量(IE9兼容的"类"实现)
        var FileUploader = {
            files: [],
            chunkSize: 5 * 1024 * 1024, // 5MB分片
            uploadId: null,
            paused: false,
            
            // 初始化(IE9兼容版)
            init: function() {
                document.getElementById('fileInput').addEventListener('change', this.handleFileSelect.bind(this));
                
                // 模拟断点续传的本地存储(IE9用userData或cookie,这里简化用localStorage)
                if (!localStorage.getItem('uploadProgress')) {
                    localStorage.setItem('uploadProgress', JSON.stringify({}));
                }
            },
            
            // 处理文件夹选择(IE9兼容版)
            handleFileSelect: function(e) {
                this.files = [];
                var fileList = e.target.files;
                
                // 递归构建文件树(IE9没有File.webkitRelativePath,需要特殊处理)
                for (var i = 0; i < fileList.length; i++) {
                    var file = fileList[i];
                    // 这里简化处理,实际需要解析webkitRelativePath或手动构建路径
                    // 兼容IE9的替代方案:让用户先压缩成zip...(开玩笑的)
                    this.files.push({
                        file: file,
                        path: file.name, // 实际应该用webkitRelativePath
                        uploaded: 0,
                        total: file.size,
                        chunks: Math.ceil(file.size / this.chunkSize),
                        uploadedChunks: 0
                    });
                }
                
                this.renderFileList();
            },
            
            // 渲染文件列表(IE9兼容版)
            renderFileList: function() {
                var list = document.getElementById('fileList');
                list.innerHTML = '';
                
                for (var i = 0; i < this.files.length; i++) {
                    var file = this.files[i];
                    var item = document.createElement('li');
                    item.innerHTML = file.path + ' (' + this.formatFileSize(file.uploaded) + '/' + this.formatFileSize(file.total) + ')';
                    list.appendChild(item);
                }
            },
            
            // 格式化文件大小(IE9兼容版)
            formatFileSize: function(bytes) {
                if (bytes === 0) return '0 Bytes';
                var k = 1024;
                var sizes = ['Bytes', 'KB', 'MB', 'GB'];
                var i = Math.floor(Math.log(bytes) / Math.log(k));
                return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
            },
            
            // 开始上传(IE9兼容版)
            startUpload: function() {
                if (this.files.length === 0) {
                    alert('请先选择文件夹');
                    return;
                }
                
                this.paused = false;
                this.uploadId = new Date().getTime(); // 简单生成上传ID
                
                // 加载之前的上传进度
                var progressData = JSON.parse(localStorage.getItem('uploadProgress'));
                
                for (var i = 0; i < this.files.length; i++) {
                    var file = this.files[i];
                    var fileId = file.path; // 实际应该用更唯一的ID
                    
                    // 如果有之前的上传记录,从断点继续
                    if (progressData[fileId] && progressData[fileId].uploadId === this.uploadId) {
                        file.uploaded = progressData[fileId].uploaded;
                        file.uploadedChunks = progressData[fileId].uploadedChunks;
                    } else {
                        progressData[fileId] = {
                            uploadId: this.uploadId,
                            uploaded: 0,
                            uploadedChunks: 0
                        };
                    }
                }
                
                localStorage.setItem('uploadProgress', JSON.stringify(progressData));
                this.uploadNextChunk();
            },
            
            // 上传下一个分片(IE9兼容版)
            uploadNextChunk: function() {
                if (this.paused) return;
                
                var fileToUpload = null;
                var fileIndex = -1;
                
                // 查找下一个需要上传的分片
                for (var i = 0; i < this.files.length; i++) {
                    var file = this.files[i];
                    if (file.uploadedChunks < file.chunks) {
                        fileToUpload = file;
                        fileIndex = i;
                        break;
                    }
                }
                
                if (!fileToUpload) {
                    alert('上传完成!');
                    return;
                }
                
                var chunkIndex = fileToUpload.uploadedChunks;
                var start = chunkIndex * this.chunkSize;
                var end = Math.min(start + this.chunkSize, fileToUpload.total);
                var chunk = fileToUpload.file.slice(start, end);
                
                // 加密分片(IE9兼容版)
                var encryptedChunk = this.encryptData(chunk, 'secret-key-123'); // 实际应该用用户提供的密钥
                
                // 模拟上传(实际应该用XMLHttpRequest或ActiveXObject for IE9)
                console.log('上传分片:', fileToUpload.path, '分片', chunkIndex, '/', fileToUpload.chunks);
                
                // 更新进度(IE9兼容版)
                fileToUpload.uploadedChunks++;
                fileToUpload.uploaded = end;
                
                // 保存上传进度
                var progressData = JSON.parse(localStorage.getItem('uploadProgress'));
                var fileId = fileToUpload.path;
                progressData[fileId] = {
                    uploadId: this.uploadId,
                    uploaded: fileToUpload.uploaded,
                    uploadedChunks: fileToUpload.uploadedChunks
                };
                localStorage.setItem('uploadProgress', JSON.stringify(progressData));
                
                // 更新UI
                this.renderFileList();
                this.updateProgressBar();
                
                // 继续上传下一个分片(使用setTimeout模拟异步)
                var self = this;
                setTimeout(function() {
                    self.uploadNextChunk();
                }, 100); // 模拟网络延迟
            },
            
            // 加密数据(IE9兼容版)
            encryptData: function(data, key) {
                // 实际应该用SM4或AES,这里简化用CryptoJS的AES
                // IE9需要额外处理ArrayBuffer到WordArray的转换
                if (data instanceof Blob) {
                    return new Promise(function(resolve) {
                        var reader = new FileReader();
                        reader.onload = function(e) {
                            var words = CryptoJS.enc.Latin1.parse(e.target.result);
                            var encrypted = CryptoJS.AES.encrypt(words, key).toString();
                            resolve(encrypted);
                        };
                        reader.readAsBinaryString(data);
                    });
                }
                // 简化处理,实际需要更复杂的实现
                return CryptoJS.AES.encrypt(data, key).toString();
            },
            
            // 暂停上传(IE9兼容版)
            pauseUpload: function() {
                this.paused = true;
                alert('上传已暂停');
            },
            
            // 更新进度条(IE9兼容版)
            updateProgressBar: function() {
                var totalUploaded = this.files.reduce(function(sum, file) {
                    return sum + file.uploaded;
                }, 0);
                
                var totalSize = this.files.reduce(function(sum, file) {
                    return sum + file.total;
                }, 0);
                
                var percent = Math.round((totalUploaded / totalSize) * 100);
                document.getElementById('progressBar').style.width = percent + '%';
                document.getElementById('progressBar').innerHTML = percent + '%';
            }
        };
        
        // 初始化(页面加载时执行)
        window.onload = function() {
            FileUploader.init();
            
            // 兼容IE9的console.log(IE9没有console对象时)
            if (!window.console) {
                window.console = {
                    log: function() {}
                };
            }
        };
        
        // 暴露全局方法(IE9兼容)
        window.startUpload = function() {
            FileUploader.startUpload();
        };
        
        window.pauseUpload = function() {
            FileUploader.pauseUpload();
        };
    


开发心得(吐槽版)

  1. IE9兼容性:这简直是一场噩梦!没有File API,没有Promise,没有const/let,没有箭头函数…我感觉自己在用石器时代的工具开发火箭。

  2. 文件夹上传:现代浏览器有webkitRelativePath,但IE9没有。解决方案?要么让用户先压缩成zip,要么…算了,还是压缩吧(客户肯定不同意)。

  3. 断点续传:localStorage在IE9中只有5MB限制,存储大量上传进度?呵呵,祝你好运。

  4. 加密传输:IE9不支持Web Crypto API,只能用crypto-js这种老库,性能感人。

  5. 20G文件:分片上传是必须的,但IE9的内存管理…算了,别让浏览器崩溃就是胜利。

加入我们的接单群(广告版)

各位同仁,如果你也在为这些奇葩需求头疼,或者想找项目合作,欢迎加入我们的QQ群:374992201

群内福利:

  • 1~99元超级大红包(每天都有)
  • 接单合作机会
  • 技术交流分享
  • 推荐项目拿20%提成
  • 超级会员50%提成(躺着赚钱)

免责声明(求生版)

以上代码仅供娱乐参考,实际项目中请:

  1. 使用成熟的库(如WebUploader、Plupload等)
  2. 考虑使用Flash作为IE9的备选方案
  3. 对于20G文件,建议使用专业的大文件传输方案
  4. 加密传输请使用标准协议(如TLS)而非前端加密
  5. 断点续传最好由后端实现

最后,祝大家项目顺利,少遇奇葩需求!如果真有客户找上门,记得推荐给我啊(手动狗头)!

将组件复制到项目中

示例中已经包含此目录
image

引入组件

image

配置接口地址

接口地址分别对应:文件初始化,文件数据上传,文件进度,文件上传完毕,文件删除,文件夹初始化,文件夹删除,文件列表
参考:http://www.ncmem.com/doc/view.aspx?id=e1f49f3e1d4742e19135e00bd41fa3de
image

处理事件

image

启动测试

image

启动成功

image

效果

image

数据库

image

效果预览

文件上传

文件上传

文件刷新续传

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

文件夹上传

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

下载示例

点击下载完整示例

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值