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

embedded/2024/9/22 19:47:00/

介绍

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

场景

  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 < file.size; i += chunkSize) {chunks.push(file.slice(i, i + chunkSize));totalChunks++;
}// 上传函数
function uploadNextChunk() {if (currentChunk >= 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 < file.size; start += chunkSize) {const end = Math.min(start + chunkSize, file.size);chunks.push(file.slice(start, end));}return chunks;
}const file = document.getElementById('fileInput').files[0];
const chunkSize = 2 * 1024 * 1024; // 2MB
const chunks = splitFile(file, chunkSize);

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 dataValidate chunk index and file identifierSave the chunk to the storageCalculate 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的示例代码,用于选择文件并上传:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>File Upload with Chunking</title>
</head>
<body><input type="file" id="fileInput" /><button onclick="uploadFile()">Upload</button><script>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 < file.size; i += chunkSize) {chunks.push(file.slice(i, i + chunkSize));}return chunks;}async function uploadFile() {for (let i = 0; i < chunks.length; i++) {const chunk = chunks[i];const formData = new FormData();formData.append('file', chunk);formData.append('fileIdentifier', fileIdentifier);formData.append('chunkNumber', i);formData.append('totalChunks', chunks.length);try {const response = await fetch('http://localhost:3000/upload', {method: 'POST',body: formData,});if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}console.log('Chunk uploaded successfully');} catch (error) {console.error('Upload error:', error);}}}</script>
</body>
</html>

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

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

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

总结 

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

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

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

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

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

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

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

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

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


http://www.ppmy.cn/embedded/35620.html

相关文章

微博视频怎么下载无水印

在当今社交媒体时代&#xff0c;微博已经成为人们获取信息、分享生活的重要平台之一。许多人在浏览微博时常常遇到一个问题&#xff1a;如何下载微博视频而不留下烦人的水印呢?今天&#xff0c;我将分享一些神秘的方法&#xff0c;让你轻松解锁微博视频的无水印下载技巧。 第…

置身事内 书摘

信息的重要性&#xff1a;所谓山高皇帝远&#xff0c;上级领导不可能掌握和处理所有信息&#xff0c;故常常不得不依赖下级提供的信息&#xff0c;内容是否可靠&#xff0c;上级不见得知道&#xff0c;因此可能被下级牵着鼻子走。但因为信息复杂&#xff0c;不易处理&#xff0…

延时任务通知服务的设计及实现(三)-- JDK的延迟队列DelayQueue

一、接着上文 上文我们讲述了使用redisson的RDelayedQueue实现分布式延迟队列&#xff0c;本文我们将自己JDK的延迟队列DelayQueue实现。 相比前者的实现&#xff0c;作为进程内的延迟队列&#xff0c;它会遇到许多技术难点&#xff1a; 如何支持分布式的多个节点部署场景应…

ROS目标跟随(路径规划、slam、定位、雷达)——接上文,改善跟随的位置

确保进行跟随的小车始终在身后 最终效果代码改进 最终效果 ROS目标跟随改进版 代码改进 这里给出上一篇博客的链接&#xff1a;https://blog.csdn.net/m0_71523511/article/details/135610191 使用上一篇的launch文件创建机器人时&#xff0c;ros会自动创建一个坐标系相对关系…

密码学《图解密码技术》 记录学习 第十三章

目录 第十三章 13.1 本章学习的内容 13.2 PGP 简介 13.2.1 什么是 PGP 13.2.2 关于 OpenPGP 13.2.3关于GNU Privacy Guard 13.2.4 PGP 的功能 公钥密码 数字签名 单向散列函数 证书 压缩 文本数据 大文件的拆分和拼合 13.3 生成密钥对 13.4 加密与解密 13.4.1 加密 生成…

Centos 7 安装 subversion 新版本

使用 Centos yum repo 安装的 svn 版本是 1.7 但是现在需要更新的版本, 因为出现错误了: svn: E155021: This client is too old to work with the working copy at使用官网下载的包进行安装 1. 下载 .tar.gz 包 wget https://dlcdn.apache.org/subversion/subversion-1.14…

Python基础学习之random模块

在编程世界中&#xff0c;随机数有着广泛的应用&#xff0c;从简单的抽奖游戏到复杂的模拟和测试场景。Python的random模块为我们提供了丰富的工具&#xff0c;让我们能够轻松地生成和操作随机数。今天&#xff0c;我们就来深入了解一下这个强大的模块。 一、导入random模块 …

C++ | Leetcode C++题解之第73题矩阵置零

题目&#xff1a; 题解&#xff1a; class Solution { public:void setZeroes(vector<vector<int>>& matrix) {int m matrix.size();int n matrix[0].size();int flag_col0 false;for (int i 0; i < m; i) {if (!matrix[i][0]) {flag_col0 true;}for …