【文件上传、秒传、分片上传、断点续传、重传】

文章目录

  • 获取文件对象
  • 文件上传(秒传、分片上传、断点续传、重传)
  • 优化

获取文件对象

input标签的onchange方法接收到的参数就是用户上传的所有文件

<html lang="en"><head><title>文件上传</title><style>#inputFile,#inputDirectory {display: none;}#dragarea{width: 100%;height: 100px;border: 2px dashed #ccc;}.dragenter{background-color: #ccc;}</style></head><body><!-- 1. 如何上传多文件:multiple2. 如何上传文件夹:为了兼顾各浏览器兼容性,需设置三个属性:webkitdirectory mozdirectory odirectory3. 如何实现拖拽上传:input默认是有拖拽性质的,但是由于浏览器兼容性问题,开发一般不使用,一般使用div阻止默认事件以及通过拖拽api实现4. 如何获取选择的所有文件--><div id="dragarea"></div><input id="inputFile" type="file" multiple><!-- 如果不想用input自带的上传文件的样式,可以通过button的click触发input的点击事件来上传文件 --><button id="buttonFile">上传文件</button><input id="inputDirectory" type="file" multiple webkitdirectory mozdirectory odirectory><button id="buttonDirectory">上传文件夹</button><ul class="fileList"></ul><script>const inputFile = document.getElementById("inputFile")const buttonFile = document.getElementById("buttonFile")const inputDirectory = document.getElementById("inputDirectory")const buttonDirectory = document.getElementById("buttonDirectory")const dragarea = document.getElementById("dragarea")const fileList = document.getElementById("fileList")const appendFile = (fileList) => {for(const file in fileList){const li = document.getElementById("li")li.innerText = `${file.name}-${file.name.split(".")[1]}-${file.size}`fileList.appendChild(li)}}const traverseFile = (entry) => {if(entry.isFile){entry.file((file) => {const li = document.getElementById("li")li.innerText = `${file.name}-${file.name.split(".")[1]}-${file.size}`fileList.appendChild(li)})}else if(entry.isDirectory){traverseDirectory(entry)}}const traverseDirectory = (directory) => {const reader = directory.createReader()// 创建读取器读取文件夹reader.readEntries((entries) => {for(const entry of entries) {traverseFile(entry)}})}buttonFile.onclick = () => {inputFile.click()}inputFile.onchange = (e) => {const files = e.target.files// 获得用户上传的所有文件appendFile(files)}inputDirectory.onchange = (e) => {console.log(e.target.files)const files = e.target.files// 获得用户上传的所有文件appendFile(files)}buttonDirectory.onclick = () => {inputDirectory.click()}dragarea.ondragenter = (e) => {e.preventDefault();console.log("拖拽进入区域")dragarea.classList.add("dragenter")}dragarea.ondragover = (e) => {e.preventDefault();console.log("拖拽着悬浮在区域上方")dragarea.classList.add("dragenter")}dragarea.ondragleave = (e) => {e.preventDefault();console.log("拖拽离开")dragarea.classList.remove("dragenter")}// 拖拽放开dragarea.ondrop = (e) => {e.preventDefault();dragarea.classList.remove("dragenter")const items = e.dataTransfer.items// 拖拽进来的所有文件for(const item of items){const entry = item.webkitGetAsEntry()traverseFile(entry)}}</script></body>
</html>

文件上传(秒传、分片上传、断点续传、重传)

秒传:调用后端的接口,将md5值传过去,后端判断如果这个md5值对应的文件是否已经合并,如果已经合并,则返回文件上传成功
分片上传:每片大小chunk_size为1m,假如文件1.5m,那么会被分成2片,使用file.slice截取[0,1),再截取[1,1.5)
断点续传:文件上传前会调用后端的接口,将md5值传过去,后端判断如果这个md5值对应的文件是否已经合并,如果没有合并,会返回这个md5值已经上传的切片的索引,前端重新上传剩余索引的片
并发控制:假如我们把文件切成了100片,如果一下子把这100片全传给后端,会给后端造成并发压力,所以在发送前可以在前端进行并发控制一下,我们将所有的请求都放在队列里,每次从队列里弹出几个请求来发送

