大文件上传可能会有浏览器限制,网络稳定等问题影响用户体验,可以通过切片上传解决。
一、切片上传
1. 原理
将大文件分割成较小的块(chunks),然后逐个上传这些块。这样可以减少单个上传请求的大小,降低网络故障和超时的风险。
在服务器端,可以接收这些块并将它们组合成完整的文件。
2. 实现步骤
2.1 文件分割
使用 JavaScript 的 `File` 对象和 `Blob` 类型来分割文件。可以根据一定的大小(如 1MB)将文件分割成多个块。
const file = document.getElementById("fileInput").files[0];const chunkSize = 1024 * 1024; // 1MBconst chunks = [];let start = 0;while (start < file.size) {const end = Math.min(start + chunkSize, file.size);const chunk = file.slice(start, end);chunks.push(chunk);start = end;}
2.2 逐个上传块
使用 `XMLHttpRequest` 或 `fetch` API 发送每个块的上传请求。可以为每个块设置一个唯一的标识符,以便服务器端能够正确地组合它们。
async function uploadChunks(chunks) {for (let i = 0; i < chunks.length; i++) {const chunk = chunks[i];const formData = new FormData();formData.append("chunk", chunk);formData.append("index", i);formData.append("totalChunks", chunks.length);formData.append("fileName", file.name);await fetch("/upload", {method: "POST",body: formData,});}}
2.3 服务器端处理
在服务器端(如 Node.js),接收上传的块并将它们存储在临时目录中。根据块的索引和总数,将它们组合成完整的文件。
const fs = require("fs");const path = require("path");app.post("/upload", (req, res) => {const chunk = req.files.chunk;const index = req.body.index;const totalChunks = req.body.totalChunks;const fileName = req.body.fileName;const filePath = path.join(__dirname, "uploads", fileName);fs.mkdirSync(path.dirname(filePath), { recursive: true });fs.appendFileSync(filePath, chunk.data, {flag: index === 0 ? "w" : "a",});if (index === totalChunks - 1) {res.send("File uploaded successfully.");} else {res.sendStatus(200);}});
二、断点续传(Resume Upload)
1. 原理
如果上传过程中出现网络故障或其他问题,可以记录上传的进度,以便在下次上传时从上次中断的地方继续上传,而不是重新开始上传整个文件。
2. 实现步骤
2.1 记录进度
在前端,可以使用 `localStorage` 或其他存储机制来记录每个块的上传状态和进度。当一个块上传成功后,将其标记为已上传。
async function uploadChunks(chunks) {for (let i = 0; i < chunks.length; i++) {if (isChunkUploaded(i)) {continue;}const chunk = chunks[i];const formData = new FormData();formData.append("chunk", chunk);formData.append("index", i);formData.append("totalChunks", chunks.length);formData.append("fileName", file.name);await fetch("/upload", {method: "POST",body: formData,});markChunkAsUploaded(i);}}function isChunkUploaded(index) {return localStorage.getItem(`chunkUploaded_${index}`) === "true";}function markChunkAsUploaded(index) {localStorage.setItem(`chunkUploaded_${index}`, "true");}
2.2 服务器端支持
服务器端需要能够识别哪些块已经上传,并在接收到新的块时正确地组合它们。可以通过检查文件的大小和块的索引来确定上传的进度。
app.post("/upload", (req, res) => {const chunk = req.files.chunk;const index = req.body.index;const totalChunks = req.body.totalChunks;const fileName = req.body.fileName;const filePath = path.join(__dirname, "uploads", fileName);fs.mkdirSync(path.dirname(filePath), { recursive: true });if (fs.existsSync(filePath) &&fs.statSync(filePath).size >= index * chunk.length) {res.sendStatus(200);return;}fs.appendFileSync(filePath, chunk.data, {flag: index === 0 ? "w" : "a",});if (index === totalChunks - 1) {res.send("File uploaded successfully.");} else {res.sendStatus(200);}});
三、进度显示
1. 原理
在上传过程中,向用户显示上传的进度,以便他们了解上传的状态。可以通过计算已上传的块的数量或已上传的字节数来显示进度。
2. 实现步骤
2.1 计算进度
在前端,可以通过计算已上传的块的数量与总块数的比例来显示进度。或者,可以计算已上传的字节数与文件总大小的比例。
async function uploadChunks(chunks) {let uploadedBytes = 0;for (let i = 0; i < chunks.length; i++) {if (isChunkUploaded(i)) {uploadedBytes += chunks[i].size;continue;}const chunk = chunks[i];const formData = new FormData();formData.append("chunk", chunk);formData.append("index", i);formData.append("totalChunks", chunks.length);formData.append("fileName", file.name);const response = await fetch("/upload", {method: "POST",body: formData,});if (response.ok) {uploadedBytes += chunk.size;markChunkAsUploaded(i);}}const progress = uploadedBytes / file.size;updateProgressBar(progress);}function updateProgressBar(progress) {const progressBar = document.getElementById("progressBar");progressBar.style.width = `${progress * 100}%`;}
2.2 显示进度
使用 HTML 和 CSS 来创建一个进度条或其他可视化元素,以显示上传的进度。可以根据计算出的进度值来更新进度条的宽度或显示其他进度信息。
<divid="progressBar"style="width: 0%; height: 10px; background-color: blue;"></div>
总结:实现前端大文件上传需要综合考虑文件分割、断点续传和进度显示等方面。通过使用切片上传和断点续传技术,可以提高上传的可靠性和效率,同时向用户提供良好的上传体验。