侧边栏壁纸
  • 累计撰写 47 篇文章
  • 累计创建 2 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

大文件上传方案

核心在于分片上传断点续传

  1. 分片上传 (Chunked Upload)

    将文件按固定大小(如 2MB 或 5MB)切割成多个 Blob 分片,为每个分片生成唯一标识,并发或顺序上传

    流程:
    前端计算文件唯一标识(如 MD5/SHA-1,可使用 spark-md5 库),用于服务端判断是否为同一文件。
    将文件分片,记录总分片数、当前分片索引。
    为每个分片单独发起 POST 或 PUT 请求上传。
    服务端接收分片并暂存(如磁盘或 OSS)。
    全部分片上传完毕后,前端通知服务端进行合并。

  2. 断点续传 (Resumable Upload)

    原理: 在上传过程中记录上传进度,即使中断(刷新页面、网络断开),再次上传时可以从已上传的部分继续,而无需重新上传全部。
    实现:
    前端: 使用 localStorage 或 IndexedDB 持久化存储已成功上传的分片索引列表。续传时,先向服务器查询该文件已上传的分片列表(秒传和续传判断),然后只上传剩余分片。
    服务端: 需要提供两个接口:
    verify 接口: 接收文件哈希值,返回已上传的分片索引列表。
    merge 接口: 在所有分片上传完成后,触发合并操作。

  3. 并发控制

    同时发起大量 HTTP 请求可能会阻塞浏览器。需要限制并发数(如使用 p-limit、async-pool 等库),维护一个请求队列。

具体实现步骤
  1. 文件选择与读取
const file = document.querySelector('input[type=file]').files[0];
  1. 生成文件哈希(用于标识文件)
import SparkMD5 from 'spark-md5';
async function calculateFileHash(file) {
    return new Promise((resolve) => {
        const spark = new SparkMD5.ArrayBuffer();
        const reader = new FileReader();
        reader.readAsArrayBuffer(file);
        reader.onload = (e) => {
            spark.append(e.target.result);
            const hash = spark.end();
            resolve(hash);
        };
    });
}
const fileHash = await calculateFileHash(file);
  1. 文件分片
const chunkSize = 5 * 1024 * 1024; // 5MB
const chunkList = [];
let start = 0;
while (start < file.size) {
    const chunk = file.slice(start, start + chunkSize);
    chunkList.push({
        chunk,
        hash: fileHash + '_' + start, // 分片唯一标识
        index: start / chunkSize,
    });
    start += chunkSize;
}
  1. 验证与获取已上传列表
const { data } = await axios.post('/api/verify', {
    fileHash,
    fileName: file.name,
    chunkSize,
});
const uploadedList = data.uploadedList || []; // 服务端返回的已上传分片hash列表
  1. 上传分片(含并发控制)
const maxConcurrent = 3; // 最大并发数
const uploadChunks = async (chunks, uploadedList) => {
    const pool = [];
    const tasks = chunks.filter(chunk => !uploadedList.includes(chunk.hash));

    for (let i = 0; i < tasks.length; i++) {
        const formData = new FormData();
        formData.append('chunk', tasks[i].chunk);
        formData.append('hash', tasks[i].hash);
        formData.append('fileHash', fileHash);
        formData.append('index', tasks[i].index);

        const task = axios.post('/api/upload', formData, {
            onUploadProgress: (e) => {
                // 更新该分片及总进度条
            }
        }).then(() => {
            // 上传成功,可从池中移除
            pool.splice(pool.indexOf(task), 1);
        });
        pool.push(task);

        if (pool.length >= maxConcurrent) {
            await Promise.race(pool); // 等待池中任意一个任务完成
        }
    }
    await Promise.all(pool); // 等待所有剩余任务完成
};
  1. 通知合并
await axios.post('/api/merge', {
    fileHash,
    fileName: file.name,
    chunkSize,
    totalChunks: chunkList.length,
});
console.log('上传并合并成功!');
服务端(Node.js 示例)关键职责

/api/verify:
根据 fileHash 检查目标文件是否已存在(实现秒传)。
检查暂存目录,返回已上传的分片标识列表。
/api/upload:
接收分片文件,以 fileHash 为名创建临时目录,以分片 hash 或 index 为名保存分片。
/api/merge:
根据 fileHash 找到临时目录,按 index 顺序读取所有分片,通过文件流(fs.createWriteStream)合并成最终文件。
合并后删除临时分片。

简单流程
  1. 用户选择文件 → 前端计算 hash。
  2. 调用 /check 接口,返回已上传分片列表。
  3. 将未上传分片并发上传至 /upload-chunk。
  4. 全部分片上传后,调用 /merge 合并。
  5. 返回最终文件 URL。
进阶优化

Web Workers: 将计算文件哈希的耗时操作放在 Worker 线程,避免页面卡顿。
HTTP/2: 利用其多路复用特性,提升并发上传效率。

第三方库

simple-uploader.js(基于 Flow.js)
resumable.js
vue-simple-uploader(Vue 组件)

0

评论区