明明浏览器可以控制请求并发,为什么前端还要自己控制并发请求?

  1. 避免浏览器并发限制:浏览器对同一域名的并发请求数量是有限制的(通常是 6-8 个,具体取决于浏览器和协议)。如果前端不控制并发请求,可能会导致大量请求堆积,超出浏览器的并发限制,从而阻塞其他重要请求(如关键 API 或资源加载),
  2. 提升用户体验:如果一次性发送过多请求,可能会导致网络带宽被占满,影响页面其他资源的加载(如图片、CSS、JS 等),并且可能会导致部分请求超时或失败,从而浪费网络资源和用户流量。
  3. 错误处理和重试机制:手动控制并发可以更好地实现错误处理和重试机制。
    例如,某个请求失败后,可以立即重试,而不是等待所有请求完成后再处理错误。
  4. 优先级控制:手动控制并发可以实现请求的优先级管理。例如,某些关键请求可以优先发送,而低优先级的请求可以稍后处理。
  5. 兼容性和稳定性:不同浏览器对并发请求的处理方式可能不同,手动控制并发可以确保应用在各种浏览器中表现一致。
<!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="https://cdn.bootcdn.net/ajax/libs/axios/1.7.2/axios.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.js"></script><style>#inputFile,#inputDirectory {display: none;}#dragarea {width: 100%;height: 100px;border: 2px dashed #ccc;}.dragenter {background-color: #ccc;}</style>
</head><body><div class="dragarea"></div><input class="inputFile" type="file" multiple><!-- 如果不想用input自带的上传文件的样式,可以通过button的click触发input的点击事件来上传文件 --><button class="buttonFile">上传文件</button><input class="inputDirectory" type="file" multiple webkitdirectory mozdirectory odirectory><button class="buttonDirectory">上传文件夹</button><button class="buttonUpload">点击上传</button><ul class="fileListElement"></ul><script>// 文件交互相关const fileList = []const chunk_size = 1 * 1024 * 1024const requestQueue = []const maxRequest = 2// 最大请求数量let currentRequest = 0// 当前请求数const inputFile = document.getElementsByClassName("inputFile")[0]const buttonFile = document.getElementsByClassName("buttonFile")[0]const inputDirectory = document.getElementsByClassName("inputDirectory")[0]const buttonDirectory = document.getElementsByClassName("buttonDirectory")[0]const dragarea = document.getElementsByClassName("dragarea")[0]const fileListElement = document.getElementsByClassName("fileListElement")[0]const buttonUpload = document.getElementsByClassName("buttonUpload")[0]// 将上传的文件展示在按钮下方const showFileList = (files) => {for (const file in files) {const li = document.getElementById("li")li.innerText = `${file.name}-${file.name.split(".")[1]}-${file.size}`fileListElement.appendChild(li)fileList.push(file)}}const traverseFile = (entry) => {// 拖拽进来的如果是文件,直接展示在按钮下方if (entry.isFile) {entry.file((file) => {const li = document.getElementById("li")li.innerText = `${file.name}-${file.name.split(".")[1]}-${file.size}`fileList.appendChild(li)})} else if (entry.isDirectory) {// 拖拽进来的如果是文件夹,读文件夹,获得文件夹里面的文件traverseDirectory(entry)}}const traverseDirectory = (directory) => {const reader = directory.createReader()reader.readEntries((entries) => {for (const entry of entries) {traverseFile(entry)}})}buttonFile.onclick = () => {inputFile.click()}inputFile.onchange = (e) => {const files = e.target.files // 获得用户上传的所有文件showFileList(files)}inputDirectory.onchange = (e) => {console.log(e.target.files)const files = e.target.files // 获得用户上传的所有文件showFileList(files)}buttonDirectory.onclick = () => {inputDirectory.click()}dragarea.ondragenter = (e) => {e.preventDefault();console.log("拖拽进入区域")dragarea.classList.add("dragenter")}dragarea.ondragover = (e) => {e.preventDefault();console.log("拖拽着悬浮在区域上方")dragarea.classList.add("dragenter")}dragarea.ondragleave = (e) => {e.preventDefault();console.log("拖拽离开")dragarea.classList.remove("dragenter")}// 拖拽放开dragarea.ondrop = (e) => {e.preventDefault();dragarea.classList.remove("dragenter")const items = e.dataTransfer.itemsfor (const item of items) {const entry = item.webkitGetAsEntry()traverseFile(entry)}}// 文件上传buttonUpload.onclick = () => {for (const file of fileList) {if (file.size <= chunk_size) {uploadSingleFile(file)} else {uploadLargeFile(file)}}}// 单文件一整个文件上传// 文件上传通过formData传输,因为formData是前后端都认识的格式,file是只有前端才认识的格式(后端不认识)const uploadSingleFile = (file) => {const formData = new FormData()formData.append("file", file) // 通过append往formData身上添加对象,如果formData身上已有file对象,会覆盖try {axios.post("http://127.0.0.1:3001/upload", formData, {headers: {"content-type": "multipart/form-data"}})} catch (error) {throw error}}// 大文件上传const uploadLargeFile = async (file) => {// 创建文件hash。创建整个文件的hash即可,每个片不用创建hash,因为每片是调用后端的方法上传的,返回成功即上传成功const md5 = await createFileMd5(file)// 大文件分片const chunksList = createChunkFile(file)// 创建文件分片对象const chunkListObj = createChunkFileObj(chunksList, file, md5)// 将md5值传给后端接口,判断文件是否在服务器上存在,如果存在,后端返回isExistObj.isExists为true,则秒传成功,// 如果不存在,后端会返回给你此md5值上传了哪些片,已上传的片的索引放在chunkIds中const isExistObj = await juedgeFileExist(file, md5)if (isExistObj && isExistObj.isExists) {alert('文件已秒传成功!')return}// 文件上传await asyncPool(chunkListObj, isExistObj.chunkIds) // chunkIds:后端返回的,已上传的分片的索引// await Promise.all(promises)concatChunkFile(file, md5)// 文件上传完毕,调用后端合并文件的接口}// 创建文件的md5值const createFileMd5 = (file) => {return new Promise((resolve, reject) => {const reader = new FileReader()// reader.readAsArrayBuffer(file)读取完毕后会调用onload,读取失败调用onerror,读取到的内容在e.target.result中reader.onload = (e) => {const md5 = SparkMD5.ArrayBuffer.hash(e.target.result)resolve(md5)}reader.onerror = () => {reject(error)}reader.readAsArrayBuffer(file)})}// 创建文件分片:每片大小chunk_size为1m,假如文件1.5m,那么会被分成2片,使用file.slice截取[0,1),再截取[1,1.5)const createChunkFile = (file) => {let current = 0const chunkList = []while (current < file.size) {chunkList.push(file.slice(current, Math.min(current + chunk_size, file.size)))current += chunk_size}return chunkList}// 创建文件分片对象。将文件的md5、文件名、本片在整个文件中的索引,都传入这个对象,调用后端接口上传时会用到的数据都可以封装进来const createChunkFileObj = (chunkList, file, md5) => {return chunkList.map((chunk, index) => {return {file: chunk,md5,name: file.name,index: index,}})}// 文件分片上传const uploadChunkFile = (chunkListObj, chunkIds) => {return chunkListObj.filter((item,index) => (!chunkIds.includes(index)))// 过滤掉已经上传的切片,让已经上传的切片没有下面那个函数.map((chunk, index) => {return () => {const formData = new FormData()formData.append("file", chunk.file, `${chunk.md5}-${chunk.index}`)formData.append("name", chunk.name)formData.append("timestamp", Date.now().toString()) // 防止走缓存try {axios.post("http://127.0.0.1:3001/upload/large", formData, {headers: {"content-type": "multipart/form-data"}})} catch (error) {return Promise.reject(error)throw error}}})}// 判断文件是否存在const juedgeFileExist = async (file, md5) => {try {const response = await axios.post("http://127.0.0.1:3001/upload/exists", formData, {params: {"name": file.nam,md5,}})return response.data.data} catch (error) {return {}throw error}}// 合并请求const concatChunkFile = (file, md5) => {try {axios.post("http://127.0.0.1:3001/upload/concatFiles", {"name": file.nam,md5,})} catch (error) {throw error}}// 把要发送的函数放在队列里,每次从头部取一个函数调用,这样就可以控制并发数量const asyncPool = (chunkListObj, chunkIds) => {return new Promise((resolve,reject) => {requestQueue.push(...uploadChunkFile(chunkListObj, chunkIds))run(resolve,reject)})}const run = (resolve,reject) => {while(currentRequest < maxRequest && requestQueue.length > 0){const task = requestQueue.shift()currentRequest++task().then().finally(() => {currentRequest--run(resolve,reject)})}if(currentRequest === 0 && requestQueue.length === 0) {resolve()}}</script>
</body></html>

