文件分块+断点续传 实现大文件上传全栈解决方案(前端+nodejs)

WongLeer 2024-07-21 11:03:01 阅读 85

1. 文件分块

将大文件切分成较小的片段(通常称为分片或块),然后逐个上传这些分片。这种方法可以提高上传的稳定性,因为如果某个分片上传失败,只需要重新上传该分片而不需要重新上传整个文件。同时,分片上传还可以利用多个网络连接并行上传多个分片,提高上传速度。

2. 断点续传

在上传过程中,如果网络中断或上传被中止,断点续传技术可以记录已成功上传的分片信息,以便在恢复上传时继续上传未完成的部分,而不需要重新上传整个文件。这种技术可以大大减少上传失败的影响,并节省时间和带宽。

3. node项目目录初始化

在这里插入图片描述

安装依赖

express 敏捷启动服务multer 读取文件,存储cors 解决跨域

目录结构

src

TED.mp4 (长视频,10分钟,可以下载这个 <code>https://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4) uploads 存放切片video 存放将切片拼接后的视频index.html 前端页面

代码附上

index.html

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8" />code>

<meta name="viewport" content="width=device-width, initial-scale=1.0" />code>

<title>Document</title>

</head>

<body>

<input id="file" type="file" />code>

<script>

// functions

// 实现切片的方法

const makeChunk = (file, size = 1024 * 1024 * 4) => {

const chunks = [];

for (let i = 0; i < file.size; i += size) {

const chunk = file.slice(i, i + size);

chunks.push(chunk);

}

return chunks;

};

// 上传分片后的文件方法

const uploadChunks = (chunks) => {

// 1. 使用Promise.all保证所有上传方法执行成功

// 2. 必须要给每个分片文件加标识,才可以让node端进行按序拼接

const list = [];

for (let i = 0; i < chunks.length; i++) {

const formData = new FormData();

formData.append("filename", "ted");

formData.append("index", i);

formData.append("chunk", chunks[i]); // 千万注意,切片文件要最后append否则会出现意外的bug

list.push(

fetch("http://localhost:3000/upload", {

method: "POST",

body: formData,

})

);

}

Promise.all(list)

.then((res) => {

console.log("上传成功", res);

fetch("http://localhost:3000/merge", {

method: "POST",

headers: {

"Content-Type": "application/json",

},

body: JSON.stringify({

fileName: "TED演讲10分钟长视频",

}),

});

})

.catch((err) => {

console.error("上传失败", err);

});

};

// logic

const file = document.querySelector("#file");

file.addEventListener("change", (e) => {

let file = e.target.files[0]; // 我们只处理单个文件因此取第一个元素即可

// file是一个对象,底层继承于Blob,借助于Blob身上的slice方法,可以实现对大文件的分片操作

console.log(file);

const chunks = makeChunk(file);

console.log("chunks:", chunks);

uploadChunks(chunks);

});

</script>

</body>

</html>

index.js

import fs from "node:fs";

import path from "node:path";

import express from "express";

import multer from "multer";

import cors from "cors";

console.log("cors:", cors);

// 1. 初始化multer

const storage = multer.diskStorage({

// 指定切片存放目录

destination: function (req, file, cb) {

cb(null, "../uploads/");

},

filename: function (req, file, cb) {

console.log("req.body.index:", req.body.index);

console.log("file", file);

cb(null, `${ req.body.filename}-${ req.body.index}`);

},

});

const upload = multer({ storage });

const app = express();

app.use(cors());

app.use(express.json());

// upload.single("chunk") 其中chunk对应前端上传的文件切片名字

app.post("/upload", upload.single("chunk"), (req, res) => {

console.log("req.body:", req.body);

res.header("Content-Type", "application/json;charset=utf-8");

res.send("ok");

});

// 拼接切片

app.post("/merge", (req, res) => {

// 获取uploadDir的目录

const uploadDir = path.join(process.cwd(), "../uploads");

const dirs = fs.readdirSync(uploadDir); // 发现是乱序的

// 对dirs数组进行排序

dirs.sort((a, b) => {

return a.split("-")[1] - b.split("-")[1];

});

const videoDir = path.join(

process.cwd(),

"../video",

`${ req.body.fileName}.mp4`

);

dirs.forEach((item) => {

// 合并成一个完整的文件至video目录

fs.appendFileSync(videoDir, fs.readFileSync(path.join(uploadDir, item)));

// 删除已合并的切片文件

fs.unlinkSync(path.join(uploadDir, item));

});

console.log(dirs);

res.send("okk");

});

app.listen(3000, () => {

console.log("server is running at port 3000");

});

最终效果

在这里插入图片描述

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。