IndexedDB + 大文件上传:离线续传的最佳实践

前端大文件上传如何通过 IndexedDB 实现离线续传?

在前端进行大文件上传时,可能会遇到网络中断、页面刷新或用户意外关闭页面的情况。使用 IndexedDB 可以存储已上传的文件切片,实现 断点续传,从而避免重新上传整个文件,提高上传效率。以下是实现方案:


1. 计算文件 Hash,确保文件唯一性

  • 先计算 文件的 Hash (如 md5sha-256),用于标识同一个文件。这样即使用户刷新页面,也能从 IndexedDB 读取已上传的切片。

  • 可使用 Web Workers 计算 Hash,避免阻塞主线程。

  • 采用 SparkMD5 库进行 Hash 计算:

    ini 复制代码
    js
    复制编辑
    import SparkMD5 from "spark-md5";
    
    async function calculateFileHash(file) {
        return new Promise((resolve) => {
            const chunkSize = 10 * 1024 * 1024; // 每次读取 10MB
            const chunks = Math.ceil(file.size / chunkSize);
            const spark = new SparkMD5.ArrayBuffer();
            let currentChunk = 0;
    
            const reader = new FileReader();
            reader.onload = (event) => {
                spark.append(event.target.result);
                currentChunk++;
                if (currentChunk < chunks) {
                    loadNext();
                } else {
                    resolve(spark.end()); // 返回文件 Hash
                }
            };
    
            function loadNext() {
                const start = currentChunk * chunkSize;
                const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
                reader.readAsArrayBuffer(file.slice(start, end));
            }
            loadNext();
        });
    }

2. 切片文件并存入 IndexedDB

  • 使用 IndexedDB 存储已上传的文件切片,避免页面刷新后数据丢失。

  • Dexie.js 库提供了更友好的 API 进行 IndexedDB 操作:

    javascript 复制代码
    js
    复制编辑
    import Dexie from "dexie";
    
    const db = new Dexie("UploadDB");
    db.version(1).stores({
        chunks: "hash,chunkIndex,data", // 以文件 Hash + 分片索引作为唯一标识
    });
    
    async function storeChunk(hash, chunkIndex, chunkData) {
        await db.chunks.put({ hash, chunkIndex, data: chunkData });
    }
  • 切片并存入 IndexedDB

    ini 复制代码
    js
    复制编辑
    async function splitFileAndStore(file, hash, chunkSize = 10 * 1024 * 1024) {
        const totalChunks = Math.ceil(file.size / chunkSize);
    
        for (let i = 0; i < totalChunks; i++) {
            const start = i * chunkSize;
            const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
            const chunk = file.slice(start, end);
            await storeChunk(hash, i, chunk);
        }
    }

3. 检测已上传的切片,实现断点续传

  • 查询 IndexedDB 获取已存储的切片

    javascript 复制代码
    js
    复制编辑
    async function getUploadedChunks(hash) {
        const chunks = await db.chunks.where("hash").equals(hash).toArray();
        return chunks.map((chunk) => chunk.chunkIndex);
    }
  • 上传文件时,跳过已上传的切片

    ini 复制代码
    js
    复制编辑
    async function uploadFile(file) {
        const hash = await calculateFileHash(file);
        const uploadedChunks = await getUploadedChunks(hash);
        const chunkSize = 10 * 1024 * 1024;
        const totalChunks = Math.ceil(file.size / chunkSize);
    
        for (let i = 0; i < totalChunks; i++) {
            if (uploadedChunks.includes(i)) {
                console.log(`Chunk ${i} 已上传,跳过`);
                continue;
            }
    
            const start = i * chunkSize;
            const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
            const chunk = file.slice(start, end);
    
            await uploadChunkToServer(hash, i, chunk); // 上传到服务器
            await storeChunk(hash, i, chunk); // 继续存入 IndexedDB,避免再次上传
        }
    }

4. 服务器合并切片

  • 前端请求合并接口,服务器合并所有切片

    javascript 复制代码
    js
    复制编辑
    async function mergeChunks(hash, totalChunks) {
        await fetch("/api/merge", {
            method: "POST",
            body: JSON.stringify({ hash, totalChunks }),
            headers: { "Content-Type": "application/json" },
        });
    }

5. 页面刷新或断网恢复后自动续传

  • 在页面加载时检查 IndexedDB 中是否有未完成的上传任务

    javascript 复制代码
    js
    复制编辑
    async function resumeUpload(file) {
        const hash = await calculateFileHash(file);
        const uploadedChunks = await getUploadedChunks(hash);
    
        console.log(`恢复上传,已上传的切片:`, uploadedChunks);
        await uploadFile(file); // 继续上传
    }

IndexedDB + 断点续传的优势

支持离线存储 :IndexedDB 允许存储大文件数据,页面刷新不丢失进度。
减少网络开销 :跳过已上传的切片,提高上传效率。
支持断网续传 :网络恢复后自动续传,用户体验更好。
高效存储:IndexedDB 在浏览器本地存储数据,适合大文件分片存储。


进阶优化

  • Web Workers 计算 Hash:避免主线程阻塞,提高性能。
  • Service Worker 结合 PWA:可支持离线存储和自动恢复上传。
  • WebAssembly 计算 Hash:优化性能,减少 JS 计算开销。

IndexedDB + 断点续传 是前端大文件上传的最佳实践之一,可以有效减少带宽消耗,提高用户体验!

总结

步骤 说明
1. 计算 Hash 计算文件 Hash,确保文件唯一性
2. 切片存储 切片并存入 IndexedDB,避免重新上传
3. 断点续传 查询 IndexedDB 记录已上传的部分,跳过已上传的切片
4. 服务器合并 前端通知服务器合并已上传的切片
5. 断网恢复 页面刷新后继续上传未完成的部分

相关推荐
牛客企业服务10 分钟前
2025年AI面试推荐榜单,数字化招聘转型优选
人工智能·python·算法·面试·职场和发展·金融·求职招聘
YongGit12 分钟前
探索 AI + MCP 渲染前端 UI
前端·后端·node.js
慧一居士1 小时前
<script setup>中的setup作用以及和不带的区别对比
前端
RainbowSea1 小时前
NVM 切换 Node 版本工具的超详细安装说明
java·前端
读书点滴1 小时前
笨方法学python -练习14
java·前端·python
Mintopia1 小时前
四叉树:二维空间的 “智能分区管理员”
前端·javascript·计算机图形学
慌糖2 小时前
RabbitMQ:消息队列的轻量级王者
开发语言·javascript·ecmascript
Mintopia2 小时前
Three.js 深度冲突:当像素在 Z 轴上玩起 "挤地铁" 游戏
前端·javascript·three.js
Penk是个码农2 小时前
web前端面试-- MVC、MVP、MVVM 架构模式对比
前端·面试·mvc
MrSkye2 小时前
🔥JavaScript 入门必知:代码如何运行、变量提升与 let/const🔥
前端·javascript·面试