【JS】基于原生JavaScript的大文件切片上传及断点续传实现

基于原生JavaScript的大文件切片上传及断点续传实现

在现代Web应用中,大文件上传是一个常见但具有挑战性的功能。随着文件大小的增加,如何高效可靠地上传文件至服务器成为亟待解决的问题。在本文中,我将介绍如何使用原生JavaScript实现大文件切片上传及断点续传功能。

为什么要使用文件切片和断点续传?
  1. 稳定性:网络连接中断或者异常时,通过断点续传,可以继续从中断处继续上传而不是重新开始。
  2. 性能优化:通过并行上传多个片段可以提高上传速度。
  3. 资源管理:大文件在上传过程中占用大量资源,通过切片可以有效管理和控制这些资源。

实现思路

  1. 文件切片:将大文件切割成较小的片段。
  2. 上传片段:逐个上传文件片段。
  3. 记录进度:保存上传进度信息,实现断点续传。
  4. 合并片段:在服务器端合并所有片段成完整文件。

前端实现

首先,我们需要设计一个简单的HTML页面,包含文件选择和上传按钮:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>大文件断点续传上传</title>
</head>
<body>
    <input type="file" id="fileInput">
    <button id="uploadBtn">上传</button>
    <div id="progress"></div>
    <script src="uploader.js"></script>
</body>
</html>

接下来,我们编写uploader.js脚本来实现核心功能:

1. 初始化和事件绑定
const CHUNK_SIZE = 5 * 1024 * 1024; // 每片大小5MB
const UPLOAD_URL = "https://example.com/upload"; // 替换为实际的上传URL

document.getElementById('uploadBtn').addEventListener('click', handleUpload);

在这一部分,我们定义了每个文件片段的大小为5MB,并设置了上传的目标URL。当用户点击上传按钮时,将触发handleUpload函数。

2. 选择文件并计算切片信息
function handleUpload() {
    const file = document.getElementById('fileInput').files[0];
    const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
    let currentChunk = readUploadProgress();

    if (file) {
        uploadChunk(file, currentChunk, totalChunks);
    }
}

handleUpload函数中,我们获取用户选择的文件,并计算出总共需要多少个片段来完成上传。同时,我们读取之前保存的上传进度,如果没有则从零开始。

3. 文件切片并上传
function uploadChunk(file, currentChunk, totalChunks) {
    if (currentChunk >= totalChunks) return;

    const start = currentChunk * CHUNK_SIZE;
    const end = Math.min(start + CHUNK_SIZE, file.size);
    const chunk = file.slice(start, end);
    const formData = new FormData();

    formData.append('chunk', chunk);
    formData.append('currentChunk', currentChunk);
    formData.append('totalChunks', totalChunks);
    formData.append('fileName', file.name);

    const xhr = new XMLHttpRequest();
    xhr.open('POST', UPLOAD_URL, true);

    xhr.onload = function () {
        if (xhr.status === 200) {
            currentChunk++;
            saveUploadProgress(currentChunk); // 保存当前上传进度
            if (currentChunk < totalChunks) {
                uploadChunk(file, currentChunk, totalChunks);
            } else {
                document.getElementById('progress').innerText = '上传完成!';
                localStorage.removeItem('uploadProgress'); // 上传完成后移除进度记录
            }
        } else {
            document.getElementById('progress').innerText = `上传片段 ${currentChunk} 失败`;
        }
    };

    xhr.onerror = function () {
        document.getElementById('progress').innerText = `上传片段 ${currentChunk} 出错`;
    };

    xhr.send(formData);
    updateProgress(currentChunk, totalChunks);
}

uploadChunk函数核心逻辑如下:

  1. 根据当前片段索引计算切片的起始和结束位置,然后切分文件得到当前片段。
  2. 使用FormData对象将片段和相关信息(当前片段索引、总片段数、文件名)封装起来。
  3. 创建一个XMLHttpRequest对象并发送HTTP POST请求上传片段。
  4. 根据请求结果进行相应处理。如果上传成功则更新进度并继续上传下一个片段,否则显示错误信息。
4. 进度更新和数据持久化
function updateProgress(currentChunk, totalChunks) {
    const progress = (currentChunk / totalChunks) * 100;
    document.getElementById('progress').innerText = `上传进度: ${progress.toFixed(2)}%`;
}

function saveUploadProgress(currentChunk) {
    localStorage.setItem('uploadProgress', currentChunk);
}

function readUploadProgress() {
    return parseInt(localStorage.getItem('uploadProgress'), 10) || 0;
}

为了实现断点续传,我们将上传进度信息存储在localStorage中,这样即使浏览器刷新或关闭也能记住上次的上传进度。

服务端实现(以Node.js为例)

在服务端,我们使用Node.js来接收并合并文件片段:

1. 配置和接收文件片段
const express = require('express');
const fs = require('fs');
const path = require('path');
const multer = require('multer');
const app = express();

const upload = multer({ dest: 'uploads/' });

app.post('/upload', upload.single('chunk'), (req, res) => {
    const { fileName, currentChunk, totalChunks } = req.body;
    const chunk = req.file;

    const destPath = path.join('uploads', fileName + '_' + currentChunk);

    fs.rename(chunk.path, destPath, (err) => {
        if (err) {
            return res.status(500).send({ error: 'Failed to save chunk' });
        }

        if (Number(currentChunk) + 1 === Number(totalChunks)) {
            combineChunks(fileName, totalChunks)
                .then(() => res.status(200).send({ message: 'Upload completed!' }))
                .catch((err) => res.status(500).send({ error: 'Failed to combine chunks' }));
        } else {
            res.status(200).send({ message: 'Chunk uploaded successfully' });
        }
    });
});

这一段代码处理从前端传来的文件片段,并将其保存到指定目录。每个片段根据文件名和片段索引命名。接收到所有片段后,开始合并文件。

2. 合并文件片段
function combineChunks(fileName, totalChunks) {
    return new Promise((resolve, reject) => {
        const writeStream = fs.createWriteStream(path.join('uploads', fileName));

        function appendChunk(index) {
            if (index >= totalChunks) {
                writeStream.end();
                resolve();
                return;
            }

            const chunkPath = path.join('uploads', fileName + '_' + index);
            const readStream = fs.createReadStream(chunkPath);

            readStream.pipe(writeStream, { end: false });

            readStream.on('end', () => {
                fs.unlink(chunkPath, (err) => {
                    if (err) reject(err);
                    appendChunk(index + 1);
                });
            });

            readStream.on('error', reject);
        }

        appendChunk(0);
    });
}

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

combineChunks函数的逻辑如下:

  1. 创建一个写入流以合并文件。
  2. 逐一读取每个片段并写入合并文件。如果读取完成一个片段后删除该片段的临时文件。
  3. 使用回调函数方式来逐块处理片段,并在所有片段处理完毕后关闭写入流并完成合并。

总结

通过以上步骤,我们实现了一个基本的文件切片上传及断点续传功能。前端通过JavaScript将文件分成多个片段逐一上传,并记录上传进度;后端使用Node.js接收片段并在上传完成后将其合并成一个完整文件。

这个实现方案可以根据实际需求进行优化和调整,例如增加并行上传、错误重试机制、进度通知等功能。本篇博客旨在为您提供一个实现思路以及基础代码,希望对您有所帮助!如果您有任何疑问或建议,欢迎留言讨论。

  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值