React前端开发中实现断点续传
在前端开发中,断点续传是指在上传文件时,如果上传过程中中断(如网络断开、页面刷新等),可以从断点处继续上传,而不是重新上传整个文件。这种功能在大文件上传场景中非常有用。
以下是实现断点续传的思路和具体步骤:
实现思路
文件分片:
将大文件分割成多个小文件块(chunk)。
每个文件块单独上传。
记录上传进度:
使用本地存储(如 localStorage)或服务端记录已上传的文件块。
断点续传:
在上传前检查已上传的文件块,跳过已上传的部分。
从未上传的部分开始继续上传。
合并文件:
所有文件块上传完成后,通知服务端合并文件。
实现步骤
1. 前端实现
以下是基于 React 和 Axios 的断点续传实现示例:
import React, { useState, useRef } from "react";
import axios from "axios";
const chunkSize = 1024 * 1024; // 每个文件块的大小(1MB)
const FileUpload = () => {
const [file, setFile] = useState(null);
const [progress, setProgress] = useState(0);
const fileInputRef = useRef(null);
// 处理文件选择
const handleFileChange = (e) => {
const selectedFile = e.target.files[0];
setFile(selectedFile);
};
// 上传文件
const handleUpload = async () => {
if (!file) return;
const totalChunks = Math.ceil(file.size / chunkSize); // 总文件块数
let uploadedChunks = 0; // 已上传的文件块数
// 检查已上传的文件块
const uploadedChunksFromStorage = JSON.parse(
localStorage.getItem(file.name) || []
);
for (let i = 0; i < totalChunks; i++) {
// 如果当前文件块已上传,跳过
if (uploadedChunksFromStorage.includes(i)) continue;
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end); // 获取文件块
const formData = new FormData();
formData.append("file", chunk);
formData.append("fileName", file.name);
formData.append("chunkIndex", i);
formData.append("totalChunks", totalChunks);
try {
await axios.post("/upload", formData, {
headers: { "Content-Type": "multipart/form-data" },
});
// 更新已上传的文件块
uploadedChunksFromStorage.push(i);
localStorage.setItem(file.name, JSON.stringify(uploadedChunksFromStorage));
// 更新上传进度
uploadedChunks++;
setProgress(Math.round((uploadedChunks / totalChunks) * 100));
} catch (error) {
console.error("上传失败", error);
return;
}
}
// 所有文件块上传完成后,通知服务端合并文件
try {
await axios.post("/merge", { fileName: file.name });
alert("上传完成");
localStorage.removeItem(file.name); // 清除本地存储的上传记录
setProgress(0);
fileInputRef.current.value = ""; // 清空文件输入
} catch (error) {
console.error("文件合并失败", error);
}
};
return (
<div>
<h1>文件断点续传</h1>
<input type="file" onChange={handleFileChange} ref={fileInputRef} />
<button onClick={handleUpload}>上传</button>
<div>上传进度: {progress}%</div>
</div>
);
};
export default FileUpload;
2. 服务端实现
服务端需要支持文件块的上传和合并。以下是一个简单的 Node.js 实现示例:
const express = require("express");
const multer = require("multer");
const fs = require("fs");
const path = require("path");
const app = express();
const upload = multer({ dest: "uploads/" });
// 上传文件块
app.post("/upload", upload.single("file"), (req, res) => {
const { fileName, chunkIndex } = req.body;
const chunkPath = path.join("uploads", `${fileName}-${chunkIndex}`);
// 将文件块保存到临时目录
fs.renameSync(req.file.path, chunkPath);
res.send({ message: "文件块上传成功" });
});
// 合并文件块
app.post("/merge", (req, res) => {
const { fileName } = req.body;
const chunksDir = path.join("uploads");
const chunks = fs
.readdirSync(chunksDir)
.filter((file) => file.startsWith(fileName))
.sort((a, b) => a.split("-")[1] - b.split("-")[1]);
// 创建可写流
const writeStream = fs.createWriteStream(path.join("uploads", fileName));
// 合并文件块
chunks.forEach((chunk) => {
const chunkPath = path.join(chunksDir, chunk);
const readStream = fs.createReadStream(chunkPath);
readStream.pipe(writeStream, { end: false });
readStream.on("end", () => {
fs.unlinkSync(chunkPath); // 删除已合并的文件块
});
});
writeStream.on("finish", () => {
res.send({ message: "文件合并成功" });
});
});
app.listen(3000, () => {
console.log("服务端运行在 http://localhost:3000");
});
关键点解析
文件分片:
使用 File.slice() 方法将文件分割成多个块。
每个块单独上传。
记录上传进度:
使用 localStorage 记录已上传的文件块索引。
上传前检查已上传的文件块,跳过已上传的部分。
断点续传:
如果上传中断,重新上传时从未上传的文件块开始。
合并文件:
所有文件块上传完成后,通知服务端合并文件。
服务端按顺序读取文件块并写入目标文件。