目录
方案分析
具体解决流程
逻辑分析
小结
首页 web前端 js教程 Node.js如何实现断点续传

Node.js如何实现断点续传

May 26, 2021 am 10:16 AM
node 断点续传

本篇文章给大家介绍一下实现Node.js断点续传的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

Node.js如何实现断点续传

平常业务需求:上传图片Excel等,毕竟几M的大小可以很快就上传到服务器。
针对于上传视频等大文件几百M或者几G的大小,就需要等待比较长的时间。
这就产生了对应的解决方法,对于大文件上传时的暂停断网网络较差的情况下,  使用切片+断点续传就能够很好的应对上述的情况

方案分析

  • 切片

    • 就是对上传视频进行切分,具体操作为:
    • File.slice(start,end):返回新的blob对象
      • 拷贝blob的起始字节
      • 拷贝blob的结束字节
  • 断点续传

    • 每次切片上传之前,请求服务器接口,读取相同文件的已上传切片数
    • 上传的是新文件,服务端则返回0,否则返回已上传切片数

【推荐学习:《nodejs 教程》】

具体解决流程

该demo提供关键点思路及方法,其他功能如:文件限制lastModifiedDate校验文件重复性缓存文件定期清除等功能扩展都可以在此代码基础上添加。

  • html
<input class="video" type="file" />
<button type="submit" onclick="handleVideo(event, &#39;.video&#39;, &#39;video&#39;)">
    提交
</button>
登录后复制
  • script
let count = 0; // 记录需要上传的文件下标
const handleVideo = async (event, name, url) => {
// 阻止浏览器默认表单事件
event.preventDefault();
let currentSize = document.querySelector("h2");
let files = document.querySelector(name).files;
// 默认切片数量
const sectionLength = 100;
// 首先请求接口,获取服务器是否存在此文件
// count为0则是第一次上传,count不为0则服务器存在此文件,返回已上传的切片数
count = await handleCancel(files[0]);

// 申明存放切片的数组对象
let fileCurrent = [];
// 循环file文件对象
for (const file of [...files]) {
  // 得出每个切片的大小
  let itemSize = Math.ceil(file.size / sectionLength);
  // 循环文件size,文件blob存入数组
  let current = 0;
  for (current; current < file.size; current += itemSize) {
    fileCurrent.push({ file: file.slice(current, current + itemSize) });
  }
  // axios模拟手动取消请求
  const CancelToken = axios.CancelToken;
  const source = CancelToken.source();
  // 当断点续传时,处理切片数量,已上传切片则不需要再次请求上传
  fileCurrent =
    count === 0 ? fileCurrent : fileCurrent.slice(count, sectionLength);
  // 循环切片请求接口
  for (const [index, item] of fileCurrent.entries()) {
    // 模拟请求暂停 || 网络断开
    if (index > 90) {
      source.cancel("取消请求");
    }
    // 存入文件相关信息
    // file为切片blob对象
    // filename为文件名
    // index为当前切片数
    // total为总切片数
    let formData = new FormData();
    formData.append("file", item.file);
    formData.append("filename", file.name);
    formData.append("total", sectionLength);
    formData.append("index", index + count + 1);

    await axios({
      url: `http://localhost:8080/${url}`,
      method: "POST",
      data: formData,
      cancelToken: source.token,
    })
      .then((response) => {
        // 返回数据显示进度
        currentSize.innerHTML = `进度${response.data.size}%`;
      })
      .catch((err) => {
        console.log(err);
      });
  }
}
};

// 请求接口,查询上传文件是否存在
// count为0表示不存在,count不为0则已上传对应切片数
const handleCancel = (file) => {
return axios({
  method: "post",
  url: "http://localhost:8080/getSize",
  headers: { "Content-Type": "application/json; charset = utf-8" },
  data: {
    fileName: file.name,
  },
})
  .then((res) => {
    return res.data.count;
  })
  .catch((err) => {
    console.log(err);
  });
};
登录后复制
  • node服务端
// 使用express构建服务器api
const express = require("express");
// 引入上传文件逻辑代码
const upload = require("./upload_file");
// 处理所有响应,设置跨域
app.all("*", (req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
  res.header("Access-Control-Allow-Headers", "Content-Type, X-Requested-With ");
  res.header("X-Powered-By", " 3.2.1");
  res.header("Content-Type", "application/json;charset=utf-8");
  next();
});
const app = express();

