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

相关推荐
未来之窗软件服务18 小时前
一体化系统(九)智慧社区综合报表——东方仙盟练气期
大数据·前端·仙盟创梦ide·东方仙盟·东方仙盟一体化
陈天伟教授21 小时前
人工智能训练师认证教程(2)Python os入门教程
前端·数据库·python
信看1 天前
NMEA-GNSS-RTK 定位html小工具
前端·javascript·html
Tony Bai1 天前
【API 设计之道】04 字段掩码模式:让前端决定后端返回什么
前端
爱吃大芒果1 天前
Flutter 主题与深色模式:全局样式统一与动态切换
开发语言·javascript·flutter·ecmascript·gitcode
苏打水com1 天前
第十四篇:Day40-42 前端架构设计入门——从“功能实现”到“架构思维”(对标职场“大型项目架构”需求)
前端·架构
king王一帅1 天前
流式渲染 Incremark、ant-design-x markdown、streammarkdown-vue 全流程方案对比
前端·javascript·人工智能
苏打水com1 天前
第十八篇:Day52-54 前端跨端开发进阶——从“多端适配”到“跨端统一”(对标职场“全栈化”需求)
前端
Bigger1 天前
后端拒写接口?前端硬核自救:纯前端实现静态资源下载全链路解析
前端·浏览器·vite