大文件切片上传--node合并大文件

大文件前端上传

- 前端进行切片

___切片是干什么的?比如说一个100MB的文件想要上传到服务器,我们可以用类似于上传图片一样的方式上传给后端,但是吧 因为这个文件内存实在是太大了,可能因为各方面原因使我们没法保证可以成功上传服务器。
___这个时候我们可以使用切片,顾名思义,就是把这个100MB的文件切成若干个小文件,比如1MB大小的文件,然后我们发送上传请求,一个接一个的去上传。最后上传成功后,我们让后端合并一下,然后返回给我们服务器上这个文件的地址就可以了。

 //上传函数
  function handleChange(e) {
    const file = e.target.files[0];
    //调用函数,进行切片
    const fileList = handleThunk(file);
    //遍历返回回来的切片数组,添加切片的文件名等信息
    const uploadList = fileList.map((item, index) => {
      //创建FormData格式的文件
      const fileData = new FormData();
      fileData.append("chunk", item.tempFile);
      fileData.append("name", uuid + "@@" + index);
      fileData.append("filename", uuid);
      return axios.post("/upload_file_thunk", fileData);
    });
    //如果所有切片请求全都成功了,我们就发送一个合并请求
    Promise.all(uploadList).then((res) => {
      //所有切片上传成功
      axios
        .post("/upload_file_end", {
          filename: uuid, //生成文件名
          extname: file.name.split(".").slice(-1)[0], //文件后缀名,用来让后端给合并后的文件
        })
        .then((res) => {
          console.log(res);
        });
    });
  }
  //切片函数
  function handleThunk(file, size = 1024 * 1024 * 0.5) {
    let current = 0; //这个文件切片之后下一个切片的大小
    const res = []; //切片结果数组
    //循环地去切片,当前所有切过的文件大小大于总大小就跳出循环,说明已经切完了
    while (current < file.size) {
      res.push({
        tempFile: file.slice(current, current + size),
      });
      current += size;
    }
    return res;
  }
- 后端接收切片

在看后端代码之前,我们先熟悉了解一下node的两个模块fs和path

  • path.join()

path.join() 方法使用特定于平台的分隔符作为定界符将所有给定的 path 片段连接在一起,然后规范化生成的路径。在这里插入图片描述

  • path.resolve()

path.resolve() 方法将路径或路径片段的序列解析为绝对路径。
在这里插入图片描述

  • path.dirname()

path.dirname() 方法返回 path 的目录名,类似于 Unix dirname 命令。 尾随的目录分隔符被忽略在这里插入图片描述

  • fs.createReadStream()

createReadStream 方法有两个参数,第一个参数是读取文件的路径,第二个参数为 options 选项,
createReadStream 的返回值为 fs.ReadStream 对象,读取文件的数据在不指定 encoding 时,默认为 Buffer。
创建可写流返回值的 rs 上的 pipe 方法是专门用来连接可读流和可写流的,可以将一个文件读来的内容通过流写到另一个文件中。pipe前是读文件,pipe后的括号中是写文件。
在创建可读流后默认是不会读取文件内容的,默认创建一个流 是非流动模式,默认不会读取数据

  • fs.createWriteStream()

createWriteStream 方法有两个参数,第一个参数是读取文件的路径,第二个参数为 options 选项。
createWriteStream 返回值为 fs.WriteStream 对象,第一次写入时会真的写入文件中,继续写入,会写入到缓存中。

  • fs.readdirSync()

读取目录的内容。

  • fs.rm()

fs.rm()方法用于删除给定路径下的文件。也可以递归地使用它来删除目录。在这里插入图片描述

fs.rm('./dummy.txt', { recursive:true }, (err) => { 
    if(err){ 
        // File deletion failed 
        console.error(err.message); 
        return; 
    } 
    console.log("File deleted successfully"); 
}) 

接收代码如下:

//接收切片
router.post("/upload_file_thunk", async (ctx, next) => {
  try {
    const files = ctx.request.body;
    const chunk = ctx.request.files.chunk;
    //创建文件读入流
    const readStream = fs.createReadStream(chunk.filepath);
    //保存文件目录---这个文件目录,需要我们先手动创建
    //path.join主要是把这些参数合并成一个新的路径
    const file = path.join(__dirname, "../public/uploads/thunk/");
    //创建文件写入流
    const writeStream = fs.createWriteStream(file + files.name);
    readStream.pipe(writeStream);
    //把传来的切片统一存储
    ctx.body = {
      title: "成功",
    };
  } catch (error) {
    console.log(error);
    ctx.body = {
      title: "失败",
      data: error,
    };
  }
});
- 后端合并切片
//合并切片
router.post("/upload_file_end", async (ctx, next) => {
  try {
    const { filename, extname } = ctx.request.body;
    //合并文件
    thunkStreamMerge(
      "../public/uploads/thunk/",
      "../public/uploads/" + filename + "." + extname
    );
    ctx.body = {
      title: "成功",
    };
  } catch (error) {
    console.log(error);
    ctx.body = {
      title: "失败",
      data: error,
    };
  }
});

合并代码

/**
 *文件合并
 * @param {*} sourceFiles 源文件目录:存放所有切片文件的目录
 * @param {*} targetFiles 目标文件:合并之后的文件名
 */
function thunkStreamMerge(sourceFiles, targetFiles) {
  const list = fs.readdirSync(path.resolve(__dirname, sourceFiles));
  const fileWriteStream = fs.createWriteStream(
    path.resolve(__dirname, targetFiles)
  );
  //进行递归调用合并文件
  thunkStreamMergeProgress(list, fileWriteStream, sourceFiles);
}
/**
 * 合并每一个切片
 * @param {*} fileList 文件数据
 * @param {*} fileWriteStream 最终的写入结果
 * @param {*} sourceFiles 文件路径
 */
function thunkStreamMergeProgress(fileList, fileWriteStream, sourceFiles) {
  console.log(fileList.length);
  if (!fileList.length) {
    return fileWriteStream.end("console.log('完成了')");
  }
  const currentFile = path.resolve(__dirname, sourceFiles, fileList.shift());
  const currentReadSteam = fs.createReadStream(currentFile);
  //写入文件内容,括号内的会覆盖readStream的内容
  currentReadSteam.pipe(fileWriteStream, { end: false });
  //合并后,删除切片
  fs.rm(currentFile, { recursive: true }, (err) => {
    if (err) {
      console.error(err.message);
      return;
    }
  });
  currentReadSteam.on("end", () => {
    thunkStreamMergeProgress(fileList, fileWriteStream, sourceFiles);
  });
}

参考

  • https://vimsky.com/examples/usage/node-js-fs-rm-method.html
  • https://nodejs.cn/api/fs.html
  • https://zhuanlan.zhihu.com/p/131627741
  • https://juejin.cn/post/6844903681255538695#heading-12
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值