面试官:如何实现大文件切片上传?

公众号:程序员白特,关注我,每天进步一点点~

前端上传文件很大时,会出现各种问题,比如连接超时了,网断了,都会导致上传失败,这个时候就需要将文件切片上传,下面我们就来学习一下如何使用vue实现大文件切片上传吧

大文件为什么要切片上传

前端上传文件很大时,会出现各种问题,比如连接超时了,网断了,都会导致上传失败;

服务端限制了单次上传文件的大小;

项目实际场景

客户端需要上传一个算法包文件到服务器,这个算法包实测 3.7G

nginx配置文件 上传文件大小最大值为100M

切片上传原理

通过file.slice将大文件chunks切成许多个大小相等的chunk

将每个chunk上传到服务器

服务端接收到许多个chunk后,合并为chunks

第一版

先对文件按指定大小进行切片

/*** file: 需要切片的文件* chunkSize: 每片文件大小,1024*1024=1M*/
chunkSlice(file, chunkSize) {const chunks = [],size = file.size,total = Math.ceil(size / chunkSize)for (let i = 0; i < size; i += chunkSize) {chunks.push({total,blob: file.slice(i, i + chunkSize),})}return chunks
}

处理切片后的文件,后端想要我传给他一个json对象,所以使用readAsDataURL读取文件

这里使用了一个插件spark-md5来生成每个切片的MD5

async handleFile(chunks) {const res = []for (const item of chunks) {const { bytes, md5 } = await this.addMark(item.blob)item.blob = bytesitem.md5 = md5res.push(md5)}return res
},
// 使用FileReader读取每一片数据,并生成MD5编码
async addMark(chunk) {return new Promise((resolve, reject) => {const reader = new FileReader()const spark = new SparkMD5()reader.readAsDataURL(chunk)reader.onload = function (e) {const bytes = e.target.resultspark.append(bytes)const md5 = spark.end()resolve({ bytes, md5 })}})
},

组装数据,包括每一片的排列顺序index,总共切了多少片total,文件IDfileID,每一片的md5编码md5,每一片数据fileData

