前言
分片上传(Multipart Upload)是一种将大文件分成较小片段(称为“分片”)进行上传的技术
实现分片上传功能
服务端
const express = require("express");
const multer = require("multer");
const fs = require("fs");
const path = require("path");
const bodyParser = require('body-parser')
const app = express();
const PORT = 3000;
app.use(bodyParser.json())
// 文件保存路径
const UPLOAD_DIR = path.resolve(__dirname, "uploads");
if (!fs.existsSync(UPLOAD_DIR)) {
fs.mkdirSync(UPLOAD_DIR);
}
// 配置multer
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, UPLOAD_DIR);
},
filename: (req, file, cb) => {
const { originalname } = file;
cb(null, originalname);
},
});
const upload = multer({ storage });
app.get("/", (req, res) => {
res.sendFile(path.resolve(__dirname, "./index.html"));
});
// 处理分片上传
app.post("/upload", upload.single("file"), (req, res) => {
console.log(req.file,req.body)
const { originalname, filename, path: tempPath } = req.file;
const { chunkIndex, totalChunks,name } = req.body;
const targetDir = path.join(UPLOAD_DIR, name);
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir);
}
// 重命名分片
const chunkPath = path.join(targetDir, `chunk-${chunkIndex}`);
console.log(tempPath,chunkPath)
fs.renameSync(tempPath, chunkPath);
res.sendStatus(200);
});
// 合并分片
app.post("/merge", (req, res) => {
const { filename, totalChunks } = req.body;
const targetDir = path.join(UPLOAD_DIR, filename);
const finalPath = path.join(UPLOAD_DIR, `merged-${filename}`);
const writeStream = fs.createWriteStream(finalPath);
for (let i = 0; i < totalChunks; i++) {
const chunkPath = path.join(targetDir, `chunk-${i}`);
const data = fs.readFileSync(chunkPath);
writeStream.write(data);
fs.unlinkSync(chunkPath); // 删除分片文件
}
writeStream.end();
fs.rmdirSync(targetDir); // 删除存储分片的目录
writeStream.on("finish", () => {
res.sendStatus(200);
});
});
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
客户端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Multipart Upload</title>
</head>
<body>
<input type="file" id="fileInput" />
<button onclick="uploadFile()">Upload</button>
<script>
const CHUNK_SIZE = 5 * 1024;
async function uploadFile() {
const fileInput = document.getElementById("fileInput");
const file = fileInput.files[0];
const name = file.name;
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
for (let i = 0; i < totalChunks; i++) {
const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
const formData = new FormData();
formData.append("file", chunk);
formData.append("chunkIndex", i);
formData.append("totalChunks", totalChunks);
formData.append("name", name);
await fetch("/upload", {
method: "POST",
body: formData,
});
}
await fetch("/merge", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
filename: name,
totalChunks: totalChunks,
}),
});
alert("File uploaded and merged successfully!");
}
</script>
</body>
</html>
OSS分片上传
创建一个bucket
创建密钥
问题1:
跨域问题
解决:
问题2:
Error: Please set the etag of expose-headers in OSS
解决:
实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OSS 分片上传</title>
</head>
<body>
<input type="file" id="fileInput" />
<button onclick="multipartUpload()">Upload</button>
<script>
const client = new OSS({
region: "", // 示例:'oss-cn-hangzhou',填写Bucket所在地域。
accessKeyId: "", // 确保已设置环境变量OSS_ACCESS_KEY_ID。
accessKeySecret: "", // 确保已设置环境变量OSS_ACCESS_KEY_SECRET。
bucket: "", // 示例:'my-bucket-name',填写存储空间名称。
});
const progress = (p, _checkpoint) => {
// Object的上传进度。
console.log(p);
// 分片上传的断点信息。
console.log(_checkpoint);
};
async function multipartUpload() {
try {
const fileInput = document.getElementById("fileInput");
const file = fileInput.files[0];
const name = file.name;
const result = await client.multipartUpload(name, file, {
progress,
});
console.log(result);
const head = await client.head(name);
console.log(head);
} catch (e) {
// 捕获超时异常。
if (e.code === "ConnectionTimeoutError") {
console.log("TimeoutError");
// do ConnectionTimeoutError operation
}
console.log(e);
}
}
</script>
</body>
</html>
结果
最后
大于100MB的文件采用分片上传更加合适,小于100MB的文件普通上传即可,网络问题中断我们还可以拓展断点续传,网络好了之后继续上传剩余的文件块