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

11 篇文章 0 订阅

介绍

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

场景

  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 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的示例代码,用于选择文件并上传:

<!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),前端可以有效地管理文件的上传过程。

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

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

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
前端文件指的是上文件大小较大,超过了常规的文件限制(例如2MB)。这时需要使用特殊的上方式,常见的有两种: 1. 分片上:将大文件分成多个小文件,分别上到服务器,并在服务器端将这些小文件合并成一个完整的文件。 2. 断点:将大文件分成多个小文件,分别上到服务器,上过程中若出现网络中断或其他原因导致上失败,可以从失败的位置继,避免重新上整个文件实现前端文件断点需要使用一些第三方库或框架,例如: 1. Plupload:一个基于Flash和HTML5的文件库,支持分片上断点。 2. Resumable.js:一个基于HTML5的文件库,支持断点。 3. jQuery-File-Upload:一个基于jQuery的文件插件,支持分片上断点。 在使用这些库或框架实现文件断点时,需要注意以下几点: 1. 服务器端需要支持分片上断点,否则无法实现这些功能。 2. 分片上断点需要对文件进行切片,这可能会影响上速度和文件完整性,需要做好相应的处理。 3. 断点需要记录上进度,以便在上失败时能够从失败的位置继。 总之,前端文件断点是一个比较复杂的问题,需要使用一些专门的库或框架来实现,同时需要注意一些细节问题,才能保证上效率和上成功率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值