app.use(bodyParser.json({ type: "application/*+json" }));
// 视频上传(查询当前切片数)
app.post("/getSize", upload.getSize);
// 视频上传接口
app.post("/video", upload.video);

// 开启本地端口侦听
app.listen(8080);
登录后复制
  • upload_file

// 文件上传模块
const formidable = require("formidable");
// 文件系统模块
const fs = require("fs");
// 系统路径模块
const path = require("path");

// 操作写入文件流
const handleStream = (item, writeStream) => {
  // 读取对应目录文件buffer
  const readFile = fs.readFileSync(item);
  // 将读取的buffer || chunk写入到stream中
  writeStream.write(readFile);
  // 写入完后,清除暂存的切片文件
  fs.unlink(item, () => {});
};

// 视频上传(切片)
module.exports.video = (req, res) => {
  // 创建解析对象
  const form = new formidable.IncomingForm();
  // 设置视频文件上传路径
  let dirPath = path.join(__dirname, "video");
  form.uploadDir = dirPath;
  // 是否保留上传文件名后缀
  form.keepExtensions = true;
  // err 错误对象 如果解析失败包含错误信息
  // fields 包含除了二进制以外的formData的key-value对象
  // file 对象类型 上传文件的信息
  form.parse(req, async (err, fields, file) => {
    // 获取上传文件blob对象
    let files = file.file;
    // 获取当前切片index
    let index = fields.index;
    // 获取总切片数
    let total = fields.total;
    // 获取文件名
    let filename = fields.filename;
    // 重写上传文件名,设置暂存目录
    let url =
      dirPath +
      "/" +
      filename.split(".")[0] +
      `_${index}.` +
      filename.split(".")[1];
    try {
      // 同步修改上传文件名
      fs.renameSync(files.path, url);
      console.log(url);
      // 异步处理
      setTimeout(() => {
        // 判断是否是最后一个切片上传完成,拼接写入全部视频
        if (index === total) {
          // 同步创建新目录,用以存放完整视频
          let newDir = __dirname + `/uploadFiles/${Date.now()}`;
          // 创建目录
          fs.mkdirSync(newDir);
          // 创建可写流,用以写入文件
          let writeStream = fs.createWriteStream(newDir + `/${filename}`);
          let fsList = [];
          // 取出所有切片文件,放入数组
          for (let i = 0; i < total; i++) {
            const fsUrl =
              dirPath +
              "/" +
              filename.split(".")[0] +
              `_${i + 1}.` +
              filename.split(".")[1];
            fsList.push(fsUrl);
          }
          // 循环切片文件数组,进行stream流的写入
          for (let item of fsList) {
            handleStream(item, writeStream);
          }
          // 全部写入,关闭stream写入流
          writeStream.end();
        }
      }, 100);
    } catch (e) {
      console.log(e);
    }
    res.send({
      code: 0,
      msg: "上传成功",
      size: index,
    });
  });
};

// 获取文件切片数
module.exports.getSize = (req, res) => {
  let count = 0;
  req.setEncoding("utf8");
  req.on("data", function (data) {
    let name = JSON.parse(data);
    let dirPath = path.join(__dirname, "video");
    // 计算已上传的切片文件个数
    let files = fs.readdirSync(dirPath);
    files.forEach((item, index) => {
      let url =
        name.fileName.split(".")[0] +
        `_${index + 1}.` +
        name.fileName.split(".")[1];
      if (files.includes(url)) {
        ++count;
      }
    });
    res.send({
      code: 0,
      msg: "请继续上传",
      count,
    });
  });
};
登录后复制

逻辑分析

  • 前端
    • 首先请求上传查询文件是否第一次上传,或已存在对应的切片
      • 文件第一次上传,则切片从0开始
      • 文件已存在对应的切片,则从切片数开始请求上传
    • 循环切片数组,对每块切片文件进行上传
      • 其中使用了模拟手动暂停请求,当切片数大于90取消请求
  • 服务端
    • 接收查询文件filename,查找临时存储的文件地址,判断是否存在对应上传文件
      • 从未上传过此文件,则返回0,切片数从0开始
      • 已上传过文件,则返回对应切片数
    • 接收上传文件切片,文件存入临时存储目录
      • 通过counttotal判断切片是否上传完毕
      • 上传完毕,创建文件保存目录,并创建可写流,进行写入操作
      • 提取对应临时文件放入数组,循环文件目录数组,依次读取并写入文件buffer
      • 写入完毕,关闭可写流。

