如何实现大文件的切片上传与断点续传

背景

    在我们实际开发中,难免会遇到需要使用上传文件的功能。比较小的文件我们也许不会考虑文件的切片上传,一旦涉及文件比较大,例如几十、上百MB,甚至是GB级别的文件,这种情况下又将如何呢?传输时间将会很长,一旦中途传输产生错误,之前传输的内容都将失效,从头再来。

功能设计

    首先思考是两个主体功能,即切片上传与断点续传。在上传过程中肯定会占用用户大量的带宽,再考虑用户再上传中可能会临时处理其他问题,需要暂停上传,于是我们思考合计需要增加暂停与继续上传功能。此场景下,上传需要一定量的时间,所以还要增加上传进度的功能。

    如何实现文件切片呢?首先,我们可以通过 input[type=file] 获取用户勾选的文件,它是包含一系列 File 对象的 FileList 对象。这里我们取 files[0] 即可。在 MDN 的介绍中可以了解到,File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。比如说, FileReader, URL.createObjectURL(), createImageBitmap() (en-US), 及 XMLHttpRequest.send() 都能处理 BlobFile。这里我们可以按照一定大小,将选择的文件切成一系列的 chunks ,然后再以此上传,待上传完毕后,由服务端将 chunks 合并完整文件。

    如何实现断点续传呢?由于服务端无法感知到我们上传的是什么文件,将文件名发给服务端验证也不可靠,因为你无法确定服务端是否有同名文件。在此基础上,我们可以先计算出文件的 MD5 值交给服务端去查询是否有相同的文件,文件是否传输完毕,已经上传了多少 chunks 。这时可能有小伙伴提出来 MD5 已经发现有碰撞,用它验证文件的唯一性有一定概率出错。这时我们可以考虑同时搭配其他方法共同验证,比如 SHA1 ,使用 MD5 + SHA1 已经足够确保文件的唯一性。这里我们主要讲思路,后面的例子就统一使用 MD5 来举例。

    那又如何实现暂停与继续上传呢?这个就比较简单了。上传 chunks 的时候,我们通常使用递归或循环来做。由此想到可以设置一个标识位,当标识为 true 时就暂停后续上传,当点击继续时,重置标识位,同时重新调用上传逻辑。

功能实现

    首先我们需要安装 JavaScript-MD5 到项目中。

shell 复制代码
npm install blueimp-md5

    页面上需要一个文件选择框,一个功能按钮。

html 复制代码
<input type="file" name="file" id="file">
<button id="upload" onClick="upload()">upload</button>

    上传逻辑。

js 复制代码
import md5 from 'blueimp-md5';

let md5hash = '';
let flag = false;
const chunkSize = 1 * 1024 * 1024;

const input = document.getElementById('file');
const file = input.files[0];

const fr = new FileReader();

fr.addEventListener(
    "load",
    () => {
      md5hash = md5(fr.result);
    },
    false,
);

fr.readAsText(file);

const verifyFile = () => {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('POST', 'https://example.com/files/verify', true);
        xhr.onload = (response) => {
            resolve(response);
        };
        xhr.send(md5hash);
    });
};

const uploadChunk = (fileChunk, chunkName) => {
    return new Promise((resolve, reject) => {
        const formData = new FormData()
        formData.append('file', fileChunk, chunkName);

        const xhr = new XMLHttpRequest();
        xhr.open('POST', 'https://example.com/files/upload', true);
        xhr.onload = (response) => {
            resolve(response);
        };
        xhr.send(formData);
    });
};

const upload = async () => {
    const response = await verifyFile();
    const { data }  = response ?? {};
    const { chunkIndex, done } = data ?? {};
    const totalChunks = parseInt(file.size / chunkSize);
    let index = chunkIndex || 0;

    while (!done && !flag && index < totalChunks) {
        count chunk = file.slice(chunkSize * index, chunkSize);
        const chunkName = file.name + 'chunk_' + index;
        
        await uploadChunk(chunk, chunkName);
    };
};

参考文献或文章

相关推荐
IMPYLH14 小时前
HTML 的 <p> 元素
前端·javascript·html
leoZ23114 小时前
第 4 篇:布局骨架——页面壳、栅格、卡片
前端·javascript·vue.js·opencv·计算机视觉·数据挖掘·语音识别
何以解忧,唯有..15 小时前
Vue 3 模板语法详解:从插值到指令的完整指南
前端·javascript·vue.js
EasyGBS15 小时前
从接入到稳定播放:无插件直播H5视频流媒体播放器EasyPlayer.js如何撑起Web端流媒体
开发语言·前端·javascript
Carl_奕然15 小时前
【智能体】Agent的四种设计模式之:React(2026最新版)
javascript·人工智能·python·react.js·设计模式·语言模型
名为沙丁鱼的猫72915 小时前
React/JSX 编码规范
前端·javascript·react.js
Highcharts.js15 小时前
甘特图纵向任务、横向时间显示图表可视化开发|Highcharts甘特图示例
开发语言·前端·javascript·可视化·甘特图·数据可视化·highcharts
烬羽16 小时前
给 Agent 一个检索式记忆:把对话历史存进 Milvus,该记的都记得
javascript·数据库·agent
磐链科技16 小时前
区块链开发入门实战:用 Hardhat 与 Ethers.js 搭建你的第一个 DApp
开发语言·javascript·区块链