js css html

Node实现断点续传

2023-03-01  本文已影响0人  赵客缦胡缨v吴钩霜雪明

断点续传,顾名思义就是文件上传/下载过程中,遇到不可抗力,比如网络中断,服务器异常,或者其他原因导致操作中断;再次操作时,可以从已经上传/下载的部分开始继续上传/下载未完成的部分,而没有必要从头开始上传/下载。

这样就避免了文件重复上传/下载,浪费服务器空间使用,节约服务器资源,而且速度更快,更高效。

断点续传-分片上传

断点续传上传将要上传的文件分成若干个分片(Part)分别上传,所有分片都上传完成后,将所有分片合并成完整的文件,完成整个文件的上传。

利用MD5 , MD5 是文件的唯一标识,可以利用文件的 MD5 查询文件的上传状态;

设计思路:

将文件切成多个小的文件;

将切片并行上传;

所有切片上传完成后,服务器端进行切片合成;

当分片上传失败,可以在重新上传时进行判断,只上传上次失败的部分实现断点续传;

当切片合成为完整的文件,通知客户端上传成功;

已经传到服务器的完整文件,则不需要重新上传到服务器,实现秒传功能;

/**
 * 切片上传
 *
 */
const PATH = require("path");
const FS = require("fs");
const mkDir = require("./ApiMkDir"); // 创建目录的封装方法

module.exports = function (req, res, config) {
  return new Promise((resolve, reject) => {
    // 获取时间
    // 生成储存目录名称
    let date = new Date();
    let path = `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}`;
    // 文件目录
    let savePath = PATH.resolve(config.root, "assets/" + path);
    let cachePath = PATH.resolve(config.root, "cacheUploads");
    // 判断目录是否存在,不存在创建目录
    if (!FS.existsSync(savePath)) mkDir(savePath);
    if (!FS.existsSync(cachePath)) mkDir(cachePath);
    // 获取分片数据
    let { index, total, md5 } = req.fields;
    // 临时文件路径
    let TmpFileName = cachePath + "/" + req.files["file"].name;
    // 存储文件路径
    let FileName = savePath + "/" + req.files["file"].name;
    // 当前文件传输进度管理
    let TmpFileNameMange = cachePath + "/" + req.files["file"].name + ".txt";
    TmpFileName = PATH.normalize(TmpFileName);
    TmpFileNameMange = PATH.normalize(TmpFileNameMange);
    FileName = PATH.normalize(FileName);
    // 是否第一次传输
    if (FS.existsSync(FileName)) FS.unlinkSync(FileName);
    if (FS.existsSync(TmpFileNameMange)) {
      let test = FS.readFileSync(TmpFileNameMange, { encoding: "utf-8" });
      if (test) {
        let { i, md5: md } = JSON.parse(test);
        if (md === md5 && Number(i) > Number(index)) {
          resolve(i);
          return;
        }
      }
    }
    // 获取上传的文件buffer
    let buffer = FS.readFileSync(req.files["file"].path);
    // 写入临时文件
    if (FS.appendFileSync(TmpFileName, buffer)) reject();
    // 传输完成,移动到保存目录
    // 写入保存文件
    FS.writeFileSync(
      TmpFileNameMange,
      JSON.stringify({ i: index, total, md5 })
    );
    if (index === total) {
      FS.renameSync(TmpFileName, FileName);
      FS.unlinkSync(TmpFileNameMange);
      resolve();
      return;
    }
    resolve(index);
  });
};
 async submit() {
      let file = this.$refs.file.files[0];
      this.upload(file);
    },
    async upload(file, index = 0) {
      // 获取文件大小
      let fileSize = file.size;
      // 每个块的大小
      let chunkSize = 1024 * 1024 * 0.0005;
      // 共多少块
      let chunkNum = Math.ceil(fileSize / chunkSize);
      // 定义formData对象
      let formData = new FormData();
      // 定义结束位置;
      let end = index + 1;
      // 片段是否最后一片,如果不是最后一片,那么就是每片的位置
      if (end < chunkNum) end = end * chunkSize;
      // 如果是最后一片,结束位置等于文件最后的位置
      else end = fileSize;
      // 获取单个切片
      let chunData = file.slice(index * chunkSize, end);
      // 储存单个切片
      formData.append("file", chunData, file.name);
      formData.append("index", index + 1); //第几片
      formData.append("total", chunkNum); //第几片
      formData.append('md5',md5(file))
      let { data } = await axios({
        url: "http://127.0.0.1:3000/api/chunkUpload",
        method: "post",
        data: formData,
        headers: { token: "token" }
      });
      // 后台需要返回当前切片位置
      index = data.data - 0; 
      if (end < fileSize) this.upload(file, index);
    }

断点续传-下载

要实现断点续传,需要进行以下步骤:获取文件的大小和已经下载的部分。

在下载文件之前,需要确定要下载的文件的大小以及已经下载的部分。

可以使用 Node.js 中的 fs.statSync() 方法获取文件的大小,或者通过发起 HEAD 请求来获取文件的大小。

对于已经下载的部分,需要记录在先前下载时下载的字节数。

使用 HTTP Range 头。 HTTP Range 头允许客户端请求服务器仅发送文件的某一部分。

使用 Range 头可以让我们下载文件的指定部分,从而实现断点续传。

我们可以使用 HTTP 模块中的 request() 方法来发送 HTTP 请求,并在请求头中包含 Range 头。

将下载的部分写入文件,可以使用 Node.js 中的 fs.createWriteStream() 方法创建一个写入文件的流,并在下载过程中将数据写入文件。

设计思路:

第一步:检查文件是否存在,如果存在,就设置 Range 头,这样服务器就只会返回文件的未下载部分。

第二步:创建一个写入文件的流,并发送 HTTP 请求。

第三步:如果服务器返回状态码 206(Partial Content),则表示可以进行断点续传;否则,就从头开始下载整个文件。

第四步:最后,将响应流写入文件。

代码示例:

const fs = require('fs');
const http = require('http');

const fileUrl = 'http://example.com/file.zip';
const filePath = './file.zip';

// 获取文件大小和已下载的部分
let options = {};
if (fs.existsSync(filePath)) {
  const stat = fs.statSync(filePath);
  const range = `bytes=${stat.size}-`;
  options.headers = { 'Range': range };
}

// 发送请求并写入文件
const file = fs.createWriteStream(filePath, { flags: 'a' });
const request = http.get(fileUrl, options, (response) => {
  // 如果服务器返回 206(Partial Content),则表示可以断点续传
  if (response.statusCode === 206) {
    console.log('继续下载');
  } else if (response.statusCode === 200) {
    console.log('从头开始下载');
  } else {
    console.log(`无法下载,错误代码:${response.statusCode}`);
    return;
  }
  response.pipe(file);
});

// 处理错误
request.on('error', (err) => {
  console.error(`下载出错:${err.message}`);
});

// 下载完成
file.on('finish', () => {
  console.log('下载完成');
});

// 关闭文件流
file.on('close', () => {
  console.log('文件流已关闭');
});

上一篇下一篇

猜你喜欢

热点阅读