ajax上传、下载文件

一、上传

1.上传数据的封装

在上传文件时,最常用的方式是使用 FormData 对象,它会自动将请求头中的 Content-Type 请求头指定为multipart/form-data

const formData = new FormData();
formData.append("file", fileInput.files[0]);  // fileInput 是 <input type="file">

const xhr = new XMLHttpRequest();
xhr.open("POST", "/upload");

// 不要设置 Content-Type,浏览器会自动生成类似于 multipart/form-data 的头
xhr.send(formData);

2.上传进度条

XMLHttpRequest 对象的 upload 属性有一个 progress 事件,它可以获取文件上传的进度。每当文件上传进度更新时,都会触发该事件,提供已上传字节数和文件总大小的信息

    const xhr = new XMLHttpRequest();
    xhr.open("POST", "/upload");  // 替换为实际的上传 URL
    // 监听上传进度事件
    xhr.upload.addEventListener("progress", (event) => {
      if (event.lengthComputable) {
        const percentComplete = (event.loaded / event.total) * 100;
        const progressBar = document.getElementById("progress-bar");
        progressBar.style.width = percentComplete + "%";
        progressBar.textContent = Math.round(percentComplete) + "%";
      }
    });
    // 上传完成事件
    xhr.onload = () => {
      if (xhr.status === 200) {
        alert("上传成功!");
      } else {
        alert("上传失败!");
      }
    };
    // 发送表单数据
    xhr.send(formData);

3.大文件上传

文件分片上传的关键技术主要涉及文件分片、分片上传、分片管理和服务器端的分片合并。这些技术通过优化传输和可靠性,确保大文件在网络不稳定或需要续传的场景下能够顺利完成上传。以下是文件分片上传的关键技术点:

文件分片(Chunking)

  • 技术点:文件分片是将大文件按固定大小(通常为 1 MB 或 5 MB)划分为若干小块(Chunk),这些分片可以单独处理和上传。通过 JavaScript 的 Blob.slice 方法可以轻松实现分片。
  • 实现细节:在前端使用 Blob.slice(start, end) 方法获取每个分片,通过循环逐片读取并存储到 FormData 中。

分片上传与进度管理

  • 技术点:每个分片单独发起上传请求(如使用 XMLHttpRequestfetch),通过监听上传进度事件来实时更新上传进度。同时,可以记录每个分片的状态,便于上传失败时进行重试。
  • 并发上传:对于大文件,可以并发上传多个分片(Promise.all),提高上传速度。例如,前端可以控制同时上传 3 个分片,并在其中一个分片完成后再上传下一个分片。
  • 并发控制:通过控制并发数,既保证上传速度,又防止过多并发导致服务器压力过大。

分片标识与校验

  • 分片标识:每个分片通常使用唯一标识(如文件名、分片序号、总分片数)来标识,可以在请求中带上这些信息,方便服务器识别每个分片的位置。
  • 分片校验:为了确保每个分片正确上传,可以对每个分片计算哈希值(如 MD5、SHA-256),服务器上传后进行校验。这样可以在上传失败或分片出错时重试,保证数据完整性。

服务端分片接收与校验

  • 接收分片:服务器根据上传请求中的分片标识(如 chunkIndextotalChunks)存储每个分片,通常放在临时文件夹中。
  • 校验分片:每个分片上传后可以计算哈希并与客户端传来的哈希比对,确保分片未损坏。校验通过后才认为分片上传成功,失败则需要重新上传该分片。

服务端合并分片

  • 合并触发:在服务器端收到所有分片后,可以触发文件合并。通常通过 chunkIndextotalChunks 判断是否收到完整文件。
  • 合并顺序:合并时需要按照 chunkIndex 顺序将所有分片按序读入并写入完整文件。
  • 并发与异步:合并过程可以使用多线程或异步方法,尤其是当文件较大时,可以提升合并效率。
  • 清理临时文件:合并完成后需要删除分片的临时文件,释放服务器存储空间。

断点续传支持

  • 断点记录:断点续传的关键在于记录已上传的分片。前端可以在本地存储(如 localStorage)中记录已成功上传的分片序号。
  • 续传实现:当上传中断时,用户可以从记录的进度重新开始上传,无需重复上传已完成的分片。这可以减少带宽浪费和上传时间。

7.实现代码