优化

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【苍穹外卖 Day1】前后端搭建 Swagger导入接口文档

项目技术选型 前端 直接使用打包好的nginx运行。 后端 1、导入初始代码结构如下&#xff1a; 2、将代码上传远程仓库。 3、创建数据库&#xff0c;并修改数据库配置。 4、断点调试&#xff0c;前后端联调。 5、使用Nginx代理&#xff0c;修改Nginx配置 好处&#xff1a;提…

零基础Vue入门6——Vue router

本节重点&#xff1a; 路由定义路由跳转 前面几节学习的都是单页面的功能&#xff08;都在专栏里面https://blog.csdn.net/zhanggongzichu/category_12883540.html&#xff09;&#xff0c;涉及到项目研发都是有很多页面的&#xff0c;这里就需要用到路由&#xff08;vue route…

深度学习里面的而优化函数 Adam,SGD,动量法,AdaGrad 等 | PyTorch 深度学习实战

前一篇文章&#xff0c;使用线性回归模型逼近目标模型 | PyTorch 深度学习实战 本系列文章 GitHub Repo: https://github.com/hailiang-wang/pytorch-get-started 本篇文章内容来自于 强化学习必修课&#xff1a;引领人工智能新时代【梗直哥瞿炜】 深度学习里面的而优化函数 …

