如何优雅地处理Web应用中的大文件上传

news2024/12/24 8:53:00

在这里插入图片描述

处理和上传大文件是Web开发中的常见挑战。一方面,我们需要确保应用的性能和响应性不被影响;另一方面,还要保证数据的安全性和完整性。接下来,我们将逐步探讨如何使用文件分片、Web Workers和SHA-256散列计算来解决这些问题。

问题引入:直接上传大文件的弊端

想象一下,当用户尝试上传一个几GB大小的视频文件时,直接上传可能会导致应用卡顿,且在网络不稳定的情况下容易上传失败,用户体验极差。此外,未对文件内容进行验证增加了数据损坏的风险。

思考:有没有更好的方法?

是的,这引出了我们的第一个解决方案:文件分片。通过将大文件切割成小的片段,我们可以逐个上传这些片段。这不仅可以有效管理网络资源,还能在上传过程中断时只重新上传未完成的片段,大大提高了效率和用户体验。我们可以有效地提高上传效率,减少失败的风险,并通过散列计算来验证数据的完整性。

如何在后台处理文件分片?

接下来,我们遇到了第二个问题:如何处理这些文件片段,而不阻塞用户界面?在JavaScript中,所有的文件处理操作默认都在主线程执行,这意味着在文件处理(特别是大文件)时,用户界面可能会出现卡顿。

幸运的是,HTML5引入了Web Workers,允许我们在后台线程中运行脚本,解决了UI阻塞的问题。但这如何实现呢?我们可以将文件分片和计算散列值的任务交给Web Worker,这样主线程就可以专注于处理用户交互,保持应用的响应性。

保证数据完整性的又一挑战:SHA-256散列计算

在文件分片上传的过程中,确保每个片段数据的安全性和完整性至关重要。传统的MD5散列计算已不再能满足安全需求,因此,我们选择使用更加安全的SHA-256散列算法。但这带来了一个新问题:如何在不影响性能的情况下计算每个文件片段的SHA-256散列值?

答案还是使用Web Workers。我们可以在Worker线程中为每个片段计算SHA-256散列值,这样即使是计算密集型的任务也不会影响主线程的性能。

实现步骤

Step 1: 文件分片

用户选择文件后,我们可以根据预设的分片大小,使用JavaScript将文件分割成多个片段。

const CHUNK_SIZE = 5 * 1024 * 1024; // 分片大小为5MB
let chunks = [];
let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
let currentChunk = 0;
let file = document.getElementById('fileInput').files[0];

while (currentChunk < file.size / CHUNK_SIZE) {
    let start = currentChunk * CHUNK_SIZE;
    let end = Math.min(start + CHUNK_SIZE, file.size);
    chunks.push(blobSlice.call(file, start, end));
    currentChunk++;
}

Step 2: 使用Web Workers进行后台处理

为了不阻塞主线程,我们可以将分片处理和SHA-256散列计算的任务交给Web Worker。

// 主线程代码
let worker = new Worker('fileProcessorWorker.js');
worker.postMessage({chunks: chunks});
// Web Worker (fileProcessorWorker.js)
self.addEventListener('message', function(e) {
    let chunks = e.data.chunks;
    chunks.forEach(async (chunk) => {
        // 对每个分片进行SHA-256计算
    });
});

Step 3: 计算分片的SHA-256散列值

在Web Worker中,我们可以利用crypto.subtle.digest方法为每个分片计算SHA-256散列值。

async function calculateSHA256(chunk) {
    const hashBuffer = await crypto.subtle.digest('SHA-256', chunk);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    return hashHex;
}

思考:如何证明主线程未被阻塞?

我们通过在界面上加入动画来直观地展示主线程的响应性。即使后台线程正在进行计算密集型的SHA-256散列计算,主线程的UI动画依然能够流畅运行,证明了我们的实现方法有效。

绝对可以。以下是在文章中讨论的技术方案的完整代码实现,供读者参考。

完整代码实现

