大文件上传可能会有浏览器限制,网络稳定等问题影响用户体验,可以通过切片上传解决。

一、切片上传

1. 原理

将大文件分割成较小的块(chunks),然后逐个上传这些块。这样可以减少单个上传请求的大小,降低网络故障和超时的风险。

在服务器端,可以接收这些块并将它们组合成完整的文件。

2. 实现步骤

2.1 文件分割

使用 JavaScript 的 `File` 对象和 `Blob` 类型来分割文件。可以根据一定的大小(如 1MB)将文件分割成多个块。

const file = document.getElementById("fileInput").files[0];

const chunkSize = 1024 * 1024; // 1MB

const 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 来创建一个进度条或其他可视化元素,以显示上传的进度。可以根据计算出的进度值来更新进度条的宽度或显示其他进度信息。

<div

  id="progressBar"

  style="width: 0%; height: 10px; background-color: blue;"

></div>

总结:实现前端大文件上传需要综合考虑文件分割、断点续传和进度显示等方面。通过使用切片上传和断点续传技术,可以提高上传的可靠性和效率,同时向用户提供良好的上传体验。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