mybatis-plus updateById源码

1.版本 : mybatis-plus-core 3.5.1 2.入口:MybatisPlusAutoConfiguration类sqlSessionFactory中的factory.getObject() 3.注入AbstractSqlInjector类中的inspectInject方法中 Overridepublic void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> m…

RabbitMQ 从入门到精通:从工作模式到集群部署实战(三)

文章目录 使用CLI管理RabbitMQrabbitmqctlrabbitmq-queuesrabbitmq-diagnosticsrabbitmq-pluginsrabbitmq-streamsrabbitmq-upgraderabbitmqadmin 使用CLI管理RabbitMQ RabbitMQ CLI 工具需要安装兼容的 Erlang/OTP版本。 这些工具假定系统区域设置为 UTF-8&#xff08;例如en…

PlanLLM: 首个支持开放词汇与封闭集任务的跨模态视频程序规划框架

2025年1月7号&#xff0c;由杨德杰、赵子敬、刘洋联合提出PlanLLM&#xff0c;一种基于可微调大型语言模型&#xff08;LLM&#xff09;的跨模态联合学习框架&#xff0c;用于解决视频程序规划任务。通过引入LLM增强规划模块和互信息最大化模块&#xff0c;PlanLLM突破了现有方…

WGCLOUD监控系统部署教程

官网地址&#xff1a;下载WGCLOUD安装包 - WGCLOUD官网 第一步、环境配置 #安装jdk 1、安装 EPEL 仓库&#xff1a; sudo yum install -y epel-release 2、安装 OpenJDK 11&#xff1a; sudo yum install java-11-openjdk-devel 3、如果成功&#xff0c;你可以通过运行 java …

6-图像金字塔与轮廓检测

文章目录 6.图像金字塔与轮廓检测(1)图像金字塔定义(2)金字塔制作方法(3)轮廓检测方法(4)轮廓特征与近似(5)模板匹配方法6.图像金字塔与轮廓检测 (1)图像金字塔定义 高斯金字塔拉普拉斯金字塔 高斯金字塔:向下采样方法(缩小) 高斯金字塔:向上采样方法(放大)…

DeepSeek-V3 与 DeepSeek R1 对比分析:技术与应用的全面解析

一、背景 在当今科技飞速发展的时代&#xff0c;深度学习技术如同一股强大的浪潮&#xff0c;席卷了自然语言处理&#xff08;NLP&#xff09;、计算机视觉&#xff08;CV&#xff09;以及多模态模型等众多领域。从智能语音助手到图像识别技术&#xff0c;从文本生成工具到多模…

基于 Spring Cloud + Spring AI + VUE 的知识助理平台介绍以及问题

前言&#xff08;一些废话&#xff09; 在看这篇文章的各位大佬&#xff0c;感谢你们留出几分钟时间&#xff0c;来看这个产品介绍&#xff0c;其实重点说实话&#xff0c;不是这个产品怎么样。而是在最后有一个郁结在心里的几个问题&#xff0c;希望大佬们能给出一些建议。万…