客户端

  const CHUNK_SIZE = 1024 * 1024; // 每片1MB
  async function uploadFile() {
    const fileInput = document.getElementById("fileInput");
    const file = fileInput.files[0];
    if (!file) {
      alert("请选择文件后再上传!");
      return;
    }

    const totalChunks = Math.ceil(file.size / CHUNK_SIZE);  // 计算分片数量
    let uploadedChunks = 0;

    for (let start = 0; start < file.size; start += CHUNK_SIZE) {
      const end = Math.min(start + CHUNK_SIZE, file.size);
      const chunk = file.slice(start, end); // 获取文件分片
      const chunkIndex = start / CHUNK_SIZE; // 当前分片序号

      // 使用 FormData 上传每个分片
      const formData = new FormData();
      formData.append("file", chunk); // 添加分片数据
      formData.append("filename", file.name); // 添加文件名
      formData.append("chunkIndex", chunkIndex); // 添加分片序号
      formData.append("totalChunks", totalChunks); // 添加总分片数

      // 上传当前分片
      try {
//等待当前task完成后再进入下一个循环 await uploadChunk(formData); uploadedChunks++; updateProgress(uploadedChunks, totalChunks); // 更新进度条 } catch (error) { console.error(`分片 ${chunkIndex} 上传失败`, error); return; // 上传失败退出 } } alert("文件上传成功!"); } function uploadChunk(formData) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("POST", "/upload-chunk"); // 替换为实际的上传 URL xhr.onload = () => { if (xhr.status === 200) { resolve(); } else { reject(new Error("分片上传失败")); } }; xhr.onerror = () => reject(new Error("网络错误")); xhr.send(formData); }); } // 更新进度条 function updateProgress(uploadedChunks, totalChunks) { const progressBar = document.getElementById("progress-bar"); const percentComplete = (uploadedChunks / totalChunks) * 100; progressBar.textContent = `上传进度: ${Math.round(percentComplete)}%`; } 

服务端

在所有分片上传完毕后,服务器端会收到所有分片并合并。服务器可以根据 filenamechunkIndex 来识别每个分片,并按顺序合并它们。

 

const fs = require("fs");
const path = require("path");
const express = require("express");
const app = express();
const uploadDir = "uploads/"; // 保存分片的临时目录

app.post("/upload-chunk", (req, res) => {
  const { filename, chunkIndex, totalChunks } = req.body;
  const chunk = req.files.file; // 获取上传的分片文件

  // 将每个分片存储到临时文件夹
  const chunkPath = path.join(uploadDir, `${filename}.part${chunkIndex}`);
  fs.writeFileSync(chunkPath, chunk.data);

  // 检查是否所有分片都上传完成
  if (parseInt(chunkIndex) === totalChunks - 1) {
    // 合并所有分片
    const filePath = path.join(uploadDir, filename);
    const writeStream = fs.createWriteStream(filePath);

    for (let i = 0; i < totalChunks; i++) {
      const partPath = path.join(uploadDir, `${filename}.part${i}`);
      const data = fs.readFileSync(partPath);
      writeStream.write(data);
      fs.unlinkSync(partPath); // 删除已合并的分片
    }

    writeStream.end();
    res.send("文件上传成功并合并完成!");
  } else {
    res.send("分片上传成功!");
  }
});

app.listen(3000, () => {
  console.log("Server started on http://localhost:3000");
});

 

二、下载

1. 使用 <a> 标签的 download 属性

这是实现文件下载最简单的方式。<a> 标签的 download 属性可以强制浏览器下载文件而不是直接打开文件。

<a href="path/to/file.pdf" download="filename.pdf">下载文件</a>
当需要下载的数据是由 JavaScript 动态生成时,可以将数据转换为 Blob 对象,并生成一个 URL 来进行下载。使用 Blob 和 URL.createObjectURL 生成下载链接
function downloadBlob(data, filename, mimeType) {
  const blob = new Blob([data], { type: mimeType });
  const url = URL.createObjectURL(blob);

  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);

  URL.revokeObjectURL(url); // 释放 URL
}

// 使用示例
const data = "Hello, this is a text file!";
downloadBlob(data, "example.txt", "text/plain");

2.使用ajax和 Blob 实现文件下载

不推荐使用该方法。下载速度比方法1要慢,要等ajax接收完文件再调用浏览器的文件保存api,文件较大时点击后可能需要很长时间才有反应,并且下载传输过程中出现问题后,无法断点续传。

async function downloadFile(url, filename) {
  const response = await fetch(url);
  const blob = await response.blob();

  const a = document.createElement("a");
  a.href = URL.createObjectURL(blob);
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);

  URL.revokeObjectURL(a.href);
}

// 使用示例
downloadFile("path/to/file.pdf", "downloaded_file.pdf");

  

 

posted @ 2024-11-06 22:23  我是格鲁特  阅读(593)  评论(0编辑  收藏  举报