Nodejs 前端基于FileReader的断点续传

Nodejs 前端基于FileReader的断点续传

准备用FileReader来做断点续传,但是目前遇到的问题是,大文件的时候,不能把文件全读入内存,就需要分段读取文件内容,前端JS也没有类似Stream的pipe方法,能够在一个请求中,将所有分段读取的内容全部给服务器,目前看到有人的解决方案是,每次读取10M内容,然后每读一次,就发送一次请求,直到文件上传成功,请问有办法能在一个请求中实现断点续传的功能么?

4 回复

Node.js 前端基于FileReader的断点续传

在处理大文件上传时,一次性将整个文件读入内存是非常低效且可能引起内存溢出的问题。因此,我们需要将文件分段读取并逐段上传。尽管浏览器的FileReader对象没有直接提供类似于Node.js中的流式处理(如pipe方法),我们仍然可以通过分段读取文件内容,并通过多次请求将这些片段上传到服务器。

分段读取文件

首先,我们需要定义一个函数来读取文件的指定部分。我们可以使用FileReaderreadAsArrayBuffer方法来读取文件的特定部分。为了实现这一点,我们可以创建一个自定义函数,该函数接受文件对象、开始位置和结束位置作为参数,并返回一个包含该范围内的文件数据的Promise

function readBlob(file, start, end) {
    return new Promise((resolve, reject) => {
        const fileReader = new FileReader();
        const blob = file.slice(start, end); // 获取文件的特定部分
        fileReader.onload = function(e) {
            resolve(e.target.result);
        };
        fileReader.onerror = function() {
            reject(new Error('Failed to read blob'));
        };
        fileReader.readAsArrayBuffer(blob);
    });
}

断点续传功能

接下来,我们需要实现一个断点续传的功能。这通常涉及到检查已经上传的部分,确定还需要上传哪些部分。假设服务器会返回一个标识符,表示当前已经上传的最大偏移量,我们可以根据这个信息来决定从哪里开始读取文件。

async function uploadFile(file, chunkSize = 10 * 1024 * 1024, offset = 0) {
    const totalSize = file.size;
    while (offset < totalSize) {
        const end = Math.min(offset + chunkSize, totalSize);
        const data = await readBlob(file, offset, end);
        
        // 发送HTTP请求将数据发送到服务器
        await fetch('/upload', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/octet-stream'
            },
            body: data,
        });

        // 更新偏移量
        offset = end;
    }
}

示例调用

最后,我们可以调用上述函数来上传文件。如果需要支持断点续传,可以将已上传的部分保存在本地存储(如localStorage)中,并在下次上传时恢复该状态。

const fileInput = document.getElementById('file');
fileInput.addEventListener('change', async () => {
    const file = fileInput.files[0];
    let offset = localStorage.getItem('offset') || 0;
    await uploadFile(file, 10 * 1024 * 1024, parseInt(offset));
    localStorage.setItem('offset', file.size); // 上传完成后更新偏移量
});

通过这种方式,我们可以有效地实现断点续传功能,即使在网络不稳定的情况下也能保证文件的完整上传。


顶上去

我觉得这个有可能,你先请求一下服务器给你一个id,然后服务器记录这个文件传了多少,然后你从那个位置开始读取

对于基于 FileReader 的断点续传问题,确实如你所说,由于浏览器端没有直接支持流处理的 API(如 Node.js 中的 Stream),我们需要通过多次发送小块数据来实现。但是,我们可以通过一些策略在一个请求中实现断点续传功能。

一种可行的方法是在客户端使用 FileReader 分段读取文件,并在每次读取后发送一段数据到服务器。服务器端需要提供相应的接口来处理这些分段数据,并能识别出哪些部分已经上传,哪些部分还需要重新上传或继续上传。

这里提供一个简单的示例,展示如何实现这样的功能:

客户端 JavaScript 示例

function uploadFileChunk(file, chunkSize, start) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        let end = start + chunkSize;
        if (end > file.size) {
            end = file.size;
        }

        const blob = file.slice(start, end);
        reader.readAsArrayBuffer(blob);

        reader.onload = function() {
            const xhr = new XMLHttpRequest();
            xhr.open('POST', '/upload-chunk', true);
            xhr.setRequestHeader('Content-Type', 'application/octet-stream');
            xhr.setRequestHeader('X-File-Start', start);
            xhr.send(reader.result);
            xhr.onreadystatechange = () => {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    resolve(xhr.responseText);
                } else if (xhr.readyState === 4) {
                    reject(xhr.statusText);
                }
            };
        };

        reader.onerror = function() {
            reject(new Error('Failed to read file'));
        };
    });
}

// 调用函数开始上传
async function startUpload(file, chunkSize = 10 * 1024 * 1024) { // 10MB
    let start = 0;
    while (start < file.size) {
        try {
            await uploadFileChunk(file, chunkSize, start);
            start += chunkSize;
        } catch (error) {
            console.error('Error:', error);
            break;
        }
    }
}

服务器端 Node.js 示例(简化版)

const express = require('express');
const fs = require('fs');
const app = express();

app.post('/upload-chunk', (req, res) => {
    const start = parseInt(req.headers['x-file-start'], 10);
    const file = fs.createWriteStream(`./uploads/${Date.now()}-${start}-${start + req.headers['content-length']}`, { flags: 'a' });

    req.pipe(file);
    req.on('end', () => {
        res.send('Chunk uploaded successfully');
    });

    req.on('error', (err) => {
        res.status(500).send('Error uploading chunk');
    });
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

这段代码展示了如何使用 FileReaderXMLHttpRequest 在前端进行文件分段上传,同时服务器端通过 Express 接收并保存这些分段数据。为了实现断点续传,你需要在前端记录上传状态,并在上传失败时恢复到正确的起点继续上传。

回到顶部