前端老炮的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();
};
开发心得(吐槽版)
-
IE9兼容性:这简直是一场噩梦!没有File API,没有Promise,没有const/let,没有箭头函数…我感觉自己在用石器时代的工具开发火箭。
-
文件夹上传:现代浏览器有webkitRelativePath,但IE9没有。解决方案?要么让用户先压缩成zip,要么…算了,还是压缩吧(客户肯定不同意)。
-
断点续传:localStorage在IE9中只有5MB限制,存储大量上传进度?呵呵,祝你好运。
-
加密传输:IE9不支持Web Crypto API,只能用crypto-js这种老库,性能感人。
-
20G文件:分片上传是必须的,但IE9的内存管理…算了,别让浏览器崩溃就是胜利。
加入我们的接单群(广告版)
各位同仁,如果你也在为这些奇葩需求头疼,或者想找项目合作,欢迎加入我们的QQ群:374992201
群内福利:
- 1~99元超级大红包(每天都有)
- 接单合作机会
- 技术交流分享
- 推荐项目拿20%提成
- 超级会员50%提成(躺着赚钱)
免责声明(求生版)
以上代码仅供娱乐参考,实际项目中请:
- 使用成熟的库(如WebUploader、Plupload等)
- 考虑使用Flash作为IE9的备选方案
- 对于20G文件,建议使用专业的大文件传输方案
- 加密传输请使用标准协议(如TLS)而非前端加密
- 断点续传最好由后端实现
最后,祝大家项目顺利,少遇奇葩需求!如果真有客户找上门,记得推荐给我啊(手动狗头)!
将组件复制到项目中
示例中已经包含此目录

引入组件

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

处理事件

启动测试

启动成功

效果

数据库

效果预览
文件上传

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

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

783

被折叠的 条评论
为什么被折叠?



