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

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

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

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

item.md5 = md5

res.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.result

spark.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 = bytes

item.md5 = md5

res.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.result

spark.append(bytes)

const md5 = spark.end()

resolve({ bytes, md5 })

}

})

}

const mergeData = (chunks, fileName, options) => {

const fileId = getUUID() // 这里更好的方式是读整个文件的 MD5

const 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 = 1

for (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) return

if (event.data.type != 'file') return

console.log('worker success', event)

const { file, chunkSize } = event.data

const 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') return

const 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,如果你们弄出来了,或者知道为什么,可以在下面留言~

#前端##机械只有转码才有出路吗?##软件开发薪资爆料##2022届毕业生现状#

相关数据流

机场的手推车怎么使用,机场手推车上飞机前放哪
假的365不让提款怎么办

机场的手推车怎么使用,机场手推车上飞机前放哪

⌚ 11-23 👁️‍🗨️ 7692
武汉市dns
勤策365

武汉市dns

⌚ 07-22 👁️‍🗨️ 7828