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

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

文章目录

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

获取文件对象

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>

优化

相关文章:

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

文章目录 获取文件对象文件上传&#xff08;秒传、分片上传、断点续传、重传&#xff09;优化 获取文件对象 input标签的onchange方法接收到的参数就是用户上传的所有文件 <html lang"en"><head><title>文件上传</title><style>#inp…...

使用Pygame制作“打砖块”游戏

1. 前言 打砖块&#xff08;Breakout / Arkanoid&#xff09; 是一款经典街机游戏&#xff0c;玩家控制一个可左右移动的挡板&#xff0c;接住并反弹球&#xff0c;击碎屏幕上方的砖块。随着砖块被击碎&#xff0c;不仅能获得分数&#xff0c;还可以体验到不断加速或复杂的反弹…...

【完整版】DeepSeek-R1大模型学习笔记(架构、训练、Infra)

文章目录 0 DeepSeek系列总览1 模型架构设计基本参数专家混合模型&#xff08;MoE&#xff09;[DeepSeek-V2提出, DeepSeek-V3改良]多头潜在注意力&#xff08;MLA&#xff09;[DeepSeek-V2提出]多token预测&#xff08;MTP&#xff09;[DeepSeek-V3提出] 2 DeepSeek-R1-Zero及…...

深入解析:如何利用 Python 爬虫获取商品 SKU 详细信息

在电商领域&#xff0c;SKU&#xff08;Stock Keeping Unit&#xff0c;库存单位&#xff09;详细信息是电商运营的核心数据之一。它不仅包含了商品的规格、价格、库存等关键信息&#xff0c;还直接影响到库存管理、价格策略和市场分析等多个方面。本文将详细介绍如何利用 Pyth…...

【3】高并发导出场景下,服务器性能瓶颈优化方案-文件压缩

使用EasyExcel导出并压缩文件是一种高效且常见的解决方案&#xff0c;尤其适用于需要处理大量数据的场景。 1. 导出多个Excel文件并压缩成ZIP文件的基本流程 &#xff08;1&#xff09;数据准备&#xff1a;从数据库或其他数据源获取需要导出的数据&#xff0c;并将其存储在Ja…...

FPGA|生成jic文件固化程序到flash

1、单击file-》convert programming files 2、flie type中选中jic文件&#xff0c;configuration decive里根据自己的硬件选择&#xff0c;单击flash loader选择右边的add device选项 3、选择自己的硬件&#xff0c;单击ok 4、选中sof选项&#xff0c;单机右侧的add file 5、选…...

【ArcGIS_Python】使用arcpy脚本将shape数据转换为三维白膜数据

说明&#xff1a; 该专栏之前的文章中python脚本使用的是ArcMap10.6自带的arcpy&#xff08;好几年前的文章&#xff09;&#xff0c;从本篇开始使用的是ArcGIS Pro 3.3.2版本自带的arcpy&#xff0c;需要注意不同版本对应的arcpy函数是存在差异的 数据准备&#xff1a;准备一…...

用Python获取股票数据并实现未来收盘价的预测

获取数据 先用下面这段代码获取上证指数的历史数据&#xff0c;得到的csv文件数据&#xff0c;为后面训练模型用的 import akshare as ak import pandas as pd# 获取上证指数历史数据 df ak.stock_zh_index_daily(symbol"sh000001")# 将数据保存到本地CSV文件 df.…...

Rust 所有权特性详解

Rust 所有权特性详解 Rust 的所有权系统是其内存安全的核心机制之一。通过所有权规则&#xff0c;Rust 在编译时避免了常见的内存错误&#xff08;如空指针、数据竞争等&#xff09;。本文将从堆内存与栈内存、所有权规则、变量作用域、String 类型、内存分配、所有权移动、Cl…...

Gateway路由匹配规则详解

在微服务架构中&#xff0c;Gateway作为请求的入口&#xff0c;扮演着至关重要的角色。它不仅负责路由转发&#xff0c;还具备安全、监控、限流等多种功能。其中&#xff0c;路由匹配规则是Gateway的核心功能之一&#xff0c;它决定了请求如何被正确地转发到目标服务。本文将详…...

项目实操:windows批处理拉取git库和处理目录、文件

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…...

前端开发知识梳理 - HTMLCSS

1. 盒模型 由内容区&#xff08;content&#xff09;、内边距&#xff08;padding&#xff09;、边框&#xff08;border&#xff09;和外边距&#xff08;margin&#xff09;组成。 &#xff08;1&#xff09;标准盒模型&#xff08;box-sizing默认值, content-box&#xff…...

nginx中的proxy_set_header参数详解

在使用 Nginx 作为反向代理服务器时&#xff0c;proxy_set_header 指令扮演着至关重要的角色。它允许我们自定义请求头信息&#xff0c;将客户端请求传递给上游服务器时&#xff0c;添加或修改特定的信息&#xff0c;从而实现更灵活的代理功能。本文将深入探讨 proxy_set_heade…...

MapReduce是什么?

MapReduce 是一种编程模型&#xff0c;最初由 Google 提出&#xff0c;旨在处理大规模数据集。它是分布式计算的一个重要概念&#xff0c;通常用于处理海量数据并进行并行计算。MapReduce的基本思想是将计算任务分解为两个阶段&#xff1a;Map 阶段和 Reduce 阶段。 Map 阶段&a…...

Text2Sql:开启自然语言与数据库交互新时代(3030)

