赞
踩
对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率。
本文是基于 springboot + vue 实现的文件上传,本文主要介绍vue实现文件上传的步骤及代码实现,服务端(springboot)的实现步骤及实现请移步本人的另一篇文章:
springboot 大文件上传、分片上传、断点续传、秒传https://blog.csdn.net/qq_43040552/article/details/122510154
本人分析上传总共分为:
文件上传:
- import md5 from 'js-md5' //引入MD5加密
- import UpApi from '@/api/common.js'
- import { concurrentExecution } from '@/utils/jnxh'
-
- /**
- * 文件分片上传
- * @params file {File} 文件
- * @params pieceSize {Number} 分片大小 默认3MB
- * @params concurrent {Number} 并发数量 默认2
- * @params process {Function} 进度回调函数
- * @params success {Function} 成功回调函数
- * @params error {Function} 失败回调函数
- */
- export const uploadByPieces = ({
- file,
- pieceSize = 3,
- concurrent = 3,
- success,
- process,
- error
- }) => {
- // 如果文件传入为空直接 return 返回
- if (!file || file.length < 1) {
- return error('文件不能为空')
- }
- let fileMD5 = '' // 总文件列表
- const chunkSize = pieceSize * 1024 * 1024 // 1MB一片
- const chunkCount = Math.ceil(file.size / chunkSize) // 总片数
- const chunkList = [] // 分片列表
- let uploaded = [] // 已经上传的
- let fileType = '' // 文件类型
- // 获取md5
- /***
- * 获取md5
- **/
- const readFileMD5 = () => {
- // 读取视频文件的md5
- fileType = file.name.substring(file.name.lastIndexOf('.') + 1, file.name.length)
- console.log('获取文件的MD5值')
- let fileRederInstance = new FileReader()
- console.log('file', file)
- fileRederInstance.readAsBinaryString(file)
- fileRederInstance.addEventListener('load', e => {
- let fileBolb = e.target.result
- fileMD5 = md5(fileBolb)
- var index = file.name.lastIndexOf('.')
- var tp = file.name.substring(index + 1, file.name.length)
- let form = new FormData()
- form.append('filename', file.name)
- form.append('identifier', fileMD5)
- form.append('objectType', fileType)
- form.append('chunkNumber', 1)
- UpApi.uploadChunk(form).then(res => {
- if (res.skipUpload) {
- console.log('文件已被上传')
- success && success(res)
- } else {
- // 判断是否是断点续传
- if (res.uploaded && res.uploaded.length != 0) {
- uploaded = [].concat(res.uploaded)
- }
- console.log('已上传的分片:' + uploaded)
- // 判断是并发上传或顺序上传
- if (concurrent == 1 || chunkCount == 1) {
- console.log('顺序上传')
- sequentialUplode(0)
- } else {
- console.log('并发上传')
- concurrentUpload()
- }
- }
- }).catch((e) => {
- console.log('文件合并错误')
- console.log(e)
- })
- })
- }
- /***
- * 获取每一个分片的详情
- **/
- const getChunkInfo = (file, currentChunk, chunkSize) => {
- let start = currentChunk * chunkSize
- let end = Math.min(file.size, start + chunkSize)
- let chunk = file.slice(start, end)
- return {
- start,
- end,
- chunk
- }
- }
- /***
- * 针对每个文件进行chunk处理
- **/
- const readChunkMD5 = () => {
- // 针对单个文件进行chunk上传
- for (var i = 0; i < chunkCount; i++) {
- const {
- chunk
- } = getChunkInfo(file, i, chunkSize)
-
- // 判断已经上传的分片中是否包含当前分片
- if (uploaded.indexOf(i + '') == -1) {
- uploadChunk({
- chunk,
- currentChunk: i,
- chunkCount
- })
- }
- }
- }
- /***
- * 原始上传
- **/
- const uploadChunk = (chunkInfo) => {
- var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
- console.log(sd, '进度')
- process(sd)
- console.log(chunkInfo, '分片大小')
- let inde = chunkInfo.currentChunk + 1
- if (uploaded.indexOf(inde + '') > -1) {
- const {
- chunk
- } = getChunkInfo(file, chunkInfo.currentChunk + 1, chunkSize)
- uploadChunk({
- chunk,
- currentChunk: inde,
- chunkCount
- })
- } else {
- var index = file.name.lastIndexOf('.')
- var tp = file.name.substring(index + 1, file.name.length)
- // 构建上传文件的formData
- let fetchForm = new FormData()
- fetchForm.append('identifier', fileMD5)
- fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1)
- fetchForm.append('chunkSize', chunkSize)
- fetchForm.append('currentChunkSize', chunkInfo.chunk.size)
- const chunkfile = new File([chunkInfo.chunk], file.name)
- fetchForm.append('file', chunkfile)
- // fetchForm.append('file', chunkInfo.chunk)
- fetchForm.append('filename', file.name)
- fetchForm.append('relativePath', file.name)
- fetchForm.append('totalChunks', chunkInfo.chunkCount)
- fetchForm.append('totalSize', file.size)
- fetchForm.append('objectType', tp)
- // 执行分片上传
- let config = {
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': '*/*'
- }
- }
-
- UpApi.uploadChunk(fetchForm, config).then(res => {
-
- if (res.code == 200) {
- console.log('分片上传成功')
- uploaded.push(chunkInfo.currentChunk + 1)
- // 判断是否全部上传完
- if (uploaded.length == chunkInfo.chunkCount) {
- console.log('全部完成')
- success(res)
- process(100)
- } else {
- const {
- chunk
- } = getChunkInfo(file, chunkInfo.currentChunk + 1, chunkSize)
- uploadChunk({
- chunk,
- currentChunk: chunkInfo.currentChunk + 1,
- chunkCount
- })
- }
-
- } else {
- console.log(res.msg)
- }
-
- }).catch((e) => {
- error && error(e)
- })
- // if (chunkInfo.currentChunk < chunkInfo.chunkCount) {
- // setTimeout(() => {
- //
- // }, 1000)
- // }
- }
- }
- /***
- * 顺序上传
- **/
- const sequentialUplode = (currentChunk) => {
- const {
- chunk
- } = getChunkInfo(file, currentChunk, chunkSize)
- let chunkInfo = {
- chunk,
- currentChunk,
- chunkCount
- }
- var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
- process(sd)
- console.log('当前上传分片:' + currentChunk)
- let inde = chunkInfo.currentChunk + 1
- if (uploaded.indexOf(inde + '') > -1) {
- console.log('分片【' + currentChunk + '】已上传')
- sequentialUplode(currentChunk + 1)
- } else {
- let uploadData = createUploadData(chunkInfo)
- let config = {
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': '*/*'
- }
- }
- // 执行分片上传
- UpApi.uploadChunk(uploadData, config).then(res => {
- if (res.code == 200) {
- console.log('分片【' + currentChunk + '】上传成功')
- uploaded.push(chunkInfo.currentChunk + 1)
- // 判断是否全部上传完
- if (uploaded.length == chunkInfo.chunkCount) {
- console.log('全部完成')
- success(res)
- process(100)
- } else {
- sequentialUplode(currentChunk + 1)
- }
-
- } else {
- console.log(res.msg)
- }
-
- }).catch((e) => {
- error && error(e)
- })
- }
- }
- /***
- * 并发上传
- **/
- const concurrentUpload = () => {
- for (var i = 0; i < chunkCount; i++) {
- chunkList.push(Number(i))
- }
- console.log('需要上传的分片列表:' + chunkList)
- concurrentExecution(chunkList, concurrent, (curItem) => {
- return new Promise((resolve, reject) => {
- const {
- chunk
- } = getChunkInfo(file, curItem, chunkSize)
- let chunkInfo = {
- chunk,
- currentChunk: curItem,
- chunkCount
- }
- var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
- process(sd)
- console.log('当前上传分片:' + curItem)
- let inde = chunkInfo.currentChunk + 1
- if (uploaded.indexOf(inde + '') == -1) {
- // 构建上传文件的formData
- let uploadData = createUploadData(chunkInfo)
- // 请求头
- let config = {
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': '*/*'
- }
- }
- UpApi.uploadChunk(uploadData, config).then(res => {
- if (res.code == 200) {
- uploaded.push(chunkInfo.currentChunk + 1)
- console.log('已经上传完成的分片:' + uploaded)
- // 判断是否全部上传完
- if (uploaded.length == chunkInfo.chunkCount) {
- success(res)
- process(100)
- }
- resolve()
- } else {
- reject(res)
- console.log(res.msg)
- }
-
- }).catch((e) => {
- reject(res)
- error && error(e)
- })
- } else {
- console.log('分片【' + chunkInfo.currentChunk + '】已上传')
- resolve()
- }
- })
- }).then(res => {
- console.log('finish', res)
- })
- }
- /***
- * 创建文件上传参数
- **/
- const createUploadData = (chunkInfo) => {
- let fetchForm = new FormData()
- fetchForm.append('identifier', fileMD5)
- fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1)
- fetchForm.append('chunkSize', chunkSize)
- fetchForm.append('currentChunkSize', chunkInfo.chunk.size)
- const chunkfile = new File([chunkInfo.chunk], file.name)
- fetchForm.append('file', chunkfile)
- // fetchForm.append('file', chunkInfo.chunk)
- fetchForm.append('filename', file.name)
- fetchForm.append('relativePath', file.name)
- fetchForm.append('totalChunks', chunkInfo.chunkCount)
- fetchForm.append('totalSize', file.size)
- fetchForm.append('objectType', fileType)
- return fetchForm
- }
- readFileMD5() // 开始执行代码
- }
并发控制:
- /**
- * 并发执行
- * @params list {Array} - 要迭代的数组
- * @params limit {Number} - 并发数量控制数,最好小于3
- * @params asyncHandle {Function} - 对`list`的每一个项的处理函数,参数为当前处理项,必须 return 一个Promise来确定是否继续进行迭代
- * @return {Promise} - 返回一个 Promise 值来确认所有数据是否迭代完成
- */
- export function concurrentExecution(list, limit, asyncHandle) {
- // 递归执行
- let recursion = (arr) => {
- // 执行方法 arr.shift() 取出并移除第一个数据
- return asyncHandle(arr.shift()).then(() => {
- // 数组还未迭代完,递归继续进行迭代
- if (arr.length !== 0) {
- return recursion(arr)
- } else {
- return 'finish'
- }
- })
- }
- // 创建新的并发数组
- let listCopy = [].concat(list)
- // 正在进行的所有并发异步操作
- let asyncList = []
- limit = limit > listCopy.length ? listCopy.length : limit
- console.log(limit)
- while (limit--) {
- asyncList.push(recursion(listCopy))
- }
- // 所有并发异步操作都完成后,本次并发控制迭代完成
- return Promise.all(asyncList)
- }
Gitee:文件分片上传https://gitee.com/wangxmsn/domes/tree/develop/file-sharding-upload
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。