前端之实现大文件上传的解决方案———断点续传

07-21 560阅读

介绍

断点续传是一种网络数据传输方式,允许从中断的地方恢复下载或上传操作,而不是从头开始。这对于大文件传输尤其有用,因为它可以节省时间并减少网络资源的浪费。在前端开发中,实现大文件的断点续传可以提升用户体验,尤其是在网络不稳定或速度较慢的情况下。

前端之实现大文件上传的解决方案———断点续传
(图片来源网络,侵删)

场景

  1. 用户上传大文件至服务器,如视频、图片集合或大型文档。
  2. 用户下载服务器上的大文件,如高清视频、大型软件安装包。
  3. 网络不稳定导致传输中断,用户希望从中断处继续传输。

 

原理

断点续传的基本原理是将大文件分割成多个小块,然后分别传输这些小块。每个小块都有自己的编号,客户端和服务器端都记录已成功传输的块。如果传输过程中断,客户端可以从最后成功传输的块之后继续传输,而不是从头开始。

 

实现方案

  1. 文件分片:将大文件分割成多个小块。
  2. 并行上传:为了提高上传速度,可以同时上传多个小块。
  3. 校验和记录:每个文件块传输前后都需要进行校验,确保数据的完整性,同时记录已上传的块。
  4. 请求恢复:在传输中断后,客户端向服务器请求恢复中断的传输。
  5. 服务器支持:服务器需要能够理解客户端的恢复请求,并提供未完成传输的文件块。

示例代码说明 

以下是使用JavaScript实现大文件断点续传的一个简单示例:

// 假设我们有一个文件对象
let file = document.getElementById('fileInput').files[0];
// 分割文件
const chunkSize = 2 * 1024 * 1024; // 2MB
let chunks = [], currentChunk = 0, totalChunks = 0;
for (let i = 0; i = totalChunks) return;
    const chunk = chunks[currentChunk];
    const formData = new FormData();
    formData.append('file', chunk);
    formData.append('chunkNumber', currentChunk);
    formData.append('totalChunks', totalChunks);
    fetch('/upload', { // 假设服务器端点是 '/upload'
        method: 'POST',
        body: formData,
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            currentChunk++;
            uploadNextChunk(); // 上传下一块
        } else {
            console.error('Upload error: ', data.message);
        }
    })
    .catch(error => console.error('Upload error: ', error));
}
// 开始上传
uploadNextChunk();

这段代码首先将文件分割成多个2MB的块,然后使用递归函数uploadNextChunk来逐个上传这些块。在上传过程中,我们使用FormData对象来构建上传请求的正文,并发送到服务器。服务器需要相应地处理这些请求,并在上传中断时能够从中断的地方恢复。

请注意,这只是一个简化的示例,实际的实现可能需要考虑更多的因素,如错误处理、上传进度显示、服务器端的逻辑等。此外,为了实现断点续传,服务器端也需要相应的支持。

1. 文件分片

文件分片是将大文件分割成多个小块的过程。这可以通过JavaScript的Blob对象来实现。

示例代码:

