学堂 学堂 学堂公众号手机端

vue项目中实现大文件分片上传要做什么,步骤是怎样

lewis 1年前 (2024-04-28) 阅读数 20 #技术
在实际应用中,我们有时候会遇到“vue项目中实现大文件分片上传要做什么,步骤是怎样”这样的问题,我们该怎样来处理呢?下文给大家介绍了解决方法,希望这篇“vue项目中实现大文件分片上传要做什么,步骤是怎样”文章能帮助大家解决问题。



对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率。

本文是基于 springboot + vue 实现的文件上传,本文主要介绍vue实现文件上传的步骤及代码实现,服务端(springboot)的实现步骤及实现请移步本人的另一篇文章:

springboot 大文件上传、分片上传、断点续传、秒传

上传分步:

本人分析上传总共分为:

MD5读取文件,获取文件的MD5编码 请求服务端判断文件是否上传,如上传完成就直接返回文件地址 如未上传,判断是否是断点续传 判断是并发上传还是顺序上传 开始分片文件上传,分片上传完成后写入已上传列表中 判断是否上传完成

直接上代码

文件上传:

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)
}

现在大家对于vue项目中实现大文件分片上传要做什么,步骤是怎样的内容应该都有一定的认识了吧,希望这篇能对大家有所帮助。最后,想要了解更多,欢迎关注博信,博信将为大家推送更多相关的文章。
版权声明

本文仅代表作者观点,不代表博信信息网立场。

热门