一个前端老鸟的"求生"之路:大文件上传项目实录
各位前端江湖的兄弟姐妹们,我是老张,一个在甘肃苦哈哈写代码的"前端农民工"。最近接了个"史诗级"外包项目,客户要求之多让我这个老程序员差点把假发都薅秃了。
项目需求:让我怀疑人生的清单
- 支持20G大文件上传(这得传到猴年马月?)
- 必须用原生JS实现(WebUploader?那是什么?)
- 文件夹上传要保留层级结构(IE9:听说你想挑战我?)
- 支持SM4/AES加密(加密算法?我只会MD5啊!)
- 断点续传(用户关机重启都不丢进度?这是要上天啊!)
- 兼容IE9到最新Chrome(Windows 7用户:我们还在用IE9呢!)
- 预算100元(客户:听说程序员喝西北风就能活?)
- 免费3年维护(我:???)
前端实现:原生JS的"极限运动"
经过三天三夜的奋战(实际是熬夜看文档+喝红牛),我终于用原生JS搞定了文件夹上传的核心功能。以下是部分代码,拿去不谢!
大文件上传系统 - 老张出品
body { font-family: 'Microsoft YaHei', sans-serif; margin: 0; padding: 20px; }
.upload-area { border: 2px dashed #***c; padding: 20px; text-align: center; margin-bottom: 20px; }
.progress-container { width: 100%; background: #f0f0f0; height: 20px; margin: 10px 0; }
.progress-bar { height: 100%; background: #4CAF50; width: 0%; transition: width 0.3s; }
.file-list { margin-top: 20px; max-height: 300px; overflow-y: auto; }
.file-item { padding: 5px; border-bottom: 1px solid #eee; }
大文件上传系统(IE9兼容版)
拖拽文件或文件夹到这里,或点击选择文件
选择文件/文件夹
准备就绪
// 全局变量存储上传状态
const uploadState = {
files: [],
chunkSize: 5 * 1024 * 1024, // 5MB分片
uploadedChunks: new Map(), // 存储已上传的分片
cryptoKey: 'thisIsASecretKey123' // 简单加密密钥(实际项目请用更安全的方案)
};
// 初始化拖拽区域
const dropArea = document.getElementById('dropArea');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false);
});
function highlight() {
dropArea.style.borderColor = '#4CAF50';
}
function unhighlight() {
dropArea.style.borderColor = '#***c';
}
// 处理文件选择/拖放
dropArea.addEventListener('drop', handleDrop, false);
document.getElementById('fileInput').addEventListener('change', handleFileSelect);
function handleFileSelect(e) {
const files = Array.from(e.target.files);
processFiles(files);
}
function handleDrop(e) {
const dt = e.dataTransfer;
const files = Array.from(dt.files);
// IE9兼容处理(实际IE9可能不支持webkitdirectory)
if (typeof dt.items !== 'undefined' && dt.items) {
const items = Array.from(dt.items);
const folderItems = items.filter(item => item.webkitGetAsEntry && item.webkitGetAsEntry().isDirectory);
if (folderItems.length > 0) {
// 处理文件夹(简化版,实际需要递归读取)
alert('检测到文件夹上传,但IE9可能不支持完整功能,建议使用Chrome/Firefox');
}
}
processFiles(files);
}
// 处理文件(简化版,实际需要递归处理文件夹)
function processFiles(files) {
updateStatus(`检测到 ${files.length} 个文件`);
files.forEach(file => {
// 简单加密文件名(实际项目请用更安全的方案)
const encryptedName = encryptFileName(file.name);
uploadState.files.push({
file: file,
encryptedName: encryptedName,
path: file.webkitRelativePath || file.name, // 保留路径结构
totalChunks: Math.ceil(file.size / uploadState.chunkSize),
uploadedChunks: 0
});
addFileToList(file.name, file.size, file.webkitRelativePath || '');
});
// 模拟上传(实际项目需要调用后端API)
setTimeout(startUpload, 1000);
}
// 简单的文件名加密(SM4/AES实现请自行搜索加密库)
function encryptFileName(name) {
// 这里只是示例,实际项目请使用加密库
return btoa(name + '|' + uploadState.cryptoKey).substring(0, 12) + '.dat';
}
// 更新状态显示
function updateStatus(msg) {
document.getElementById('status').textContent = msg;
console.log(msg);
}
// 添加文件到列表显示
function addFileToList(name, size, path) {
const fileList = document.getElementById('fileList');
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div>${name} (${formatFileSize(size)})</div>
<div style="font-size: 12px; color: #666;">路径: ${path}</div>
<div class="chunk-progress" id="progress-${name.replace(/\./g, '_')}"></div>
`;
fileList.appendChild(fileItem);
}
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 开始上传(简化版)
function startUpload() {
updateStatus('开始上传...');
uploadState.files.forEach((fileInfo, index) => {
// 实际项目需要为每个文件创建上传任务
// 这里简化处理,实际需要实现分片上传、断点续传等
// 模拟上传进度
let uploaded = 0;
const interval = setInterval(() => {
uploaded += Math.floor(Math.random() * 1000000);
if (uploaded >= fileInfo.file.size) {
uploaded = fileInfo.file.size;
clearInterval(interval);
updateFileProgress(fileInfo.file.name, 100);
updateStatus(`文件 ${fileInfo.file.name} 上传完成`);
} else {
const progress = Math.min(100, Math.round((uploaded / fileInfo.file.size) * 100));
updateFileProgress(fileInfo.file.name, progress);
}
}, 200);
});
}
// 更新单个文件上传进度
function updateFileProgress(fileName, percent) {
const progressId = 'progress-' + fileName.replace(/\./g, '_');
const progressElem = document.getElementById(progressId);
if (progressElem) {
progressElem.innerHTML = `<div style="width: ${percent}%; height: 10px; background: #4CAF50;"></div>`;
}
// 更新总进度条
const totalFiles = uploadState.files.length;
const ***pletedFiles = uploadState.files.filter(f => {
// 实际项目需要根据真实上传状态判断
return true; // 这里简化处理
}).length;
const overallProgress = Math.round((***pletedFiles / totalFiles) * 100);
document.getElementById('progressBar').style.width = overallProgress + '%';
}
// 初始化(实际项目需要从本地存储加载上传状态)
document.addEventListener('DOMContentLoaded', () => {
updateStatus('系统就绪,请选择文件或文件夹');
// 模拟从本地存储加载已上传的分片信息(实际项目需要实现)
// loadUploadStateFromLocalStorage();
});
项目感悟:前端人的辛酸泪
- IE9兼容性:这简直就是前端开发者的噩梦,建议客户直接给用户发新电脑
- 大文件上传:分片上传、断点续传、加密传输,每个功能都能写一篇论文
- 文件夹结构:webkitRelativePath在IE中不支持,需要额外处理
- 加密存储:SM4/AES加密需要引入加密库,但又要考虑IE9兼容性
- 性能优化:上传20G文件,前端不崩溃已经是奇迹
解决方案:曲线救国
-
劝说客户:建议降低需求,比如:
- 不支持IE9(现在连微软都放弃了)
- 降低加密要求(AES-128比SM4更容易实现)
- 增加预算(100元连杯星巴克都买不了)
-
技术选型:
- 使用WebUploader(虽然客户要求原生JS,但可以偷偷用)
- 引入加密库(crypto-js)
- 使用IndexedDB存储上传状态(实现断点续传)
-
团队协作:
- 加入我的QQ群:374992201(加群送红包,还能接私活)
- 大家一起吐槽客户,分享技术解决方案
- 组建接单团队,共同承接大项目
最终建议
各位前端同仁,遇到这种"史诗级"需求时:
- 先评估技术可行性
- 再评估时间成本
- 最后评估自己的发际线
如果实在搞不定,就像我一样:
- 写个简化版demo
- 加入各种限制条件
- 然后…跑路!(开玩笑的)
实际上,我已经把客户拉进了我的QQ群,让他们直接和"技术大神"(其实就是群里的同行)沟通去了。这招叫做:转移矛盾,共同致富!
(PS:以上代码仅供娱乐,实际项目请勿直接使用,否则后果自负哦~)
将组件复制到项目中
示例中已经包含此目录
引入组件
配置接口地址
接口地址分别对应:文件初始化,文件数据上传,文件进度,文件上传完毕,文件删除,文件夹初始化,文件夹删除,文件列表
参考:http://www.ncmem.***/doc/view.aspx?id=e1f49f3e1d4742e19135e00bd41fa3de
处理事件
启动测试
启动成功
效果
数据库
效果预览
文件上传
文件刷新续传
支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传
文件夹上传
支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。
下载示例
点击下载完整示例