当前位置: 首页 > news >正文

前端大文件分块上传、断点续传

文章目录

  • 前端分块上传流程
  • 分块上传代码
  • 重点
    • 1. `let start = currentChunk * chunkSize;`
    • 2. `let end = Math.min(file.size, start + chunkSize);`
    • 3. `let chunk = file.slice(start, end);`
  • 结合断点续传
  • 注意事项

大文件上传是一个复杂的过程,尤其是在前端,我们需要考虑用户体验、网络状况、文件完整性等多个方面。
以下是一个使用HTML5的 File APIXMLHttpRequest进行大文件分块上传的详解和示例代码。

前端分块上传流程

  1. 选择文件:使用<input type="file">元素让用户选择文件。

  2. 读取文件:使用FileReader API读取文件内容。

  3. 分块文件:根据设定的大小将文件切割成多个小块。

  4. 上传分块:使用XMLHttpRequestfetch API将每个分块上传到服务器。

  5. 合并文件:服务器接收到所有分块后,将其合并成完整的文件。


分块上传代码

以下是一个简单的示例代码,展示了如何使用JavaScript实现大文件的分块上传。

<!DOCTYPE html>  
<html lang="en">  
<head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>大文件分块上传</title>  
</head>  
<body>  <input type="file" id="fileInput">  <button id="uploadButton">上传</button>  <script>  const chunkSize = 1024 * 1024; // 每块大小设置为1MB  let fileInput = document.getElementById('fileInput');  let uploadButton = document.getElementById('uploadButton');  uploadButton.addEventListener('click', function () {  let file = fileInput.files[0];  let chunks = Math.ceil(file.size / chunkSize); // 计算文件块数(文件总大小 / 每块大小)let currentChunk = 0; // 当前上传的块数  function uploadChunk() {  if (currentChunk < chunks) {  let start = currentChunk * chunkSize; // 计算当前要上传的分片的起始字节位置(已经上传的字节)let end = Math.min(file.size, start + chunkSize);   // 计算当前要上传的分片的结束字节位置(当前准备上传的字节)let chunk = file.slice(start, end);  let formData = new FormData();  formData.append('chunk', chunk);  formData.append('fileName', file.name);  formData.append('totalChunks', chunks);  formData.append('currentChunk', currentChunk);  let xhr = new XMLHttpRequest();  xhr.open('POST', '/upload', true); // 替换为你的上传接口  xhr.upload.onprogress = function (e) {  // 监听上传进度if (e.lengthComputable) {  console.log((e.loaded / e.total * 100) + '%');  }  };  xhr.onload = function () {  if (xhr.status === 200) {  currentChunk++;  uploadChunk(); // 递归上传下一个块  } else {  console.error('上传失败');  }  };  xhr.send(formData);  } else {  console.log('上传完成');  }  }  uploadChunk(); // 开始上传第一个块  });  </script>  
</body>  
</html>

重点

1. let start = currentChunk * chunkSize;

  • currentChunk当前要上传的文件分片的索引
    如果这是第一个分片,currentChunk 通常是0;如果是第二个分片,它是1,依此类推。
    每当当前分片上传成功,便会累加

  • chunkSize每个分片的大小(以字节为单位)
    这通常是一个固定的值,比如1MB(即1024 * 1024字节)或其他合适的值,取决于你的应用需求和网络条件

  • startcurrentChunkchunkSize 相乘,你得到了当前分片开始的字节位置
    例如,假设你有一个10MB的文件,并且你决定每个分片大小为1MB:
    对于第一个分片(currentChunk = 0),start 会是 0 * 1MB = 0,即从文件的开头开始。
    对于第二个分片(currentChunk = 1),start 会是 1 * 1MB = 1MB,即从文件的1MB位置开始。
    对于第三个分片(currentChunk = 2),start 会是 2 * 1MB = 2MB,以此类推。

.

2. let end = Math.min(file.size, start + chunkSize);

end 是一个变量,用于表示文件分片上传时的结束字节位置。
这个计算确保了分片不会超出文件的实际大小,并且在最后一个分片时能够正确地设置结束位置。

  • start + chunkSize:计算当前分片应该结束的位置,也就是当前需要上传的文件分片

  • file.size:获取文件的总大小

  • Math.min(...):取上述两个值中的较小者作为当前分片的结束位置

这样做的原因是,当处理文件的最后一个分片时,start + chunkSize 可能会超出文件的实际大小。
为了避免这种情况,我们使用 Math.min 函数来确保 end 不会大于文件的实际大小(file.size)。

或者可以这么理解: let end = start + chunkSize > file.size ? file.size : start + chunkSize