小结

以上代码涉及到具体的业务流程会有所更改或偏差,这只是其中一种具体实现的方式。
希望这篇文章能对大家有所帮助,如果有写的不对的地方也希望指点一二。

  • 以上代码地址:https://github.com/Surprise-ling/uploadFile

更多编程相关知识,请访问:编程视频!!

以上是Node.js如何实现断点续传的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
4 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

nvm 怎么删除node nvm 怎么删除node Dec 29, 2022 am 10:07 AM

nvm删除node的方法:1、下载“nvm-setup.zip”并将其安装在C盘;2、配置环境变量,并通过“nvm -v”命令查看版本号;3、使用“nvm install”命令安装node;4、通过“nvm uninstall”命令删除已安装的node即可。

node项目中如何使用express来处理文件的上传 node项目中如何使用express来处理文件的上传 Mar 28, 2023 pm 07:28 PM

怎么处理文件上传?下面本篇文章给大家介绍一下node项目中如何使用express来处理文件的上传,希望对大家有所帮助!

深入浅析Node的进程管理工具“pm2” 深入浅析Node的进程管理工具“pm2” Apr 03, 2023 pm 06:02 PM

本篇文章给大家分享Node的进程管理工具“pm2”,聊聊为什么需要pm2、安装和使用pm2的方法,希望对大家有所帮助!

Pi Node教学:什么是Pi节点?如何安装和设定Pi Node? Pi Node教学:什么是Pi节点?如何安装和设定Pi Node? Mar 05, 2025 pm 05:57 PM

PiNetwork节点详解及安装指南本文将详细介绍PiNetwork生态系统中的关键角色——Pi节点,并提供安装和配置的完整步骤。Pi节点在PiNetwork区块链测试网推出后,成为众多先锋积极参与测试的重要环节,为即将到来的主网发布做准备。如果您还不了解PiNetwork,请参考Pi币是什么?上市价格多少?Pi用途、挖矿及安全性分析。什么是PiNetwork?PiNetwork项目始于2019年,拥有其专属加密货币Pi币。该项目旨在创建一个人人可参与

聊聊用pkg将Node.js项目打包为可执行文件的方法 聊聊用pkg将Node.js项目打包为可执行文件的方法 Dec 02, 2022 pm 09:06 PM

​如何用pkg打包nodejs可执行文件?下面本篇文章给大家介绍一下使用pkg将Node项目打包为可执行文件的方法,希望对大家有所帮助!

npm node gyp失败怎么办 npm node gyp失败怎么办 Dec 29, 2022 pm 02:42 PM

npm node gyp失败是因为“node-gyp.js”跟“Node.js”版本不匹配,其解决办法:1、通过“npm cache clean -f”清除node缓存;2、通过“npm install -g n”安装n模块;3、通过“n v12.21.0”命令安装“node v12.21.0”版本即可。

什么是单点登录系统?用nodejs怎么实现? 什么是单点登录系统?用nodejs怎么实现? Feb 24, 2023 pm 07:33 PM

什么是单点登录系统?用nodejs怎么实现?下面本篇文章给大家介绍一下使用node实现单点登录系统的方法,希望对大家有所帮助!

使用Angular和Node进行基于令牌的身份验证 使用Angular和Node进行基于令牌的身份验证 Sep 01, 2023 pm 02:01 PM

身份验证是任何Web应用程序中最重要的部分之一。本教程讨论基于令牌的身份验证系统以及它们与传统登录系统的区别。在本教程结束时,您将看到一个用Angular和Node.js编写的完整工作演示。传统身份验证系统在继续基于令牌的身份验证系统之前,让我们先看一下传统的身份验证系统。用户在登录表单中提供用户名和密码,然后点击登录。发出请求后,通过查询数据库在后端验证用户。如果请求有效,则使用从数据库中获取的用户信息创建会话,然后在响应头中返回会话信息,以便将会话ID存储在浏览器中。提供用于访问应用程序中受

See all articles