mergeData(chunks) {const fileId = this.getUUID()const data = []for (let i = 0; i < chunks.length; i++) {const obj = {fileId,fileData: chunks[i].blob,//每片切片的数据fileIndex: i + 1,//每片数据索引fileTotal: chunks[i].total + '',md5: chunks[i].md5,}data.push(obj)}return { data, fileId }
},

上传文件,这里使用并发上传文件,提升文件上传速度

const chunks = chunkSlice(file,1024*1024)
this.handleFile(chunks)
const data = this.mergeData(chunks)for(let i = 0; i < data.length; i++){this.uplload(data[i])
}

第一版遇到的问题

文件太大,切片太小,上传接口的timeout太短,并发请求时,全都在pendding,导致请求出错

第一版问题解决

对上传文件接口的timeout修改,调整时长,大一点

限制每次并发的数量,我用的是500个每次

第二版,切片 + web worker

为什么要使用web worker

在生成文件MD5编码时,需要读文件,是一个I/O操作,会阻塞页面,文件太大,导致页面卡死

将耗时操作转移到worker线程,主页面就不会卡住

vue2,使用worker

yarn add worker-loader

vue.config.js 配置

// vue.config.js
chainWebpack(config) {config.module.rule('worker').test(/\.worker\.js$/).use('worker-loader').loader('worker-loader')// .options({ inline: 'fallback' })// 这个配置是个坑,不要加
},

新建file.worker.js

// file.worker.js
import SparkMD5 from 'spark-md5'const chunkSlice = (file, chunkSize) => {const chunks = [],size = file.size,total = Math.ceil(size / chunkSize)for (let i = 0; i < size; i += chunkSize) {chunks.push({total,blob: file.slice(i, i + chunkSize),})}return chunks
}
const handleFile = async (chunks) => {const res = []for (const item of chunks) {const { bytes, md5 } = await addMark(item.blob)item.blob = bytesitem.md5 = md5res.push(md5)}return res
}
const addMark = (chunk) => {return new Promise((resolve, reject) => {const reader = new FileReader()const spark = new SparkMD5()reader.readAsDataURL(chunk)reader.onload = function (e) {const bytes = e.target.resultspark.append(bytes)const md5 = spark.end()resolve({ bytes, md5 })}})
}
const mergeData = (chunks, fileName, options) => {const fileId = getUUID() // 这里更好的方式是读整个文件的 MD5const data = []for (let i = 0; i < chunks.length; i++) {const obj = {...options,suffix: '.tar.gz',fileId,fileName,fileData: chunks[i].blob,fileIndex: i + 1 + '',fileTotal: chunks[i].total + '',md5: chunks[i].md5,}data.push(obj)}return { data, fileId }
}
const getUUID = () => {return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16))
}
const dataSlice = (data, step, fileId) => {const total = Math.ceil(data.length / step)let index = 1for (let i = 0; i < data.length; i += step) {const params = {type: 'workerFile',index,total,fileId,data: data.slice(i, i + step),}self.postMessage(params)index++}
}
self.addEventListener('error', (event) => {console.log('worker error', event)
})self.addEventListener('message', async (event) => {// 确保接受的是我想要的消息  if (!event.data.type) returnif (event.data.type != 'file') returnconsole.log('worker success', event)const { file, chunkSize } = event.dataconst chunks = chunkSlice(file, chunkSize)const allMD5 = await handleFile(chunks)console.log(allMD5)// 此处 allMD5 可用来做后续的断点续传const { data, fileId } = mergeData(chunks, file.name)// 这里对处理好的数据进行切片,分片传递给主线程,是由于 Web Worker 试图将大量数据复制到主线程中,会导致内存溢出。dataSlice(data, 100, fileId)})

这个报错一般是在使用 JavaScript Web Worker 时出现的,通常是由于 Web Worker 试图将大量数据复制到主线程中,导致内存溢出所引起的。

主进程使用

// xxx.vue文件
import Worker from '@/utils/worker/file.worker.js'const worker = new Worker()
worker.postMessage({ type: 'file', file: this.curFile, chunkSize: 1024 * 1024 })worker.onerror = (error) => {console.log('main error', error)worker.terminate()
}const finalData = []
worker.onmessage = async (event) => {console.log('main success', event)if (event.data.type != 'workerFile') returnconst fileId = mergeWorkerData(finalData, event.data)if (fileId) {worker.terminate()const status = await stepLoad(finalData, 500)if (!status) {this.$message.error('文件上传失败')} else {this.$message.success('文件上传成功')}}
}mergeWorkerData = (res, params) => {res.push(...params.data)return params.index == params.total ? params.fileId : false
}const stepLoad = async (data, step) => {const res = []for (let i = 0; i < data.length; i += step) {res.push(data.slice(i, i + step))}for (const item of res) {const chunkRes = await Promise.all(item.map((v) => this.$api.upload(v)))if (chunkRes.some((v) => v.httpCode != 0)) {return false}const isEnd = chunkRes.filter((v) => v.finish)if (isEnd.length) {return true}}
}

总结

worker引入脚本或三方库可以使用importScript(),但是我没弄成功,一使用importScript()就会报错,Renference: importScript() xxxxxxxxxxxx,如果你们弄出来了,或者知道为什么,可以在下面留言~

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

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

相关文章

Python filter()用法:深入解析与实战应用

Python filter()用法&#xff1a;深入解析与实战应用 在Python编程中&#xff0c;filter() 函数是一个内置的高阶函数&#xff0c;它用于过滤序列&#xff0c;过滤掉不符合条件的元素&#xff0c;返回由符合条件元素组成的新列表。该函数在数据处理和筛选时非常有用&#xff0…

计算机系统基础知识-经典题目

【第1题】 对计算机评价的主要性能指标有时钟频率、 (1) 、运算精度和内存容量等。对数据库管理系统评价的主要性能指标有 (2) 、数据库所允许的索引数量和最大并发事务处理能力等。 (1) A.丢包率 B.端口吞吐量 C.可移植性 D.数据处理速率 (2) A.MIPS B.支持协议和标准 C.最大…

【设计模式】工厂模式(创建型)⭐⭐⭐

文章目录 1.概念1.1 什么是工厂模式1.2 优点与缺点 2.实现方式2.1 简单工厂模式&#xff08;Simple Factory&#xff09;2.2 简单工厂模式缺点2.3 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09; 3. Java 哪些地方用到了工厂模式4. Spring 哪些地方用到了工厂模…

【SpringBoot】SpringBoot同时可以处理多少请求

目录 问题Web三大容器三者区别TomcatJetty小结 最大连接数和最大等待数同时处理请求数拓展&#xff1a;设置Web容器设置容器为Jetty设置容器为Undertow 问题 之前看到过一个面试题&#xff1a;SpringBoot同时可以处理多少请求&#xff1f; 准确的来说&#xff0c;Spring Boot…

mysql聚簇索引

1.聚簇索引是物理索引&#xff0c;数据在表里是按顺序存储的&#xff0c;物理上是连续的&#xff0c;一般选主键id作为聚簇索引&#xff0c;且一张表里只能有一个聚簇索引。 2.只有InnoDB支持聚簇索引。 3.非聚簇索引是逻辑索引&#xff0c;将数据的某个字段抽取出来组成独立的…

JVM常用概念之锁粗化和循环

1.什么是锁粗化 锁粗化一般指有效地合并几个相邻的锁定块&#xff0c;从而减少锁定开销。如下述代码所示&#xff1a; 锁粗化前代码&#xff1a; synchronized (obj) {// statements 1 } synchronized (obj) {// statements 2 }锁粗化后代码&#xff1a; synchronized (obj)…

pycharm绘图时中英文不能同时出现 中文出现小框框的问题解决

# 设置字体为微软雅黑&#xff0c;正确显示负号 plt.rcParams[font.sans-serif] [Microsoft YaHei] plt.rcParams[axes.unicode_minus] False

Python 组合序号

import pandas as pd # 创建一个示例数据框 data { group: [A, A, A, B, B, C, C, C, C], value: [3, 1, 2, 5, 4, 6, 9, 7, 8] } df pd.DataFrame(data) # 先按group分组&#xff0c;再按value列升序排序 df_sorted_asc df.sort_values(by[group, value]) # 使…

如何使用vsCode打开intel D435i深度相机

一、下载并安装相机SDK文件 1.SDK下载地址&#xff1a; Release Intel RealSense™ SDK 2.0 (v2.54.2) IntelRealSense/librealsense GitHub 2.下载后&#xff0c;双击即可安装 3.环境配置 1&#xff09;window的开始菜单&#xff0c;搜索环境变量&#xff0c;选择编辑系…

LitCTF2024部分wp

litctf wp 第一次ak了web和misc&#xff0c;非常激动&#xff0c;感谢lictf给我这个机会 最终成果 全靠队里的密码逆向✌带飞。一个人就砍了近一半的分数 这里是我们队的wp web exx 题目名反过来就是xxe&#xff0c;考察xxe&#xff0c;查看登录的数据包 发现传的就是xml…

机器学习中的泛化与适应:深入理解域泛化、域适应、少样本学习、零样本学习、开放世界识别与开放词汇识别

机器学习中的泛化与适应&#xff1a;深入理解域泛化、域适应、少样本学习、零样本学习、开放世界识别与开放词汇识别 &#x1f60e; 作者介绍&#xff1a;我是程序员行者孙&#xff0c;一个热爱分享技术的制能工人。计算机本硕&#xff0c;人工制能研究生。公众号&#xff1a;A…

Java Web学习笔记8——表单

表单标签&#xff1a; 场景&#xff1a;在网页中主要负责数据采集功能&#xff0c;如注册、登录等数据采集。 标签&#xff1a;<form> 表单项、表单元素&#xff1a; 不同类型的input元素、下拉列表、文本域等。 <input>: 定义表单项、通过type形式控制输入形…

echarts系列:echarts中的legend名称最上面被遮挡一部分

在 ECharts 中,遇到 legend 的名称在图表中被遮挡。 被遮挡的原因,通常是因为布局问题,可能涉及到 legend 的位置、尺寸或者是与其他组件的重叠。 通过排查问题,发现以下一些解决 legend 名称被遮挡的方案: 调整 Legend 的位置: 你可以通过改变 legend 的 top, bottom, …

深入解读Prometheus Adapter:云原生监控的核心组件

一、引言 Prometheus Adapter的背景与重要性 在现代的云原生架构中&#xff0c;微服务和容器化技术得到了广泛的应用。这些技术带来了系统灵活性和扩展性的提升&#xff0c;但同时也增加了系统监控和管理的复杂度。Prometheus作为一款开源的监控系统&#xff0c;因其强大的指标…

JAVA面试八股文----Mybatis

1、Mybatis 1.1#{}和${}的区别是什么? Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值; Mybatis在处理 0 时,就是把 0时,就是把 0时,就是把{}替换成变量的值。 有了#{}为什么还需要${}? #{}会被预编译处理,可以有效的防止SQL注…

有关Qt的调用其他cpp文件出现的小问题

一开始出现了运行mainwindow文件过程调用其他cpp文件&#xff0c;而导致运行的ui界面卡住&#xff0c;后来发现在两个cpp文件中都进行了界面的初始化而导致界面的某些控件二次使用&#xff0c;所以会卡住。 ui->setupUi(this); 在Qt框架中&#xff0c;ui->setupUi(this)…

PPP-B2b精密产品使用注意事项及分析

1、因为在使用PPP-B2b进行定轨的时候&#xff0c;发的精密轨道产品是B3频点的&#xff0c;需要改正的卫星质心&#xff08;Com&#xff09;与SP3精密星历对比。 2、PPP-B2b产品吸收了电离层误差&#xff0c;因此电离层提取方面与IGS电离层完全无法对其。 3、由于PPP-B2b产品精…

JS包装类:为什么循环中为什么建议用变量存储str.length进行循环判断?

前言 在Javascript通常我们在遍历一个字符串的时候通常使用的方式是 var str "abcdefg"; for(let i0;i<str.length;i){}但在最近的学习中&#xff0c;有人建议我最好应该是下面这样执行。 var str "abcdefg"; for(let i0,len str.length;i<len;i)…

元宇宙3D品牌营销虚拟场景提升客户对企业的黏性

在这个充满创意与想象的3D元宇宙时代&#xff0c;我们为您推出了全新的3D元宇宙场景在线制作编辑平台&#xff0c;让您轻松构建专属的虚拟展厅&#xff0c;展现无限可能。 3D元宇宙场景在线制作编辑平台允许您快速完成空间设计&#xff0c;根据您的个性化需求&#xff0c;自由设…

适合初学者人手一本的LLM大语言模型综述,爆火全网

今天给大家推荐一本大模型&#xff08;LLM&#xff09;这块的一本外文书&#xff0c;经过整理已经出中文版了&#xff0c;就是这本《大型语言模型综述》&#xff01;本书在git上有9.2k star&#xff0c;还是很不错的一本大模型方面的书。 本教程内容主要内容&#xff1a;中文版…