一、Text2Sql 简介 在当今数字化时代&#xff0c;数据处理和分析的需求日益增长。对于众多非技术专业人员而言&#xff0c;数据库操作的复杂性常常成为他们获取所需信息的障碍。而 Text2Sql 技术的出现&#xff0c;为这一问题提供了有效的解决方案。 Text2Sql&#xff0c;即文…...

《图解设计模式》笔记(五)一致性

十一、Composite模式&#xff1a;容器与内容的一致性 像文件夹与文件一样&#xff0c;文件夹中可以放子文件夹与文件&#xff0c;再比如容器中可以放更小的容器和具体内容。 Composite模式&#xff1a;使容器与内容具有一致性&#xff0c;创造出递归结构。 Composite&#x…...

华为支付-免密支付接入免密代扣说明

免密代扣包括支付并签约以及签约代扣场景。 开发者接入免密支付前需先申请开通签约代扣产品&#xff08;即申请配置免密代扣模板及协议模板ID&#xff09;。 华为支付以模板维度管理每一个代扣扣费服务&#xff0c;主要组成要素如下&#xff1a; 接入免密支付需注意&#x…...

React组件中的列表渲染与分隔符处理技巧

React组件中的列表渲染与分隔符处理技巧 摘要问题背景解决方案分析方案一&#xff1a;数组拼接法方案二&#xff1a;Fragment组件方案三&#xff1a;动态生成key 关键技术点1. key的使用原则2. Fragment组件3. 性能优化 实战演练挑战1&#xff1a;动态分隔符样式挑战2&#xff…...

【Pytorch和Keras】使用transformer库进行图像分类

目录 一、环境准备二、基于Pytorch的预训练模型1、准备数据集2、加载预训练模型3、 使用pytorch进行模型构建 三、基于keras的预训练模型四、模型测试五、参考 现在大多数的模型都会上传到huggface平台进行统一的管理&#xff0c;transformer库能关联到huggface中对应的模型&am…...

快速了解 c++ 异常处理 基础知识

相关代码概览&#xff1a; #include<stdexcept>std::runtime_errorcatch (const std::runtime_error& e) e.what() 相信大家一定见过这些代码&#xff0c;那么这些代码具体什么意思呢&#xff1f;我们一起来看一下 知识精讲&#xff1a; 异常处理是C中非常重要…...

[Java 基础]Java 中的关键字

在 Java 编程语言中&#xff0c;关键字 (Keywords) 是预定义的、具有特殊含义的标识符 (identifiers)。它们是 Java 语言语法的一部分&#xff0c;被 Java 编译器赋予了特定的功能和用途。因此&#xff0c;你不能将关键字用作变量名、类名、方法名或其他用户自定义的标识符。 …...

发版前后的调试对照实践:用 WebDebugX 与多工具构建上线验证闭环

每次产品发版都是一次“高压时刻”。版本升级带来的不仅是新功能上线&#xff0c;更常伴随隐藏 bug、兼容性差异与环境同步问题。 为了降低上线风险&#xff0c;我们逐步构建了一套以 WebDebugX 为核心、辅以 Charles、Postman、ADB、Sentry 的发版调试与验证流程&#xff0c;…...

若依添加添加监听容器配置(删除键,键过期)

1、配置Redis的键触发事件 # 基础配置 bind 0.0.0.0 # 允许所有IP连接 protected-mode no # 关闭保护模式&#xff08;生产环境建议结合密码使用&#xff09; port 6379 # 默认端口 daemonize no …...

【深度学习-Day 24】过拟合与欠拟合:深入解析模型泛化能力的核心挑战

Langchain系列文章目录 01-玩转LangChain&#xff1a;从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块&#xff1a;四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain&#xff1a;从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...

C++课设:实现简易文件加密工具(凯撒密码、异或加密、Base64编码)

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 专栏介绍&#xff1a;《编程项目实战》 目录 一、初识文件加密&#xff1a;为什么需要…...

Python项目中添加环境配置文件

在Python项目中添加配置文件有多种方式&#xff0c;每种方式对应不同的依赖包和读取方法。以下是 7种主流配置管理方案&#xff0c;包含安装命令、配置示例和变量读取方法&#xff1a; 1. .env 文件&#xff08;推荐简单项目&#xff09; 依赖包: python-dotenv pip install …...

三维GIS开发cesium智慧地铁教程(4)城市白模加载与样式控制

一、添加3D瓦片 <!-- 核心依赖引入 --> <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"><!-- 模型数据路径 --> u…...

手机号段数据库与网络安全应用

手机号段数据库的构成与原理 手机号段数据库存储着海量手机号段及其关联信息&#xff0c;包括号段起始与结束号码、运营商归属、地区编码、卡类型等核心数据。这些数据主要来源于通信管理机构的官方分配信息、运营商的业务更新数据以及合法采集的使用数据。经过数据清洗、校验…...

关键字--sizeof

sizeof 是 C 中的一个编译时运算符&#xff0c;用于获取一个类型或对象在内存中所占的字节数&#xff08;单位&#xff1a;字节&#xff0c;byte&#xff09;。 用法 获取类型的大小&#xff1a; std::cout << sizeof(int) << std::endl; // 输出int类型的字节数…...

Qt客户端技巧 -- 窗口美化 -- 圆角窗口

不解析&#xff0c;直接给代码例子 利用窗口重绘事件处理函数paintEvent main.cpp #include <QtCore/qglobal.h> #if QT_VERSION > 0x050000 #include <QtWidgets/QApplication> #else #include <QtGui/QApplication> #endif#include "roundedwin…...