大文件上传的核心技术方案

473 阅读3分钟

大文件上传是一个常见的需求,尤其是在需要上传视频、大型文档或数据集时。由于文件较大,直接上传可能会遇到网络不稳定、服务器限制、内存占用高等问题。因此,大文件上传通常需要特殊的技术方案来优化用户体验和系统性能。以下是大文件上传的常见解决方案和相关技术点:


1. 大文件上传的常见问题

  • 网络不稳定:大文件上传时间长,网络波动可能导致上传失败。
  • 服务器限制:服务器可能对单次请求的大小或时间有限制。
  • 内存占用高:一次性读取大文件到内存中可能导致浏览器或服务器内存溢出。
  • 用户体验差:上传时间长,用户可能误操作或关闭页面。
  • 断点续传需求:上传失败后,用户希望从中断处继续上传,而不是重新开始。

2. 大文件上传的核心技术方案

2.1 分片上传(Chunked Upload)

将大文件分割成多个小块(chunk),分批次上传到服务器。服务器接收完所有分片后,再将这些分片合并成完整的文件。

  • 优点

    • 减少单次请求的压力。
    • 支持断点续传。
    • 提高上传的稳定性。
  • 实现步骤

    1. 前端将文件分割成固定大小的块(如 1MB 或 5MB)。
    2. 为每个分片生成唯一标识(如 MD5 或 SHA-256)。
    3. 依次上传每个分片,并记录上传进度。
    4. 所有分片上传完成后,通知服务器合并文件。
  • 示例代码

    const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
    async function uploadFile(file) {
        const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
        for (let i = 0; i < totalChunks; i++) {
            const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
            const formData = new FormData();
            formData.append('file', chunk);
            formData.append('chunkIndex', i);
            formData.append('totalChunks', totalChunks);
            formData.append('fileId', file.name + '-' + file.size); // 唯一标识
    
            await fetch('/upload', {
                method: 'POST',
                body: formData,
            });
            console.log(`Chunk ${i + 1}/${totalChunks} uploaded`);
        }
        console.log('Upload complete');
    }
    

2.2 断点续传

在上传过程中,记录已上传的分片信息。如果上传中断,用户可以从上次中断的地方继续上传,而不是重新开始。

  • 实现方式

    1. 前端记录已上传的分片索引。
    2. 上传前,先向服务器查询哪些分片已经上传。
    3. 只上传未完成的分片。
  • 示例代码

    async function resumeUpload(file, fileId) {
        const response = await fetch(`/upload-status?fileId=${fileId}`);
        const { uploadedChunks } = await response.json();
    
        const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
        for (let i = 0; i < totalChunks; i++) {
            if (uploadedChunks.includes(i)) continue; // 跳过已上传的分片
    
            const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
            const formData = new FormData();
            formData.append('file', chunk);
            formData.append('chunkIndex', i);
            formData.append('fileId', fileId);
    
            await fetch('/upload', {
                method: 'POST',
                body: formData,
            });
            console.log(`Chunk ${i + 1}/${totalChunks} uploaded`);
        }
        console.log('Upload complete');
    }
    

2.3 文件秒传

如果服务器已经存在相同的文件,可以直接跳过上传,实现“秒传”。

  • 实现方式

    1. 前端计算文件的唯一标识(如 MD5 或 SHA-256)。
    2. 上传前,先向服务器查询该文件是否已存在。
    3. 如果存在,直接返回成功;否则,开始上传。
  • 示例代码

    async function checkFileExists(file) {
        const fileHash = await calculateFileHash(file); // 计算文件哈希
        const response = await fetch(`/check-file?hash=${fileHash}`);
        const { exists } = await response.json();
        return exists;
    }
    
    async function uploadFile(file) {
        const exists = await checkFileExists(file);
        if (exists) {
            console.log('File already exists, skip upload');
            return;
        }
        // 开始分片上传
    }
    

2.4 并发上传

同时上传多个分片,以提高上传速度。

  • 实现方式

    1. 使用 Promise.allWorker 实现并发上传。
    2. 注意控制并发数,避免占用过多资源。
  • 示例代码

    async function uploadFile(file) {
        const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
        const uploadPromises = [];
    
        for (let i = 0; i < totalChunks; i++) {
            const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
            const formData = new FormData();
            formData.append('file', chunk);
            formData.append('chunkIndex', i);
            formData.append('fileId', file.name + '-' + file.size);
    
            uploadPromises.push(
                fetch('/upload', {
                    method: 'POST',
                    body: formData,
                })
            );
        }
    
        await Promise.all(uploadPromises);
        console.log('Upload complete');
    }
    

2.5 前端优化

  • 文件预览:在上传前生成文件的预览(如图片、视频)。
  • 进度显示:实时显示上传进度,提升用户体验。
  • 压缩文件:在上传前对文件进行压缩(如图片、视频)。

3. 服务器端处理

  • 接收分片:服务器需要接收并存储每个分片。
  • 合并文件:在所有分片上传完成后,将分片合并成完整的文件。
  • 文件校验:检查文件的完整性和一致性(如通过 MD5 或 SHA-256)。
  • 清理临时文件:如果上传失败或取消,清理未完成的分片。

4. 总结

大文件上传的核心是分片上传断点续传,通过将文件分割成小块,可以提高上传的稳定性和效率。同时,结合文件秒传、并发上传等技术,可以进一步优化用户体验和系统性能。