function splitFile(file, chunkSize) {
  const chunks = [];
  for (let start = 0; start  

2. 并行上传

并行上传可以提高上传速度,特别是当网络带宽允许多个连接同时进行时。这可以通过JavaScript的Promise.all来实现。

示例代码:

 

async function uploadChunks(chunks, fileIdentifier) {
  const uploadPromises = chunks.map((chunk, index) => {
    const formData = new FormData();
    formData.append('file', chunk);
    formData.append('index', index);
    formData.append('filename', fileIdentifier);
    
    return fetch('/upload', {
      method: 'POST',
      body: formData,
    }).then(response => response.json());
  });
  return Promise.all(uploadPromises);
}
const fileIdentifier = 'unique_file_identifier'; // 服务器用来识别文件的标识
uploadChunks(chunks, fileIdentifier).then(results => {
  if (results.every(result => result.success)) {
    console.log('All chunks uploaded successfully.');
  } else {
    console.error('Some chunks failed to upload.');
  }
});

3. 校验和记录

校验和用于验证数据的完整性。记录已上传的块可以用于断点续传。

示例代码:

// 假设服务器返回每个块的校验和
async function verifyChunks(chunks) {
  const results = await uploadChunks(chunks, fileIdentifier);
  const checksums = results.map(result => result.checksum);
  return checksums;
}
// 假设有一个函数用于记录校验和
function recordChecksums(checksums) {
  // 将校验和存储在localStorage或数据库中
}
// 上传并记录校验和
verifyChunks(chunks).then(recordChecksums);

 

4. 请求恢复

当传输中断时,客户端需要请求恢复中断的传输。

示例代码:

function resumeUpload(fileIdentifier, lastUploadedIndex) {
  const remainingChunks = chunks.slice(lastUploadedIndex + 1);
  return uploadChunks(remainingChunks, fileIdentifier);
}
// 假设从localStorage或数据库中获取最后上传的块的索引
const lastUploadedIndex = getLastUploadedIndex(fileIdentifier);
if (lastUploadedIndex !== undefined) {
  resumeUpload(fileIdentifier, lastUploadedIndex).then(results => {
    if (results.every(result => result.success)) {
      console.log('Resuming upload completed.');
    } else {
      console.error('Failed to resume upload.');
    }
  });
}

5. 服务器支持

服务器端需要能够接收分片数据,处理并行上传,并支持断点续传。

示例伪代码:

/upload (POST method)
  Receive file chunk data
  Validate chunk index and file identifier
  Save the chunk to the storage
  Calculate and return the checksum of the chunk

 请注意,这些示例代码仅用于说明断点续传的实现原理,实际应用中需要考虑更多的细节,如错误处理、安全性、性能优化等。服务器端的实现也需要相应的逻辑来处理分片上传、验证、存储和恢复。

6.完整案例

为了实现一个简单的断点续传功能,使用Node.js作为后端服务器,并且使用Express框架来简化HTTP请求的处理。前端将使用JavaScript的Fetch API来处理文件的上传。

后端实现 (Node.js + Express)

npm install express body-parser multipart-parser --save

 以下是Node.js服务器的示例代码:

const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const multipartParser = require('parse-multipart');
const app = express();
const port = 3000;
// 配置中间件
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// 文件上传的端点
app.post('/upload', (req, res) => {
  const body = req.body;
  const file = req.files.file;
  // 假设我们有一个文件标识符和块编号
  const fileIdentifier = body.fileIdentifier;
  const chunkNumber = parseInt(body.chunkNumber);
  const totalChunks = parseInt(body.totalChunks);
  // 定义文件保存的路径和文件名
  const filePath = `./uploads/${fileIdentifier}`;
  const chunkPath = `${filePath}/chunk_${chunkNumber}`;
  // 检查文件标识符对应的文件夹是否存在,如果不存在则创建
  if (!fs.existsSync(filePath)) {
    fs.mkdirSync(filePath, { recursive: true });
  }
  // 保存文件块
  const fileStream = fs.createWriteStream(chunkPath);
  fileStream.write(file.data, 'binary', (err) => {
    if (err) {
      return res.status(500).send('Error saving file chunk.');
    }
    res.status(200).json({ success: true, message: 'Chunk uploaded successfully.' });
  });
  // 检查是否所有块都已上传
  const allChunksUploaded = Array.from({ length: totalChunks }, (_, i) =>
    fs.existsSync(`${filePath}/chunk_${i + 1}`)
  );
  if (allChunksUploaded.every(Boolean)) {
    // 合并文件块
    const chunks = fs.readdirSync(filePath).map(chunk => fs.readFileSync(path.join(filePath, chunk)));
    const output = fs.createWriteStream(`./uploads/${fileIdentifier}.complete`);
    chunks.forEach((chunk) => output.write(chunk));
    
    // 删除临时文件夹
    fs.rmSync(filePath, { recursive: true });
    res.status(200).json({ success: true, message: 'File assembled successfully.' });
  }
});
app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

前端实现 (HTML + JavaScript)

以下是前端HTML和JavaScript的示例代码,用于选择文件并上传:



    
    File Upload with Chunking


    
    Upload
    
        const fileInput = document.getElementById('fileInput');
        let file, chunks, fileIdentifier;
        fileInput.addEventListener('change', () => {
            file = fileInput.files[0];
            fileIdentifier = Date.now().toString(); // 简单的文件标识符
            chunks = splitFile(file);
        });
        function splitFile(file, chunkSize = 2 * 1024 * 1024) {
            const chunks = [];
            for (let i = 0; i  

在这个示例中,前端使用允许用户选择一个文件,然后通过splitFile函数将文件分割成多个块。当用户点击“Upload”按钮时,uploadFile函数被调用,它将循环遍历所有的块,并将它们作为表单数据上传到服务器。

后端使用Express处理上传请求,并将文件块保存在本地磁盘上。一旦所有块都上传完毕,服务器将它们合并成原始文件,并删除临时文件块。

请注意,这个示例是一个简化的版本,没有实现所有可能的错误处理、安全性措施(如验证用户权限、限制文件大小和类型等)以及生产环境中可能需要的其他功能。在实际部署之前,需要添加这些功能以确保系统的健壮性和安全性。

总结 

断点续传是一种在网络传输中提高效率和可靠性的技术,特别适用于大文件的上传和下载。以下是实现大文件断点续传的关键步骤的总结:

  1. 文件分片:将大文件分割成多个小块,这允许并行上传和从中断处恢复。

  2. 并行上传:通过同时上传多个文件块,可以提高整体的上传速度。

  3. 校验和记录:每个文件块在上传前后都进行校验,以确保数据的完整性。同时,记录已成功上传的块,为断点续传提供依据。

  4. 请求恢复:当传输中断时,客户端使用记录的信息请求从最后成功上传的块继续上传。

  5. 服务器支持:服务器端需要能够接收分片数据,验证块的完整性,并支持断点续传的逻辑。

在前端实现中,JavaScript提供了强大的API来处理文件操作和网络请求。通过使用Blob对象分割文件,FormData对象构建请求,以及异步编程模式(如Promise),前端可以有效地管理文件的上传过程。

然而,为了实现一个完整的断点续传功能,还需要服务器端的配合。服务器需要能够接收分片数据,存储它们,并在客户端请求恢复时提供必要的信息。

最后,实现断点续传时,还需要考虑实际应用中的各种挑战,包括但不限于网络波动、错误处理、上传进度的显示、安全性(如认证和加密)以及性能优化。通过综合这些因素,可以为用户提供一个可靠、高效和用户友好的大文件传输解决方案。

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]