作者:watermelo617
涉及领域:Vue、SpingBoot、Docker、LLM、python
-------------------------------------------------------------------------------------------------------------------------
--------------------------温柔地对待温柔的人,包容的三观就是最大的温柔。--------------------------
-------------------------------------------------------------------------------------------------------------------------
编辑
分片上传技术全解析:原理、优势与应用(含简单实现源码)
一、什么是分片上传
分片上传(Chunked Upload)是将大文件分成多个较小的部分(分片)来逐个上传到服务器。上传完成后,服务器将这些分片重新组装成原始文件。这个过程通常包括以下几个步骤:
- 分片:文件被切割成多个小的片段,每个片段的大小通常是预定义的。
- 上传:每个分片被单独上传到服务器。上传过程中,通常会附带分片的索引和其他元数据。
- 组装:服务器接收到所有分片后,将它们按正确的顺序重新组装成完整的文件。
- 确认:完成组装后,服务器可以返回一个确认响应,表示文件上传成功。
编辑
二、分片上传解决了什么问题
分片上传是一个有效的处理大文件上传问题的方案,它通过将文件分割为小片段来提高上传的可靠性和效率,并确保数据的完整性。在网络环境环境不好,存在需要重传风险的场景,分片上传能有效提升上传的体验。
其具体优势在于:
- 大文件上传的稳定性:对于大文件,直接上传可能会因为网络问题、超时等导致上传失败。分片上传可以在单个分片上传失败时仅重新上传失败的分片,而不是整个文件,提高了上传的成功率。
- 减少内存使用:分片上传可以将大文件拆分为较小的片段,这样可以减少内存消耗并优化上传性能。
- 断点续传:分片上传支持断点续传。即使上传过程中发生了中断,用户可以从中断的位置继续上传,不必从头开始。
- 负载均衡:可以在多个服务器之间分配分片上传任务,提高系统的负载均衡能力。
三、分片上传的本质
3.1、分片上传的本质意义
分片上传的本质就是将大文件分割成多个较小的部分,逐个上传到服务器,然后在服务器端将这些部分重新组合成完整的文件。这种方法不仅解决了大文件上传的问题,还能优化网络带宽和提高上传效率。
大多数情况下,服务器会负责将接收到的分片重新组装成完整的文件。服务器通常会根据分片的索引顺序将它们拼接起来。在某些情况下,客户端可能会将所有分片上传到服务器后,由客户端自行处理合并操作(通常较少见)。
3.2、分片上传的风险与规避方式
①网络问题
网络中断可能导致某些分片上传失败。为了应对这种情况,可以实现重试机制和断点续传功能。
②服务器端错误
服务器在接收和组装分片时可能会发生错误。应确保服务器有适当的错误处理和日志记录机制。
③文件完整性校验
为了确保文件在上传和组装过程中没有发生损坏,通常会计算文件的哈希值,并在上传完成后进行校验。在第四部分案例代码中使用了SparkMD5
来计算文件的MD5哈希值,以确保文件的一致性和完整性。
3.3、分片上传中的重试机制与断点续传
这些技术可以结合使用,以提高上传的鲁棒性。重试机制确保分片上传的稳定性,断点续传支持上传中断后的恢复。
①重试机制
重试机制是在上传过程中,如果遇到网络问题、服务器错误或其他上传失败的情况,自动重新尝试上传失败的部分(通常是分片上传中会使用重试机制)。适用于网络不稳定或服务器偶尔出现故障的情况。可以做到:
- 提高成功率:自动处理上传失败的情况,提高上传成功率。
- 错误恢复:在上传过程中遇到错误时,可以自动恢复,无需用户干预。
- 可配置性:重试次数和时间间隔可以进行配置,以适应不同的需求和网络环境。
②断点续传
断点续传断点续传是允许在上传过程中中断后,能够从中断的位置继续上传,而不是从头开始。这通常涉及记录已上传的部分,并在恢复时跳过这些已上传部分。
一般来说,断点续传是从断开的分片起,重新上传该分片及之后的分片。但也有更精细的实现方式,从精确的分片的断点位置起,这样在中断频繁的网络环境中表现更好,可以避免重复上传已经部分成功的内容。更加高效和灵活,但实现复杂度较高。
断点续传有助于:
- 中断恢复:在上传过程中,如果发生中断,可以从中断点继续上传,而不是重新上传整个文件。
- 记录状态:需要在客户端或服务器端记录上传进度。
- 用户体验:提升用户体验,避免长时间上传过程中的中断带来的困扰。
四、基于js的简单实现案例
前端代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <input id="upload" type="file" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.7.2/axios.min.js"></script> <script> // 如何获取input,监听上传事件 var inputs = document.getElementById('upload') inputs.onchange = async () => { // console.log(inputs.files, 'file') // 获取到上传的文件对象 var file = inputs.files[0] // 创建分片数据 var chunks = createChunk(file, 50 * 1024 *1024) // 文件内容创建hash var res = await hash(chunks) console.log(res, 'hash') // 调用发送分片的接口 uploadChunk(chunks, res, file.name) } // 创建分片 function createChunk(file, chunkSize) { const result = [] for (let i = 0; i < file.size; i += chunkSize) { result.push(file.slice(i, i + chunkSize)) } return result } // 根据文件内容创建hash值 function hash(chunks) { return new Promise((resolve) => { var spark = new SparkMD5() // 递归 function _read(i) { if (i >= chunks.length) { resolve(spark.end()) return } // 获取当前其中的一个片段 var blob = chunks[i] // 创建一个FileReader对象 var reader = new FileReader() reader.onload = e => { var bytes = e.target.result spark.append(bytes) _read(i + 1) } reader.readAsArrayBuffer(blob) } _read(0) }) } // 分片上传 function uploadChunk(chunks, hash, fileName) { // 文件怎么样传给后端? ok // 所有分片上传成功后,有一个通知告诉后端已经上传完成? var taskArr = [] chunks.forEach((chunk, index) => { var formdata = new FormData() formdata.append('chunk', chunk) formdata.append('chunkName', `${hash}-${index}-${fileName}`) formdata.append('fileName', fileName) var task = axios.post('http://127.0.0.1:3000/upload', formdata, { headers: { 'Content-Type': 'multipart/form-data' } }) taskArr.push(task) }); Promise.all(taskArr).then(() => { // 所有分片上传成功后的结果 console.log('通知后端') }) } </script> </body> </html>
极简的后端接口(模拟发送请求,不具备实际后端作用):
const express = require('express') const cors = require('cors') const app = express() app.use(cors()) const hostname = '127.0.0.1' const port = 3000 app.use(express.json()) // 模拟支付 app.post('/upload', (req, res) => { res.status(200).json({}) }) app.listen(port, hostname, () => { console.log(`Server is running at http://${hostname}:${port}/`) })
五、总结
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
更多优质内容,请关注:
JS语法与Vue开发:
你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解
最细最有条理解析:事件循环(消息循环)是什么?进程与线程的定义、关系与差异
通过array.every()实现数据验证、权限检查和一致性检查,array.some与array.every的区别
通过array.some()实现权限检查、表单验证、库存管理、内容审查和数据处理
通过array.map()实现数据转换、创建派生数组、异步数据流处理、搜索和过滤等需求
通过array.reduce()实现数据汇总、条件筛选和映射、对象属性的扁平化、转换数据格式等
通过array.filter()实现数组的数据筛选、数据清洗和链式调用
多维数组操作,不要再用遍历循环foreach了,来试试数组展平的小妙招!
shpfile转GeoJSON且控制转化精度;如何获取GeoJSON?GeoJson结构详解
Mapbox添加行政区矢量图层、分级设色图层、自定义鼠标悬浮框、添加天地图底图等
Element plus拓展:
通过el-tree自定义渲染网页版工作目录,实现鼠标悬浮显示完整名称等
el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能
优质前端组件库: