大文件分片上传方案-前端

一、方案概述

大文件分片上传方案旨在解决大文件上传过程中的性能问题、网络中断问题以及用户体验问题。通过将大文件分割成多个小分片,分别上传后再在服务器端合并,可以有效提高上传效率和成功率。

二、具体实现步骤

(一)文件选择与初步处理

  1. HTML 文件选择 :使用<input type="file">元素,让用户选择本地要上传的大文件。

  2. 获取文件对象 :通过 JavaScript 监听文件选择事件,获取用户选择的文件对象。

<input type="file" id="fileInput">
document.getElementById('fileInput').addEventListener('change', (e) => {
    const file = e.target.files[0];
    if (file) {
        // 后续分片上传操作
    }
});

(二)文件分片

  1. 定义分片大小 :通常设置为 5MB 左右,可以根据实际需求调整。

const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
  1. 计算分片数量 :根据文件大小和分片大小计算总分片数。

const chunkCount = Math.ceil(file.size / CHUNK_SIZE);
  1. 分片处理 :将文件按分片大小分割成多个小块。

for (let i = 0; i < chunkCount; i++) {
    const start = i * CHUNK_SIZE;
    const end = Math.min(start + CHUNK_SIZE, file.size);
    const chunk = file.slice(start, end);
    // 上传分片
}

(三)分片上传

  1. 并发上传控制 :获取设备的线程数量,合理控制并发上传的线程数。

const THREAD_COUNT = navigator.hardwareConcurrency || 4;
const threadChunkCount = Math.ceil(chunkCount / THREAD_COUNT);
  1. 多线程上传实现 :使用 Web Workers 实现多线程上传,提高上传效率。

for (let i = 0; i < THREAD_COUNT; i++) {
    const worker = new Worker('./worker.js', {
        type: 'module',
    });
    let start = i * threadChunkCount;
    let end = Math.min((i + 1) * threadChunkCount, chunkCount);
    worker.postMessage({
        file,
        start,
        end,
        CHUNK_SIZE,
    });
    worker.onmessage = (e) => {
        // 处理上传结果
    };
}

(四)断点续传

  1. 记录上传进度 :在本地存储记录已上传的分片信息。

localStorage.setItem('uploadProgress', JSON.stringify({ file: file.name, uploaded: [] }));
  1. 恢复上传 :在上传中断后,读取本地存储的进度,跳过已上传的分片。

const uploadProgress = JSON.parse(localStorage.getItem('uploadProgress'));
if (uploadProgress && uploadProgress.file === file.name) {
    for (let i = 0; i < uploadProgress.uploaded.length; i++) {
        // 跳过已上传的分片
    }
}

(五)文件合并

  1. 服务器合并 :服务器在接收到所有分片后,按照分片索引顺序合并文件。

  2. 前端确认 :前端在所有分片上传完成后,发送请求到服务器确认文件合并完成。

// 前端示例
fetch('/merge', {
    method: 'POST',
    body: JSON.stringify({ fileName: file.name }),
});

(六)上传进度与反馈

  1. 实时进度更新 :在上传过程中实时更新上传进度,并提供用户友好的界面反馈。

// 监听上传进度事件
worker.onmessage = (e) => {
    const progress = (uploadedChunks / chunkCount) * 100;
    console.log(`Upload progress: ${progress}%`);
};
  1. 错误处理与重试 :添加重试机制,处理上传过程中可能发生的网络错误。

let retryCount = 0;
const maxRetries = 3;
async function uploadChunk(chunk, index) {
    try {
        await fetch('/upload', { method: 'POST', body: chunk });
    } catch (error) {
        if (retryCount < maxRetries) {
            retryCount++;
            await uploadChunk(chunk, index);
        } else {
            console.error('Upload failed after multiple retries');
        }
    }
}

三、完整代码示例

(一)main.js

import { cutfile } from './cutfile.js';

document.getElementById('fileInput').addEventListener('change', (e) => {
    const file = e.target.files[0];
    if (file) {
        console.time('cutfile');
        cutfile(file).then((chunks) => {
            console.timeEnd('cutfile');
            console.log(chunks);
        });
    }
});

(二)cutfile.js

const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB 每个分片的大小

export async function cutfile(file) {
    const chunkCount = Math.ceil(file.size / CHUNK_SIZE); // 计算分片的数量
    const result = [];
    for (let i = 0; i < chunkCount; i++) {
        const prom = createChunk(file, i, CHUNK_SIZE);
        result.push(prom);
    }
    const THREAD_COUNT = navigator.hardwareConcurrency || 4; // 获取当前设备的线程数量
    const threadChunkCount = Math.ceil(chunkCount / THREAD_COUNT); // 计算每个线程处理的分片数量
    for (let i = 0; i < THREAD_COUNT; i++) {
        const worker = new Worker('./worker.js', {
            type: 'module',
        });
        let start = i * threadChunkCount; // 每个线程的起始分片下标
        let end = Math.min((i + 1) * threadChunkCount, chunkCount); // 每个线程的结束分片下标
        worker.postMessage({
            file,
            start,
            end,
            CHUNK_SIZE,
        });
        worker.onmessage = (e) => {
            result.push(...e.data); // 将每个线程处理的分片结果添加到结果数组中
        };
        worker.onerror = (e) => {
            console.error('Worker error:', e);
        };
    }
    return result;
}

(三)createchunk.js

import { SparkMD5 } from './sparkmd5.js';

export function createChunk(file, index, chunkSize) {
    return new Promise((resolve) => {
        const start = index * chunkSize;
        const end = start + chunkSize;
        const spark = new SparkMD5.ArrayBuffer();
        const fileReader = new FileReader();
        const blob = file.slice(start, end);
        fileReader.onload = () => {
            spark.append(fileReader.result);
            resolve({
                start,
                end,
                index,
                hash: spark.end(),
                blob,
            });
        };
        fileReader.readAsArrayBuffer(blob);
    });
}

(四)worker.js

import { createChunk } from './createchunk.js';

onmessage = (e) => {
    const { file, start, end, CHUNK_SIZE } = e.data;
    const result = [];
    for (let i = start; i < end; i++) {
        const prom = createChunk(file, i, CHUNK_SIZE);
        result.push(prom);
    }
    Promise.all(result).then((chunks) => {
        postMessage(chunks);
    });
};

四、总结与优化建议

(一)总结

本方案通过文件选择、分片处理、并发上传、断点续传、文件合并等步骤,实现了一个完整的大文件分片上传功能。利用 Web Workers 实现多线程处理,提高了上传效率和用户体验。

(二)优化建议

  1. 动态调整分片大小 :根据网络状况和设备性能动态调整分片大小,以达到最佳上传效果。

  2. 增强错误处理机制 :增加更详细的错误日志记录和用户提示,方便排查问题。

  3. 性能优化 :对代码进行进一步优化,减少不必要的计算和内存占用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

下面我们举个栗子说明一下

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值