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

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

前端上传文件很大时,会出现各种问题,比如连接超时了,网断了,都会导致上传失败,这个时候就需要将文件切片上传,下面我们就来学习一下如何使用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,一经查实,立即删除!

相关文章

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

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

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形式控制输入形…

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

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

有关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产品精…

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

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

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

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

牛客热题:矩阵最长递增路径

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 牛客热题&#xff1a;矩阵最长递增路径题目链接方法一…

leetcode155 最小栈

题目 设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶部的元素。i…

关于LPC1768在线升级的实现的注意事项

开发环境&#xff1a;Keil 5 uVision V5.38.0.0 1、使用IAP的方式&#xff08;应用程序在线编程&#xff09; 2、在Flash中构建用于升级的不同区域 &#xff08;1&#xff09;引导区 &#xff08;2&#xff09;APP区 &#xff08;3&#xff09;升级代码存放区域 注意&…

阿里影业业绩大涨,除了沸腾的文娱市场还有什么原因?

影视文娱市场的火热仍在延续&#xff0c;新定档的电影和演出&#xff0c;不断引人关注这个行业的生机。而这个行业的支持者、受益者&#xff0c;就站在一线。 5月29日晚&#xff0c;阿里影业发布截至2024年3月31日的2024财年业绩公告。社会文娱消费的热情&#xff0c;对优质项…

深度学习笔记:2.Jupyter Notebook

Jupyter Notebook 常用操作快捷键魔法指令_jupyter notebook快捷键调用函数-CSDN博客https://blog.csdn.net/qq_26917905/article/details/137211336?ops_request_misc%257B%2522request%255Fid%2522%253A%2522171748112816800182160793%2522%252C%2522scm%2522%253A%25222014…

视频生成框架EasyAnimate正式开源!

近期&#xff0c;Sora模型的热度持续上涨&#xff0c;社区中涌现了一些类Sora的开源项目&#xff0c;这些项目均基于Diffusion Transformer结构&#xff0c;使用Transformer结构取代了UNet作为扩散模型的基线&#xff0c;旨在生成更长、更高分辨率、且效果更好的视频。EasyAnim…

罗德与施瓦茨RS SMA100A 9KHZ-3GHZ或6GHZ信号发生器

R&S SMA100A 提供信号质量、速度和灵活性。R&S SMA100A 是一款高级模拟发生器&#xff0c;因其出色的特性而树立了标准。 它结合了卓越的信号质量和极高的设置速度。无论是在开发、生产、服务还是维护方面&#xff0c;R&SSMA100A 都能出色地完成任务。 罗德与施瓦茨…

空调外机清洁机器人设计

现在的空调&#xff0c;有很多安装在高层&#xff0c;一旦安装使用后&#xff0c;外机几乎不可能再清洗。因为费用高&#xff0c;清洁工人的钱应该是好几百还不止&#xff1b;清洁风险高&#xff0c;空调师傅需要高空作业&#xff0c;如果发生意外业主难以承担。但空调运行几年…