IEEE 802.3/802.2 | LLC / SNAP

注&#xff1a;本文为 “IEEE 802.3/802.2 | LLC / SNAP” 相关文章合辑。 未整理去重。 第三篇部分内容出自第二篇。 802.2 协议 haoay321 2010-01-28 20:52:02 LLC 协议 LLC&#xff08;Logic Link Control&#xff0c;逻辑链路控制&#xff09;是 IEEE 802.2 协议中规定…

【Elasticsearch】Geo-distance聚合

geo_distance聚合的形状是圆形。它基于一个中心点&#xff08;origin&#xff09;和一系列距离范围来计算每个文档与中心点的距离&#xff0c;并将文档分配到相应的距离范围内。这种聚合方式本质上是以中心点为圆心&#xff0c;以指定的距离范围为半径的圆形区域来划分数据。 为…

Chapter 4-1. Troubleshooting Congestion in Fibre Channel Fabrics

This chapter covers the following topics: 本章包括以下内容: Congestion troubleshooting methodology and workflow. Hints and tips for troubleshooting congestion. Cisco MDS NX-OS commands for troubleshooting congestion. Case studies demonstrating troubleshoo…

【字节青训营-7】:初探 Kitex 字节微服务框架(使用ETCD进行服务注册与发现)

本文目录 一、Kitex概述二、第一个Kitex应用三、IDL四、服务注册与发现 一、Kitex概述 长话短说&#xff0c;就是字节跳动内部的 Golang 微服务 RPC 框架&#xff0c;具有高性能、强可扩展的特点&#xff0c;在字节内部已广泛使用。 如果对微服务性能有要求&#xff0c;又希望…

设计模式Python版 享元模式

文章目录 前言一、享元模式二、享元模式示例 前言 GOF设计模式分三大类&#xff1a; 创建型模式&#xff1a;关注对象的创建过程&#xff0c;包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式。结构型模式&#xff1a;关注类和对象之间的组合&…

蓝桥杯嵌入式备赛(三)—— LED +按键 + LCD

目录 一、LED1、原理图介绍2、程序代码 二、按键1、原理图介绍2、程序代码 三、LCD1、原理图介绍2、程序代码 一、LED 1、原理图介绍 如果所示&#xff0c;STM32G431RBT6中有八个LED&#xff0c;由八个GPIO控制&#xff0c;分别为PC8-15&#xff0c;当输出为低电平时点亮。其中…

IDEA 中集成 Maven,配置环境、创建以及导入项目

目录 在 IntelliJ IDEA 中集成 Maven 并配置环境 1. 打开 IDEA 设置 2. 定位 Maven 配置选项 3. 配置 Maven 路径 4. 应用配置 创建 Maven 项目 1. 新建项目 2. 选择项目类型 3. 配置项目信息 4. 确认 Maven 设置 5. 完成项目创建 导入 Maven 项目 1. 打开导入窗口…

javaEE-6.网络原理-http

目录 什么是http? http的工作原理&#xff1a; 抓包工具 fiddler的使用 HTTP请求数据: 1.首行:​编辑 2.请求头(header) 3.空行&#xff1a; 4.正文&#xff08;body&#xff09; HTTP响应数据 1.首行&#xff1a;​编辑 2.响应头 3.空行&#xff1a; 4.响应正文…

链式结构二叉树(递归暴力美学)

文章目录 1. 链式结构二叉树1.1 二叉树创建 2. 前中后序遍历2.1 遍历规则2.2 代码实现图文理解 3. 结点个数以及高度等二叉树结点个数正确做法&#xff1a; 4. 层序遍历5. 判断是否完全二叉树 1. 链式结构二叉树 完成了顺序结构二叉树的代码实现&#xff0c;可以知道其底层结构…

复制粘贴小工具——Ditto

在日常工作中&#xff0c;复制粘贴是常见的操作&#xff0c;但Windows系统自带的剪贴板功能较为有限&#xff0c;只能保存最近一次的复制记录&#xff0c;这对于需要频繁复制粘贴的用户来说不太方便。今天&#xff0c;我们介绍一款开源、免费且功能强大的剪贴板增强工具——Dit…