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

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

前端上传文件很大时,会出现各种问题,比如连接超时了,网断了,都会导致上传失败,这个时候就需要将文件切片上传,下面我们就来学习一下如何使用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.rhkb.cn/news/341726.html

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

相关文章

【前端】响应式布局笔记——flex

二、Flex Flex(FlexiableBox:弹性盒子&#xff0c;用于弹性布局&#xff0c;配合rem处理尺寸的适配问题)。 1、flex-direction:子元素在父元素盒子中的排列方式。 父级元素添加&#xff1a;flex-direction: row; 父级元素添加&#xff1a;flex-direction: row-reverse; 父…

HTML旋转照片盒子

效果图 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compatible" content…

Vue05-数据绑定

一、数据绑定 1-1、v-bind指令 1-2、v-model指令 1、单项数据绑定&#xff1a; 2、双向数据绑定 注意&#xff1a; 表单元素&#xff0c;必须要有属性&#xff1a;value&#xff01;&#xff01;&#xff01; 1-3、小结

【常见报错】影刀小窗口消失-作者:【小可耐教你学影刀RPA】

现象描述&#xff1a; 影刀能够正常登录并运行&#xff0c;但是从常规模式切换到调度模式后能出现启动页&#xff0c;然后程序就退出了&#xff0c;查看影刀日志和事件查看器中的日志都没有任何异常消息 问题原因&#xff1a; 正常切换调度后会在窗口右下角出现一个机器人的小…

C++基础编程100题-004 OpenJudge-1.1-06 空格分隔输出

更多资源请关注纽扣编程微信公众号 http://noi.openjudge.cn/ch0101/06/ 描述 读入一个字符&#xff0c;一个整数&#xff0c;一个单精度浮点数&#xff0c;一个双精度浮点数&#xff0c;然后按顺序输出它们&#xff0c;并且要求在他们之间用一个空格分隔。输出浮点数时保留…

这才是大模型价格战背后的真相

想必大家今天肯定被各家大模型厂商的降价新闻刷圈了&#xff0c;如果说 Meta Llama 3 的开源是国外大模型市场的搅局者&#xff0c;那 DeepSeek-V2 就是国内大模型市场的鲶鱼&#xff0c;但是价格战背后是大模型基础设施优化带来的物美价廉&#xff0c;还是浑水摸鱼的噱头&…

【C++小知识】为什么C语言不支持函数重载,而C++支持

为什么C语言不支持函数重载&#xff0c;而C支持 编译链接过程函数名修饰过程总结 在了解C函数重载前&#xff0c;如果对文件的编译与链接不太了解。可以看看我之前的一篇文章&#xff0c;链接: 文件的编译链接 想要清楚为什么C语言不支持函数重载而C支持&#xff0c;有俩个过程…

HTML跨年烟花

目录 写在前面 关于小编 HTML简介 程序设计 系列文章 写在后面 写在前面 学会了这个html烟花秀&#xff0c;跨年就不缺文案喽~ 关于小编 平易近人&#xff0c;慈眉善目&#xff0c;爱交朋友&#xff0c;舍己为人&#xff0c;和蔼可亲&#xff0c;能说会道&#xff0c;…

NAT端口映射,实现外网访问内网服务器

目录 前言一、搭建网络拓扑1.1 配置server和pc1.1.1 配置server01.1.2 配置server11.1.3 配置pc0 1.2 配置客户路由器1.2.1 配置路由器IP1.2.2 配置静态路由 1.3 配置ISP路由器 二、配置端口映射2.1 在客户路由器配置端口映射2.2 测试公网计算机访问私网服务器2.2.1 PC0向serve…

Kafka消费者api编写教程

1.基本属性配置 输入new Properties().var 回车 //创建属性Properties properties new Properties();//连接集群properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"node1:9092,node2:9092");//反序列化properties.put(ConsumerConfig.KEY_DESERIALIZER_CL…

web刷题记录(3)

[NISACTF 2022]checkin 简单的get传参,好久没做过这么简单的题了 王德发&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff01;&#xff0c;看了源代码以后&#xff0c;本来以为是js脚本的问题&#xff0c;但是禁用js脚本没用&#xff0c;看了大佬的wp以后…

ThinkPHP5发送邮件如何配置?有哪些技巧?

ThinkPHP5发送邮件的性能怎么优化&#xff1f;批量发信的方法&#xff1f; 邮件发送功能是许多应用程序的关键组成部分&#xff0c;尤其是在用户注册、密码重置和通知等功能中尤为重要。AokSend将详细介绍如何在thinkphp5中配置和使用邮件发送功能&#xff0c;并确保你可以轻松…

我们如何利用 0 美元营销将 UX/UI 产品发展到 320k 用户

嘿 &#x1f44b; 我是 Paul&#xff0c;FlowMapp 的联合创始人。 现在&#xff0c;我们是一个由7人&#xff08;少数兼职成员&#xff09;组成的团队&#xff0c;试图将产品扩展到$ 1M ARR。 希望这些对您有所帮助&#xff0c;并祝您未来的产品好运&#xff01; 我决定与…

17K star,一款开源免费的手机电脑无缝同屏软件

导读&#xff1a;白茶清欢无别事&#xff0c;我在等风也等你。 作为程序员&#xff0c;在我们的工作中经常需要把手机投票到电脑进行调试工作&#xff0c;选择一款功能强大的投屏软件是一件很必要的事情。今天给大家介绍一款开源且免费的投屏软件&#xff0c;极限投屏&#xff…

jmeter多用户登录并退出教程

有时候为了模拟更真实的场景&#xff0c;在项目中需要多用户登录并退出操作&#xff0c;大致参考如下 多用户登录前面已经实现&#xff1a;参考博文 多用户登录并退出jmx文件&#xff1a;百度网盘 提取码&#xff1a;0000 一、多用户退出操作 添加一个setUp线程组&#xff0…

FPGA-ARM架构与分类

ARM架构&#xff0c;曾称进阶精简指令集机器&#xff08;Advanced RISC Machine&#xff09;更早称作Acorn RISC Machine&#xff0c;是一个32位精简指令集&#xff08;RISC&#xff09;处理器架构。 主要是根据FPGA zynq-7000的芯片编写的知识思维导图总结,废话不多说自取吧 …

三十七、openlayers官网示例Earthquakes Heatmap解析——在地图上加载热力图

官网demo地址&#xff1a; Earthquakes Heatmap 这篇主要介绍了热力图HeatmapLayer HeatmapLayer 是一个用于在地图上显示热力图的图层类型&#xff0c;通常用于表示地理数据中的密度或强度。例如&#xff0c;它可以用来显示地震、人口密度或其他空间数据的热点区域。在这个示…

排序(前篇)

1.排序的概念及其运用 2.插入排序的概念及实现 3.希尔排序的概念及实现 4.选择排序概念及实现 总代码&#xff08;对比各个排序在大量的数据情况排序所化的时间&#xff09;&#xff1a; 1.排序的概念及其运用 1.1排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使…

纯血鸿蒙开发教程:如何实现运动饮食卡片效果

开发背景 人们对健康的要求越来越高&#xff0c;从单纯的健康饮食到健康运动&#xff0c;再到两者的结合。但是&#xff0c;饮食和运动之间的平衡一般人很难掌握&#xff0c;而我们这款 APP 将饮食、运动、以及自身身体状况&#xff08;如体脂、体重、内脂等&#xff09;有机结…

保护关键业务资产的四个步骤

提到 “关键资产 ”&#xff0c;相信大家并不陌生&#xff0c;它是企业 IT 基础设施中对组织运作至关重要的技术资产。如果这些资产&#xff08;如应用服务器、数据库或特权身份&#xff09;出现问题&#xff0c;势必会对企业安全态势造成严重影响。 但每项技术资产都被视为关…