前端老哥的奇幻漂流:20G文件上传历险记

大家好,我是那个"预算100元想造航天飞机"的陕西前端老哥。最近接了个外包,客户要求用原生JS实现20G文件上传,还要兼容IE9…我差点没把手中的肉夹馍吓掉!

需求分析(血压升高版)

客户要的功能清单:

  • 20G文件上传(我电脑硬盘才256G啊!)
  • 文件夹结构保持(1000+文件那种)
  • 加密传输(SM4/AES)
  • 断点续传(关机重启都不能丢进度)
  • 兼容IE9(2011年的老古董了)
  • 100元预算(还含3年维护)

这哪是外包需求,这分明是想用自行车价格买劳斯莱斯啊!不过老哥我混迹江湖多年,还是硬着头皮上了…

前端解决方案(穷得叮当响版)

// 文件上传核心代码 - 精简版(完整代码请加QQ群领取)
class MegaUploader {
  constructor() {
    this.chunkSize = 5 * 1024 * 1024; // 5MB分片(IE9会哭的)
    this.maxRetry = 3; // 重试次数(心态要好)
    this.queue = []; // 上传队列(希望不要爆内存)
    this.supportFolder = !!window.FileSystem; // 检测文件夹支持(IE9:你在想peach)
  }

  // 加密函数(假装很安全)
  encrypt(data, key, algo = 'AES') {
    return `${algo}::${btoa(data)}::${key}`; // 真加密请用crypto-js
  }

  // 分片上传(祈祷不要蓝屏)
  async uploadChunk(file, start, end, chunkIndex) {
    const chunk = file.slice(start, end);
    const encrypted = this.encrypt(chunk, '客户付不起加密钱');
    
    return new Promise((resolve, reject) => {
      let retry = 0;
      const tryUpload = () => {
        const xhr = new XMLHttpRequest();
        xhr.open('POST', '/upload', true);
        xhr.setRequestHeader('Content-Type', 'application/octet-stream');
        xhr.setRequestHeader('X-Chunk-Index', chunkIndex);
        
        xhr.onload = () => resolve(xhr.response);
        xhr.onerror = () => ++retry < this.maxRetry ? tryUpload() : reject();
        
        xhr.send(encrypted);
      };
      tryUpload();
    });
  }

  // 处理文件夹上传(IE9用户请自觉退出)
  async uploadFolder(folder) {
    if (!this.supportFolder) {
      alert('您的浏览器太古老,建议升级到本世纪版本');
      return;
    }
    
    const entries = await this.readDirectory(folder);
    for (const entry of entries) {
      if (entry.isFile) {
        await this.uploadFile(await entry.getFile());
      } else if (entry.isDirectory) {
        await this.uploadFolder(entry);
      }
    }
  }

  // 断点续传(全靠localStorage死撑)
  saveProgress(fileId, progress) {
    localStorage.setItem(`upload_${fileId}`, JSON.stringify(progress));
    // 注意:IE9的localStorage只有5MB哦~
  }
}

// 使用示例(希望客户不会看控制台)
const uploader = new MegaUploader();
document.getElementById('file-input').addEventListener('change', (e) => {
  const file = e.target.files[0];
  if (file.size > 20 * 1024 * 1024 * 1024) {
    alert('老板,得加钱!20G文件太烧CPU了');
    return;
  }
  uploader.uploadFile(file).catch(() => alert('上传失败,请检查网络或加预算'));
});

IE9兼容方案(玄学版)

// IE9专属polyfill(效果看天意)
if (navigator.userAgent.indexOf('MSIE 9') !== -1) {
  console.log('检测到上古浏览器,开始施法...');
  
  // 假装支持Promise
  window.Promise = window.Promise || function(executor) {
    executor(
      value => setTimeout(() => this.onFulfilled(value), 0),
      reason => setTimeout(() => this.onRejected(reason), 0)
    );
  };
  
  // 假装支持File API
  window.File = window.File || function() {};
  window.FileReader = window.FileReader || function() {
    this.readAsArrayBuffer = function() {
      alert('IE9不支持大文件上传,建议换浏览器或加钱');
    };
  };
}

技术总结(心酸版)

  1. 大文件上传:必须分片,但IE9可能会原地爆炸
  2. 文件夹结构:现代浏览器可用webkitRelativePath,IE9…放弃吧
  3. 断点续传:localStorage存进度,但IE9只有5MB空间
  4. 加密传输:crypto-js库可以,但会增大体积
  5. 100元预算:建议买杯咖啡清醒一下

友情提示

老哥我最后想通了:这需求100元真做不了!不过欢迎加群374992201交流(真的有红包),我们可以:

  • 一起吐槽奇葩客户
  • 分享接单防坑指南
  • 组团开发分(逃)担(跑)压力

记住:程序员要团结,别让低价外包毁了行业!💪

(完整代码因"预算不足"无法在此展示,请加群领取…开玩笑的,真要实现这么复杂的功能,建议预算后面加几个零)

将组件复制到项目中

示例中已经包含此目录
image

引入组件

image

配置接口地址

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

处理事件

image

启动测试

image

启动成功

image

效果

image

数据库

image

效果预览

文件上传

文件上传

文件刷新续传

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

文件夹上传

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

批量下载

支持文件批量下载
批量下载

下载续传

文件下载支持离线保存进度信息,刷新页面,关闭页面,重启系统均不会丢失进度信息。
下载续传

文件夹下载

支持下载文件夹,并保留层级结构,不打包,不占用服务器资源。
文件夹下载

下载示例

点击下载完整示例

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