例如,假设你有一个13MB的文件,并且你决定每个分片大小为5MB。即:

  • file.size = 13*1024*1024 => 文件大小为 13631488 字节

  • chunkSize = 5*1024*1024 => 每个分片大小为 5242880 字节

  • let chunks = Math.ceil(file.size / chunkSize) => 文件块数为3

这时:

  • 第一个分片:start + chunkSize 将分别是 0 + 5MB

  • 第二个分片:start + chunkSize 将分别是 5MB + 5MB

  • 第三个分片:如果简单地使用 10MB + 5MB,你会得到一个超出文件大小的结束位置(15MB>13MB)。
    通过 Math.min,你可以确保第三个分片的结束位置正确地设置为文件的末尾(即13MB):
    Math.min(file.size, start + chunkSize) => Math.min(13*1024*1024, (10+5)*1024*1024) => 13*1024*1024 字节 => 13MB

.

3. let chunk = file.slice(start, end);

在JavaScript中,file.slice(start, end) 是 Blob 对象的一个方法,用于创建一个新的 Blob 对象,该对象包含源 Blob 对象中指定范围内的数据。这通常用于文件操作,特别是当你需要处理文件的一部分(例如,分片上传)时。

file 是一个 Blob 或 File 对象(File 继承自 Blob),而 start 和 end 是指定切片范围的参数:

  • start(包含):开始切片的字节偏移量。
  • end(不包含):结束切片的字节偏移量。(如果 end 被省略或大于 Blob 的大小,则切片会一直到 Blob 的末尾。)

例如,如果你有一个 File 对象 file,你可以使用 slice 方法来获取文件的部分字节

var slice1 = file.slice(0, 100);  // 获取文件的前100个字节var slice2 = file.slice(100, 200); // 获取从第101个字节开始到第200个字节结束的部分

注意:

  • slice 方法不会改变原始的 Blob 或 File 对象
  • 它返回一个新的 Blob 对象,该对象包含指定范围内的数据

结合断点续传

断点续传(Resume Download)是一种在网络传输中常见的功能,它允许在下载大文件时,如果由于某种原因(如网络中断、程序崩溃等)导致下载中断,可以从之前中断的地方继续下载,而不是重新下载整个文件。这可以大大节省时间和带宽资源。