HTML 文件 (index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>大文件分片处理</title>
    <script src="./spark-md5.min.js"></script> <!-- 假设使用SHA-256, 这里仅作示例 -->
    <style>
        .cube {
            width: 50px;
            height: 50px;
            background-color: #3498db;
            position: relative;
            transform-style: preserve-3d;
            animation: moveCube 1s linear infinite alternate;
        }

        @keyframes moveCube {
            0% {
                transform: translateX(0);
            }
            100% {
                transform: translateX(100%);
            }
        }
    </style>
</head>
<body>
    分块大小:<span id="chunkSize"></span>
    <input type="file" id="fileInput" multiple />
    <button onclick="handleFiles()">处理文件</button>
    <div class="cube"></div>
    <script src="main.js"></script>
</body>
</html>

主线程 JavaScript 文件 (main.js)

const CHUNK_SIZE = 5 * 1024 * 1024; // 定义分片大小为5MB
let chunkSize = document.getElementById('chunkSize');
chunkSize.innerHTML = formatSizeUnits(CHUNK_SIZE);

// 格式化显示文件大小
function formatSizeUnits(bytes) {
    if (bytes < 1024) return bytes + ' B';
    else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
    else if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
    else return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
}

let fileInput = document.getElementById('fileInput');

function handleFiles() {
    let files = fileInput.files;
    if (files.length === 0) {
        alert('请先选择文件');
        return;
    }
    for (let i = 0; i < files.length; i++) {
        processAllChunks(files[i], CHUNK_SIZE);
    }
}

async function processAllChunks(file, chunkSize) {
    // 分片逻辑省略,详见上文
}

Web Worker 文件 (worker.js)

// 导入spark-md5.min.js库以进行SHA-256计算,这里用于示例,实际应用中应该使用crypto API
importScripts('spark-md5.min.js'); // 假定更改为适合SHA-256的库

onmessage = function (event) {
    const { file, totalChunks, chunkSize } = event.data;
    // 分片处理和SHA-256散列值计算的逻辑,详见上文
};

请注意,这里的代码仅作为示例提供,并未包含所有细节。例如,processAllChunks函数和worker.js中的具体实现需要根据前文的描述进行完善。此外,原始代码示例中使用了spark-md5库进行MD5计算,但在实际实现中,你应使用Web Crypto API进行SHA-256计算,以确保数据的安全性和完整性。

偷懒想一键复制?
worker.js

// 导入 spark-md5.min.js 库以进行MD5计算
importScripts('spark-md5.min.js');

onmessage = function (event) {
    const { file, totalChunks, chunkSize } = event.data;
    let completedChunks = 0;
    var startTime = new Date().getTime();

    const processChunk = (currentChunk) => {
        const start = currentChunk * chunkSize;
        const end = Math.min(start + chunkSize, file.size);
        const chunk = file.slice(start, end);

        return new Promise((resolve, reject) => {
            const reader = new FileReader();

            reader.onload = async function (e) { // 将事件处理器标记为async
                try {
                    const data = e.target.result;
                    const hashHex = await calculateSHA256(data); // 等待异步SHA-256计算完成

                    resolve({
                        chunkIndex: currentChunk,
                        start,
                        end,
                        hashHex, // 使用计算得到的散列值
                        message: `Chunk ${currentChunk + 1}/${totalChunks} processed.`,
                    });
                } catch (error) {
                    reject(error);
                }
            };

            reader.onerror = () => reject(reader.error);
            reader.readAsArrayBuffer(chunk);
        });
    };

    async function calculateSHA256(data) {
        const hashBuffer = await crypto.subtle.digest('SHA-256', data);
        const hashArray = Array.from(new Uint8Array(hashBuffer)); // 转换为字节数组
        const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // 转换为十六进制字符串
        return hashHex;
    }

    const updateProgress = () => {
        completedChunks++;
        if (completedChunks === totalChunks) {
            postMessage({ message: (new Date().getTime() - startTime)+'ms,All chunks processed.', allChunksProcessed: true });
        }
    };

    const promises = Array.from({ length: totalChunks }, (_, i) => processChunk(i));

    Promise.all(promises).then((results) => {
        results.forEach((result) => {
            // Ideally, instead of posting message for each chunk, aggregate results or update UI at key intervals
            postMessage(result);
            updateProgress();
        });
    }).catch((error) => {
        postMessage({ error: error.message });
    });
};
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>大文件分片处理</title>
    <script src="./spark-md5.min.js"></script>
    <style>
        .cube {
            width: 50px;
            height: 50px;
            background-color: #3498db;
            position: relative;
            transform-style: preserve-3d;
            animation: moveCube 1s linear infinite alternate;
        }

        @keyframes moveCube {
            0% {
                transform: translateX(0);
            }
            100% {
                transform: translateX(100%);
            }
        }
    </style>
</head>

<body>
    分块大小:<span id="chunkSize"></span>
    <input type="file" id="fileInput" multiple />
    <button onclick="handleFiles()">处理文件</button>
    <div class="cube"></div>
    <script>
        const CHUNK_SIZE = 5 * 1024 * 1024;
        let chunkSize = document.getElementById('chunkSize');
        chunkSize.innerHTML = formatSizeUnits(CHUNK_SIZE);
        // 格式化显示文件大小
        function formatSizeUnits(bytes) {
            if (bytes < 1024) {
                return bytes + ' B';
            } else if (bytes < 1024 * 1024) {
                return (bytes / 1024).toFixed(2) + ' KB';
            } else if (bytes < 1024 * 1024 * 1024) {
                return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
            } else {
                return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
            }
        }
        let fileInput = document.getElementById('fileInput');

        async function processAllChunks(file,chunkSize) {
            const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
            const worker = new Worker('worker.js');

            worker.postMessage({ file, totalChunks , chunkSize});

            worker.onmessage = function (event) {
                const chunkResult = event.data;
                if(chunkResult['allChunksProcessed'] && chunkResult['message']){
                    console.log(chunkResult['message']);
                }
                // 在这里可以处理已经完成的分片数据,例如上传到服务器
                // chunkResult 包含了分片的信息和处理后的数据
                // chunkResult.data 就是分片的处理后的数据
            };

            worker.onerror = function (error) {
                console.error('处理分片时发生错误:', error);
            };
        }

        function handleFiles() {
            let files = fileInput.files;

            if (files.length === 0) {
                alert('请先选择文件');
                return;
            }

            for (let i = 0; i < files.length; i++) {
                processAllChunks(files[i], CHUNK_SIZE);
            }
        }
    </script>
</body>

</html>

总结

通过文件分片、后台计算和SHA-256散列值验证,我们不仅优化了大文件的处理和上传过程,还确保了数据的安全性和完整性。以上实现方式展示了现代Web技术如何解决传统问题,提升用户体验,同时保障数据处理的效率和安全。

希望这篇博客能够帮助你理解和实现在Web应用中处理大文件的最佳实践。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1501215.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构---复杂度(1)

1.时间复杂度 衡量算法的好坏&#xff0c;使用大写的o来表示时间复杂度&#xff0c;通俗的讲&#xff0c;就是一个算法执行的次数&#xff1b; 时间复杂度就是数学里面的函数表达式&#xff1b;本质上是一个函数&#xff1b; 下面举几个例子&#xff1a; (1)这里的执行次数是…

【RISC-V 指令集】RISC-V 向量V扩展指令集介绍(二)-向量元素到向量寄存器状态的映射

1. 引言 以下是《riscv-v-spec-1.0.pdf》文档的关键内容&#xff1a; 这是一份关于向量扩展的详细技术文档&#xff0c;内容覆盖了向量指令集的多个关键方面&#xff0c;如向量寄存器状态映射、向量指令格式、向量加载和存储操作、向量内存对齐约束、向量内存一致性模型、向量…

测试人员需要掌握的 k8s 知识

前言 kubernetes 在容器编排领域已经形成统治地位&#xff0c;不管是开发、运维还是测试&#xff0c;掌握 kubernetes 都变得非常有必要。这篇文章通过 minikube 搭建一个简单的 kubernetes 运行环境。 安装虚拟机 主流的操作系统都支持 kubernetes&#xff0c;但是 windows…

禁止使用搜索引擎,你了解吗?

员工A&#xff1a;“我今天想搜索的时候&#xff0c;用不了浏览器了&#xff0c;你能用么&#xff1f;” 员工B&#xff1a;“不知道啊我试一下啊” “也不行” 员工C&#xff1a;“为什么啊&#xff1f;” 针对上述对话&#xff0c;我们不禁思考&#xff1a; 公司为什么禁…

一代影后阮玲玉:国际妇女节这天自杀,让很多人都感到震惊和惋惜!

一代影后阮玲玉:国际妇女节这天自杀&#xff0c;让很多人都感到震惊和惋惜&#xff01; 说起阮玲玉&#xff0c;那可真是咱们中国电影史上的一位大明星啊。#李秘书讲写作# 今日说起她演过的电影和成就&#xff0c;嘿&#xff0c;说出来可都是响当当的。 阮玲玉&#xff0c;原名…

算法刷题day25:多路归并

目录 引言概念一、鱼塘钓鱼二、技能升级三、序列 引言 关于这个多路并归蓝桥杯考的不是很多&#xff0c;如果要出的话&#xff0c;可能模型都会差不多&#xff0c;因为不会出太难的题&#xff0c;难题基本上都是贪心、DP之类的&#xff0c;所以好好刷题刷熟练就行了&#xff0…

探讨2024年AI辅助研发的趋势

一、引言 随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经成为当今时代最具变革性的技术之一。AI的广泛应用正在重塑各行各业&#xff0c;其中&#xff0c;AI辅助研发作为科技和工业领域的一大创新热点&#xff0c;正引领着研发模式的深刻变革。从医药…

安卓7原生相机切到视频崩溃

目录 1、查看日志 2、分析日志、提取重点 3、寻找解决方法 author daisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 daisy.skye_嵌入式,Linux,Qt-CSDN博客daisy.skye擅长嵌入式,Linux,Qt,等方面的知识https://blog.csdn.net/qq_40715266?typeblog 1、查看日志 由于安…

帮管客 CRM jiliyu SQL注入漏洞复现

0x01 产品简介 帮管客CRM是一款集客户档案、销售记录、业务往来等功能于一体的客户管理系统。帮管客CRM客户管理系统,客户管理,从未如此简单,一个平台满足企业全方位的销售跟进、智能化服务管理、高效的沟通协同、图表化数据分析帮管客颠覆传统,重新定义企业管理系统。 …

Redis核心数据结构之字典(二)

字典 解决键冲突 当有两个或以上数量的键被分配到了一个哈希表数组的同一个索引上面&#xff0c;我们称这些键发生了冲突(collision)。 Redis的哈希表使用链地址法(separate chaining)来解决键冲突&#xff0c;每个哈希表节点都有一个next指针&#xff0c;多个哈希表节点可以…

python三剑客之一——Numpy

温故而知新&#xff0c;借着工作需要用到Numpy的机会重新学习一遍Numpy。 Numpy是一个运行速度非常快的数学库&#xff0c;主要用于数组计算&#xff0c;包含如下&#xff1a; 一个强大的N维数组对象ndarray【Nd&#xff08;Dimension维度&#xff09;array】 广播功能函数 整…

九型人格测试,9号和平型人格的职业分析业?

九型人格测试&#xff0c;人格分为9个类型&#xff0c;而和平型人格&#xff0c;也就是我们所说的和平使者&#xff0c;为人非常善良&#xff0c;十分地体贴&#xff0c;能够体谅到对方的难处&#xff0c;并且会大度地原谅对方的错误。愿意去了解对方&#xff0c;但却没有时间在…

支小蜜校园防欺凌系统听到声音之后会自动识别吗

在校园安全领域&#xff0c;特别是在预防和应对欺凌问题上&#xff0c;校园防欺凌系统作为新兴的技术应用&#xff0c;正在受到越来越多的关注和探索。那么当这样的系统听到声音之后&#xff0c;它是否能够自动识别并作出相应反应呢&#xff1f;本文将围绕这一问题展开探讨。 …

JAVA虚拟机实战篇之内存调优[4](内存溢出问题案例)

文章目录 版权声明修复问题内存溢出问题分类 分页查询文章接口的内存溢出问题背景解决思路问题根源解决思路 Mybatis导致的内存溢出问题背景问题根源解决思路 导出大文件内存溢出问题背景问题根源解决思路 ThreadLocal占用大量内存问题背景问题根源解决思路 文章内容审核接口的…

编译内核错误 multiple definition of `yylloc‘

编译内核错误 # make ARCHarm CROSS_COMPILEarm-mix410-linux- uImageHOSTLD scripts/dtc/dtc /usr/bin/ld: scripts/dtc/dtc-parser.tab.o:(.bss0x10): multiple definition of yylloc; scripts/dtc/dtc-lexer.lex.o:(.bss0x0): first defined here collect2: error: ld ret…

c++提高部分

c++提高部分 这部分主要涉及泛型编程和STL技术 1. 模版 1.1 模版的概念 模版就是通用的模具,大大提高复用性,但需要根据需求改动一些东西 1.2 函数模版 c++另一种编程思想为泛型编程,主要利用的技术就是模版c++提供两种模版机制:函数模板和类模板1.2.1 函数模板语法 …

C++ 篇 数组

数组是含有多个数据项的数据结构&#xff0c;并且这些数据项都具有相同的数据类型。这些数据项称为数组的元素&#xff0c;我们可以根据元素在数组中的位置来选取元素。 最简单的数组就是一维数组。数组元素在内存中是依次排列的&#xff0c;如下图所示&#xff1a; 声明一个…

js【详解】原型 vs 原型链

原型 每个 class 都有显示原型 prototype每个实例都有隐式原型_proto_实例的_proto_指向对应 class 的 prototype 如下范例&#xff1a; class Student 创建了 实例 xialuo 获取属性 xialuo.name 或执行方法 xialuo.sayhi()时&#xff0c;先在自身属性和方法寻找&#xff0…

1.5如何缓解图像分类任务中训练数据不足带来的问题?

1.5 图像数据不足时的处理方法 场景描述 在机器学习中&#xff0c;绝大部分模型都需要大量的数据进行训练和学习(包括有监督学习和无监督学习)&#xff0c;然而在实际应用中经常会遇到训练数据不足的问题。 比如图像分类&#xff0c;作为计算机视觉最基本的任务之一&#xff0…

C++进阶之路---继承(二)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、继承与友元 友元关系不能继承&#xff0c;也就是说基类友元不能访问子类私有和保护成员。 class Student; class Per…