大文件上传

简介: 大文件上传

大文件上传的技术实现


前言


文件上传是网页操作中很常见的功能, 只要给 html 的 input 标签设置一个file 类型,配合form 表单,我们可以很轻易的实现一个文件上传功能,这种实现方案满足大多数情景下的需求,毕竟最常见的需求只是要你上传一个照片、简历、少量的excel 数据;但是遇到大文件比如几百M或者几个G,这种简单的实现方案就暴露出来它的致命缺点:

  1. 单个 http 连接在传输中极易因网络原因等中断,导致需要重新建立 http 服务;
  2. 无法对上传过程进行控制,如暂停、断点续传、上传进度展示等;

于是大文件上传解决方案应运而生,工具类软件中经常有相关的场景,这里记录我自己实践的一套基础示例


实现方案:


前端(vanilla.js) + 后端(node)+ 数据库 (mysql)


前端界面效果:


1687777764076.png


环境准备


这次以 Node 为主,通过 koa、node 搭建一套基础开发环境,提供 rest api 给前端调用,前端就一个页面就不单独搭建服务了,将其集成到一起,代码结构为


1687777776113.png


1687777784512.png


前端处理


切片


文件处理第一步是把文件切割成一个个的小文件流,可以按照固定大小来分割或者百分比的方式都可以,然后将切片放到一个数组对象里

注意点:

  1. 切片单位 100kb || or
  2. 切片身份标示ID
  3. 切片索引值 Index
  4. 根据长度截取每次需要上传的数据
  5. File对象继承自Blob对象,因此包含slice方法


// 代码片段
const slice = (file, piece) => {
  let totalSize = file.size; // 文件总大小
  let start = 0; // 每次上传的开始字节
  let end = start + piece; // 每次上传的结尾字节
  let chunks = [];
  while (start < totalSize) {
    let blob = file.slice(start, end);
    chunks.push(blob);
    start = end;
    end = start + piece;
  }
  return chunks;
}
let file = document.querySelector("[name=file]").files[0];
const LENGTH = 1024 * 1024 * 0.1;
let chunks = slice(file, LENGTH);


分片发送 && 通知 ending


这里没有做节流处理,因为是示例,如果业务场景下就需要控制异步发送的频率了,这里我定义了一个任务数组,把分片后的每个数据流包装成一个http promise,这样通过Promise.all 来异步批量请求,将分片数据流传输给node 服务,在数据传输完成后 调用 mkFile 接口告知服务器进行文件合成操作。


// 代码片段
let tasks = [];
chunks.forEach((chunk, index) => {
  let formData = new FormData();
  formData.append("file", chunk);
  formData.append("context", file.name);
  formData.append("chunk", index + 1); 
  tasks.push(post("/api/postLargeFile", formData));
});
Promise.all(tasks).then((res) => {
  let formData = new FormData();
  formData.append("context", context);
  formData.append("chunks", chunks.length);
  post("/api/mkFile", formData);
});


Node 端接口处理


服务端主要是提供两个接口,一个切片文件接收,一个文件合成通知接收;对切片的处理中,要先临时保存到本地文件夹中,在之后文件合成后销毁;


切片文件接收


切片注意点:

  1. 判断是否是初始请求,需要的话要生成此文件对应的文件夹
  2. 文件流的基本操作
  3. 获取上传文件
  4. 读取文件流
  5. 设置文件保存路径
  6. 组装成绝对路径


// 代码片段
"POST /api/postLargeFile": async (ctx, next) => {
  if (!fs.existsSync(`img/${ctx.request.body.context}-slice`)) {
    fs.mkdir(`img/${ctx.request.body.context}-slice`, 0777, (res) => {
      if (res) {
        const file = ctx.request.files.file;
        const fileReader = fs.createReadStream(file.path);
        const filePath = path.join(
          __dirname,
          `../img/${ctx.request.body.context}-slice`
        );
        const fileResource = filePath + `/${ctx.request.body.chunk}`;
        const writeStream = fs.createWriteStream(fileResource);
        fileReader.pipe(writeStream);
      } else {
        console.log("写入 fail");
      }
      return;
    });
  }
  const file = ctx.request.files.file;
  const fileReader = fs.createReadStream(file.path);
  const filePath = path.join(
    __dirname,
    `../img/${ctx.request.body.context}-slice`
  );
  const fileResource = filePath + `/${ctx.request.body.chunk}`;
  const writeStream = fs.createWriteStream(fileResource);
  fileReader.pipe(writeStream);
  ctx.rest({ code: 200 });
}


文件合成


文件合成就是把之前保存的文件流片段重新按照次序生成一个新的完整文件的过程。

合成注意点:

  1. 文件夹中文件目录读取,排序
  2. 按照次序将文件流入新管道,生成合成文件
  3. 合成后销毁临时文件


// 代码片段
"POST /api/mkFile": async (ctx) => {
  const writeStream = fs.createWriteStream(`img/${ctx.request.body.context}`);
  const filePath = path.join(
    __dirname,
    `../img/${ctx.request.body.context}-slice`
  );
  let currentFile;
  fs.readdir(filePath, (err, files) => {
    if (err) return;
    const newFiles = files.sort((a, b) => a - b);
    let main = () => {
      if (!newFiles.length) {
        writeStream.end("Done");
        return;
      }
      currentFile = `${filePath}/${newFiles.shift()}`;
      readStream = fs.createReadStream(currentFile);
      readStream.pipe(writeStream, { end: false });
      readStream.on("end", function () {
        main();
      });
    };
    main();
  });
  ctx.rest({
    code: 200,
    msg: "文件已合成",
  });
}


保存到数据库


这是最后一步,生成的完整文件需要通过 mysql 等数据库保存下来,然后再提供一个查询的 Api,这样一个基础的文件上传流程就走完了,关于数据库的基本操作,这里就不赘述了,有兴趣可以到本文示例源码查看。

目录
相关文章
|
存储 缓存 前端开发
视频播放大文件上传
视频播放大文件上传
153 0
|
3月前
|
Web App开发 存储 移动开发
大文件上传实现方式比较
大文件上传实现方式比较
61 5
|
3月前
|
Java C#
断点续传(上传)C#版
断点续传(上传)C#版
36 0
视频文件上传接口上传不了,怎样解决??
视频文件上传接口上传不了,怎样解决??
|
6月前
|
前端开发 NoSQL Redis
如何实现大文件上传:秒传、断点续传、分片上传
如何实现大文件上传:秒传、断点续传、分片上传
496 0
|
6月前
大文件上传如何断点续传
该文档描述了一个大文件上传流程,包括:1) 文件分片,2) 计算文件及分片的Hash值以生成唯一标识符,3) 上传分片并检查已上传状态以避免重复,4) 在上传中断时能恢复,5) 服务端合并分片成原始文件,6) 错误处理(如网络中断、服务器故障、上传失败等)并通知用户,最后7) 返回上传成功信息。
|
6月前
|
存储 前端开发 安全
如何处理大文件上传
【4月更文挑战第20天】
626 9
|
监控 UED
大文件上传如何做断点续传
大文件上传如何做断点续传
280 0
|
存储 前端开发 NoSQL
注册java实现文件分片上传并且断点续传
一、简单的分片上传 针对第一个问题,如果文件过大,上传到一半断开了,若重新开始上传的话,会很消耗时间,并且你也并不知道距离上次断开时,已经上传到哪一部分了。因此我们应该先对大文件进行分片处理,防止上面提到的问题。
|
存储 分布式计算 网络协议
文件上传下载系列——大文件分片上传
文件上传下载系列——大文件分片上传