// 假设你已经通过某种方式获取了文件对象以及要续传的起始字节位置 startByte  
const file = document.querySelector('input[type="file"]').files[0];  
const chunkSize = 1024 * 1024; // 1MB 分片大小  
const startByte = // 从服务器获取的已下载字节数,用于断点续传  
let currentChunk = Math.ceil(startByte / chunkSize); // 计算当前应该开始下载的分片索引  // 分片函数  
function createChunk(file, start, end) {  const chunk = file.slice(start, end);  return new Blob([chunk], { type: file.type });  
}  // 创建分片并发送请求的函数  
function uploadChunk(chunk, chunkIndex) {  const formData = new FormData();  formData.append('file', chunk);  formData.append('chunkIndex', chunkIndex);  formData.append('fileName', file.name);  formData.append('chunkSize', chunkSize);  formData.append('totalSize', file.size);  axios.post('/upload-chunk', formData, {  headers: {  'Content-Type': 'multipart/form-data'  },  onUploadProgress: progressEvent => {  const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);  console.log(`Chunk ${chunkIndex + 1} uploaded: ${percentCompleted}%`);  },  onDownloadProgress: progressEvent => {  // 可以在这里处理下载进度,如果需要的话  }  })  .then(response => {  if (response.data.success) {  console.log(`Chunk ${chunkIndex + 1} uploaded successfully.`);  // 检查是否所有分片都已上传完成  if (chunkIndex < Math.ceil(file.size / chunkSize) - 1) {  // 上传下一个分片  const nextChunkStart = chunkIndex * chunkSize + chunkSize;  const nextChunkEnd = Math.min(nextChunkStart + chunkSize, file.size);  const nextChunk = createChunk(file, nextChunkStart, nextChunkEnd);  uploadChunk(nextChunk, chunkIndex + 1);  } else {  // 所有分片上传完成,可以通知服务器合并文件或进行其他操作  console.log('All chunks uploaded successfully.');  }  } else {  console.error('Chunk upload failed:', response.data.error);  }  })  .catch(error => {  console.error('Chunk upload error:', error);  // 可以在这里处理错误,比如重试上传当前分片或全部重新开始  });  
}  // 开始上传  
const firstChunkStart = currentChunk * chunkSize;  
const firstChunkEnd = Math.min(firstChunkStart + chunkSize, file.size);  
const firstChunk = createChunk(file, firstChunkStart, firstChunkEnd);  
uploadChunk(firstChunk, currentChunk);

在这个示例中:

  • createChunk 函数用于创建文件的分片

  • uploadChunk 函数用于发送包含分片数据的 POST 请求到服务器

  • startByte 是从服务器获取的已下载字节数,用于确定从哪里开始上传分片

每次上传一个分片后,如果成功,则检查是否还有剩余的分片需要上传,如果有,则继续上传下一个分片。

请注意,这个示例仅涵盖了前端的部分逻辑。为了实现完整的断点续传功能,你还需要在后端实现相应的逻辑来处理分片上传、合并分片以及存储和检索已上传的分片信息。此外,还需要考虑如何安全地验证和授权上传请求,以避免安全风险。


注意事项

  1. 文件完整性校验:上传完成后,服务器需要对合并后的文件进行完整性校验,确保文件没有损坏。

  2. 断点续传:在实际应用中,还需要考虑断点续传的功能,即当上传中断时,可以从上次中断的地方继续上传。

  3. 上传进度显示:在上传过程中,可以通过XMLHttpRequestupload.onprogress事件来显示上传进度。

  4. 错误处理:在上传过程中可能会遇到各种错误,如网络错误、服务器错误等,需要妥善处理这些错误,并给用户友好的提示。

  5. 安全性:在处理文件上传时,需要注意安全性问题,如防止恶意文件上传、防止跨站脚本攻击等。

相关文章:

前端大文件分块上传、断点续传

文章目录 前端分块上传流程分块上传代码重点1. let start currentChunk * chunkSize;2. let end Math.min(file.size, start chunkSize);3. let chunk file.slice(start, end); 结合断点续传注意事项 大文件上传是一个复杂的过程&#xff0c;尤其是在前端&#xff0c;我们需…...

使用新版FLIR (FLIR_ADAS_v2) 数据集创建yolo格式数据集(目标检测)

FLIR在2022.1.19发布了新版的FLIR_ADAS_v2&#xff0c;有着更多的类别和数量更丰富的图像。数据集同步注释热图像和无注释RGB图像供参考。本文章主要介绍如何使用FLIR_ADAS_v2中的rgb图像和thermal图像来制作yolo格式数据集。 1.官方数据集下载&#xff1a;FLIR_ADAS_v2数据集…...

PHP发票查验接口未返回正确信息的原因、发票ocr识别接口

发票查验接口未返回正确信息的原因一般有以下几种&#xff0c;第一种可能是接口没有调通&#xff0c;第二种是本身这张发票就是一张错票、假票&#xff0c;第三种可能是税局系统或者网络问题等等。那么&#xff0c;遇到这种情况应该如何解决呢&#xff1f;翔云发票查验接口&…...

RA4000CE为汽车动力传动系统提供解决方案

目前汽车电气化的水平越来越高&#xff0c;其中比较显著的一个发展方向就是将发动机管理系统和自动变速器控制系统&#xff0c;集成为动力传动系统的综合控制(PCM)。作为汽车动力的核心部件&#xff0c;通过电子系统的运用&#xff0c;将外部多个传感器和执行环节的数据进行统一…...

算法中的二阶差分

众所周知&#xff0c;在往区间的每一个数都加上一个相同的数k&#xff0c;进行n次后会得到一个新的数列&#xff0c;如果每次加都循环区间挨个数加上k&#xff0c;这样时间复杂度无疑是O(n^2)&#xff0c;很高。这时可以采用一阶差分就可解决&#xff0c;这里默认会一阶差分&am…...

第十五届蓝桥杯Java A组参赛总结

一、比赛 4月13号那天上午9点到下午1点&#xff0c;线上比赛总共4小时。 因为很久没有参加过竞赛了&#xff0c;所以还是很紧张&#xff0c;睡觉都有点睡不好&#xff0c;生怕出什么差错 我参加的是java的A组&#xff0c;两道填空&#xff08;每道5分&#xff09;和六道大题…...

springCloudAlibaba集成seata实战(分布式事物详解)

一、分布式事务 1. 事务介绍 1.1 基础概念 事务&#xff1a;保证我们多个数据库操作的原子性&#xff0c;多个操作要么都成功要么都不成功 事务ACID原则 A&#xff08;Atomic&#xff09;原子性&#xff1a;构成事务的所有操作&#xff0c;要么都执行完成&#xff0c;要么全部…...

VRTK/SteamVR手柄震动功能

VRTK/SteamVR手柄震动功能 前言代码块 前言 手柄震动功能配合虚拟仿真模块的模拟电击等功能非常方便 代码块 SteamVR_Controller.DeviceRelation.Rightmost是右侧手柄 SteamVR_Controller.DeviceRelation.Leftmost是左侧手柄 var deviceIndex2 SteamVR_Controller.GetDevic…...

MYSQL索引优化方法

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是小周同志&#xff0c;25届双非校招生Java选手&#xff0c;很高兴认识大家 &#x1f4d5;学习出处&#xff1a;本文是学自小林coding (xiaolincoding.com) 网站的MYSQL图解篇 &#x1f525;如果感觉博主的文章还不错的…...

多模态 ——LLaVA 集成先进图像理解与自然语言交互GPT-4的大模型

概述 提出了一种大型模型 LLaVA&#xff0c;它使用 GPT-4 生成多模态语言图像指令跟随数据&#xff0c;并利用该数据将视觉和语言理解融为一体。初步实验表明&#xff0c;LLaVA 展示了出色的多模态聊天能力&#xff0c;在合成多模态指令上的表现优于 GPT-4。 在科学质量保证中…...

文献学习-33-一个用于生成手术视频摘要的python库

VideoSum: A Python Library for Surgical Video Summarization Authors: Luis C. Garcia-Peraza-Herrera, Sebastien Ourselin, and Tom Vercauteren Source: https://arxiv.org/pdf/2303.10173.pdf 这篇文章主要关注的是如何通过视频摘要来简化和可视化手术视频&#xff0c…...

Unity Android 2021 Release-Notes

&#x1f308;Unity Android 2021 Release-Notes 版本更新内容2021.3.34Android: Google play.core package is replaced with separate plugins including play.asset-delivery 2.1.0 to solve PAD related compatibility problem with Android 14.(UUM-54157)2021.3.34Androi…...

Java8新特性--lambda表达式

lambda表达式本质上是一个匿名函数&#xff0c;在lambda表达式中我们只需要关心参数列表以及方法体。优点是可以减少代码量。 1.语法 基本语法&#xff1a;(参数)->表达式 或 (参数) -> {语句;} 2.函数式接口 要了解lambda表达式&#xff0c;首先要了解什么是函数式接口…...

C/C++中设置随机数

前言 我们通常在写一个数据结构后&#xff0c;需要去测试其正确性和性能比较&#xff0c;那在平常手动输入数据的方式太鸡肋&#xff0c;并且不具有普遍性和随机性。基于这个原因&#xff0c;我们必须要掌握设置随机数&#xff0c;不但可以给我们提供更多的数据&#xff0c;还可…...

ARM 三个小灯闪烁

.text .global _start _start: 使能GPIOE的外设时钟 LDR R0,0x50000A28 指定基地址 LDR R1,[R0] 读取r0中的数据保存到r1中 ORR R1,R1,#(0X3<<4) [4]设置为1,表示 STR R1,[R0] 将修改之后的值放回去 设置PE10,PE8为输出 LDR R0,0X50006000…...

创业之路:从市场洞察到产品实现的全方位指南

创业是一项挑战性的旅程&#xff0c;需要综合考虑市场、产品、技术、团队等多个方面。在这篇文章中&#xff0c;我们将深入探讨如何更好地进行创业&#xff0c;从市场分析到产品实现的各个环节。 深入市场洞察 在创业之前&#xff0c;深入了解目标市场是至关重要的。我们需要…...

C++ 红黑树模拟实现

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;C知识分享⏪   &#x1f69a;代码仓库:C高阶&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多C知识   &#x1f51d;&#x1f51d; 前言 前面我们实现了AVL树&#xff0c;发明AVL树…...

【数据结构】第三节:单链表

前言 本篇要求掌握的C语言基础知识&#xff1a;指针、结构体 目录 前言 单链表 概念 对比链表和顺序表 创建链表 实现单链表 准备工作 打印链表 创建节点并初始化 尾插 二级指针的调用 尾插代码 头插 尾删 头删 查找&#xff08;返回节点&#xff09; 在指定位…...

Python中操作Excel表对象并打包为脚本

一、准备工作 pip install pandas pip install openpyxl pip install pyinstaller 数据表格&#xff1a; 数据表下载 二、执行写入操作 import pandas as pd # pyinstaller --onefile attendance_records_score.py # 打包 # 读取源Excel文件&#xff08;假设源表有列A…...

Python学习笔记23 - 目录操作

os模块操作目录相关函数 os.path模块操作目录相关函数 案例1 —— 列出指定目录下的所有.py文件 案例2 —— walk()...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)

前言&#xff1a; 在Java编程中&#xff0c;类的生命周期是指类从被加载到内存中开始&#xff0c;到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期&#xff0c;让读者对此有深刻印象。 目录 ​…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...