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. 断网恢复 页面刷新后继续上传未完成的部分

相关推荐
林深现海6 小时前
Jetson Orin nano/nx刷机后无法打开chrome/firefox浏览器
前端·chrome·firefox
黄诂多6 小时前
APP原生与H5互调Bridge技术原理及基础使用
前端
前端市界6 小时前
用 React 手搓一个 3D 翻页书籍组件,呼吸海浪式翻页,交互体验带感!
前端·架构·github
文艺理科生6 小时前
Nginx 路径映射深度解析:从本地开发到生产交付的底层哲学
前端·后端·架构
千寻girling6 小时前
主管:”人家 Node 框架都用 Nest.js 了 , 你怎么还在用 Express ?“
前端·后端·面试
C澒7 小时前
Vue 项目渐进式迁移 React:组件库接入与跨框架协同技术方案
前端·vue.js·react.js·架构·系统架构
xiaoxue..7 小时前
合并两个升序链表 与 合并k个升序链表
java·javascript·数据结构·链表·面试
清山博客7 小时前
OpenCV 人脸识别和比对工具
前端·webpack·node.js
要加油哦~7 小时前
AI | 实践教程 - ScreenCoder | 多agents前端代码生成
前端·javascript·人工智能
程序员Sunday7 小时前
说点不一样的。GPT-5.3 与 Claude Opus 4.6 同时炸场,前端变天了?
前端·gpt·状态模式