Home > Article > Web Front-end > How to implement breakpoint resumption in Node.js
This article will introduce you to the method of implementing Node.js. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to everyone.
Normal business needs: upload pictures, Excel, etc. After all, the size of a few M can be uploaded to the server quickly.
For uploading videos and other large files that are hundreds of M or several G in size, you need to wait for a long time.
This produces corresponding solutions. For pause, network disconnection, poor network situations when uploading large files, useSlicing Breakpoint resume transfer can handle the above situation very well
Slice
Resumable transfer
[Recommended learning: "nodejs tutorial"]
This demo provides key Some ideas and methods, other functions such as: File restriction, lastModifiedDate verification file repeatability, Cache file regular clearing and other functional extensions can be based on this code Add on.
<input class="video" type="file" /> <button type="submit" onclick="handleVideo(event, '.video', 'video')"> 提交 </button>
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); }); };
// 使用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, }); }); };
The above is the detailed content of How to implement breakpoint resumption in Node.js. For more information, please follow other related articles on the PHP Chinese website!