写点什么

大文件传输与断点续传实现(极简 Demo:React+Node.js)

作者:EquatorCoco
  • 2024-12-20
    福建
  • 本文字数:3399 字

    阅读完需:约 11 分钟

简述


使用 React 前端和 Node.js 后端实现大文件传输和断点续传的功能。通过分片上传技术,可以有效地解决网络不稳定带来的传输中断问题。


文章内容


前端实现(React)


首先,您需要在前端项目中安装axiosspark-md5库以处理 HTTP 请求。可以使用以下命令:


npm i axios spark-md5
复制代码


以下是实现大文件上传的 React 组件代码:


import axios from 'axios';import { useRef, useState } from 'react';
// 定义文件分片大小(例如 5MB)const CHUNK_SIZE = 5 * 1024 * 1024;
/** 解析当前页面功能 */export default function FileUploader() { const [file, setFile] = useState(null); // 用于存储用户选择的文件 const [uploadProgress, setUploadProgress] = useState(0); // 上传进度 const uploading = useRef(false); // 用于防止重复触发上传逻辑
// 当用户选择文件时触发 const handleFileChange = (e) => { setFile(e.target.files[0]); // 将选择的文件存入状态 setUploadProgress(0); // 重置上传进度 };
// 计算文件的唯一标识 (哈希) const calculateFileHash = async (file) => { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = (e) => { const sparkMD5 = require('spark-md5'); const hash = sparkMD5.ArrayBuffer.hash(e.target.result); resolve(hash); }; reader.readAsArrayBuffer(file); }); };
// 开始文件上传 const handleUpload = async () => { if (!file || uploading.current) return; // 如果未选择文件或正在上传,则直接返回
uploading.current = true; // 标记为正在上传 const fileHash = await calculateFileHash(file); // 获取文件的唯一标识(哈希值) console.log('fileHash', fileHash); const totalChunks = Math.ceil(file.size / CHUNK_SIZE); // 计算文件分片总数 // 检查哪些分片已经上传 const { data: uploadedChunks } = await axios.post( 'http://localhost:5000/check', { fileName: file.name, fileHash, }, );
// 上传未完成的分片 for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { if (uploadedChunks?.includes(chunkIndex)) { console.log('跳过chunkIndx', chunkIndex); setUploadProgress(((chunkIndex + 1) / totalChunks) * 100); // 更新进度 continue; // 跳过已经上传的分片 } console.log('上传chunkIndx', chunkIndex); // 创建当前分片 const start = chunkIndex * CHUNK_SIZE; // 分片起始字节 const end = Math.min(file.size, start + CHUNK_SIZE); // 分片结束字节 const chunk = file.slice(start, end); // 获取分片
// 上传分片 const formData = new FormData(); formData.append('chunk', chunk); // 当前分片 formData.append('fileName', file.name); // 文件名 formData.append('fileHash', fileHash); // 文件唯一标识 formData.append('chunkIndex', chunkIndex); // 分片索引
await axios.post( `http://localhost:5000/upload?fileHash=${fileHash}&chunkIndex=${chunkIndex}&fileName=${file.name}`, formData, { onUploadProgress: (progressEvent) => { const progress = ((chunkIndex + progressEvent.loaded / progressEvent.total) / totalChunks) * 100; setUploadProgress(progress); // 实时更新上传进度 }, }, ); }
// 通知服务端合并分片 await axios.post('http://localhost:5000/merge', { fileName: file.name, fileHash, totalChunks, });
alert('上传成功!'); uploading.current = false; // 标记上传完成 };
return ( <div style={{ padding: '20px' }}> <p>大文件上传(支持断点续传)</p> <input type="file" onChange={handleFileChange} /> <button onClick={handleUpload}>提交上传文件</button> <div style={{ marginTop: '20px' }}> <progress value={uploadProgress} max="100" /> <div>上传进度:{uploadProgress.toFixed(2)}%</div> </div> </div> );}
复制代码


后端实现(Node.js)


在后端,您需要安装以下依赖:multerfs-extraexpresscorsbody-parser。可以使用以下命令:


npm i multer fs-extra express cors body-parser
复制代码


注意: 这些包应用于生产环境和开发环境。


以下是 Node.js 服务器的实现代码:


// 文件:server.jsconst express = require("express");const multer = require("multer");const fs = require("fs");const bodyParser = require("body-parser");const path = require("path");const cors = require("cors");const app = express();const uploadDir = path.join(__dirname, "uploads"); // 上传目录
// 确保上传目录存在if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir);}
app.use(cors()); // 允许跨域请求app.use(bodyParser.json());app.use(express.json()); // 解析 JSON 请求体
// 检查已上传的分片app.post("/check", (req, res) => { const { fileHash } = req.body; console.log("fileHash check",fileHash)
const fileChunkDir = path.join(uploadDir, fileHash); // 分片存储目录 if (!fs.existsSync(fileChunkDir)) { return res.json([]); // 如果目录不存在,返回空数组 }
// 返回已上传的分片索引 const uploadedChunks = fs.readdirSync(fileChunkDir).map((chunk) => { return parseInt(chunk.split("-")[1]); // 提取分片索引 }); res.json(uploadedChunks);});

// 设置 multer 中间件,用于处理文件上传const storage = multer.diskStorage({ destination: (req, file, cb) => { const fileHash = req.query.fileHash; // 从查询参数获取 fileHash const chunkDir = path.join(uploadDir, fileHash); // 确保切片目录存在 if (!fs.existsSync(chunkDir)) { fs.mkdirSync(chunkDir, { recursive: true }); } cb(null, chunkDir); }, filename: (req, file, cb) => { const { chunkIndex } = req.query; cb(null, `chunk-${chunkIndex}`); },});
const upload = multer({ storage:storage });
// 上传文件分片app.post("/upload", upload.single("chunk"), (req, res) => { const { fileHash } = req.body; res.status(200).send("分片上传成功");});

// 合并分片app.post("/merge", (req, res) => { const { fileName, fileHash, totalChunks } = req.body; console.log("fileName",req.body) const fileChunkDir = path.join(uploadDir, fileHash); const filePath = path.join(uploadDir, fileName);
// 创建可写流用于最终合并文件 const writeStream = fs.createWriteStream(filePath);
for (let i = 0; i < totalChunks; i++) { const chunkPath = path.join(fileChunkDir, `chunk-${i}`); const data = fs.readFileSync(chunkPath); // 读取分片 writeStream.write(data); // 写入最终文件 // fs.unlinkSync(chunkPath); // 删除分片文件--留下来,可以看上传记录 }
writeStream.end(); // 关闭流// fs.rmdirSync(fileChunkDir); // 删除分片目录--留下来,可以看上传记录 res.send("文件合并完成");});
app.listen(5000, () => { console.log("服务器已启动:http://localhost:5000");});
复制代码


总结


通过上述代码,您可以实现大文件的分片上传和断点续传功能。这种方法不仅提高了上传的可靠性,还能有效应对网络的不稳定性。希望这篇文章对您有所帮助!


文章转载自:走,板砖去

原文链接:https://www.cnblogs.com/chatGPT-Last/p/18617776

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
大文件传输与断点续传实现(极简Demo:React+Node.js)_前端_EquatorCoco_InfoQ写作社区