文件切片上传

182 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 21 天,点击查看活动详情

切片上传

思路,由于大文件上传需要时间较长,而由于网络波动导致文件上传失败,需要重新上传,从而使用户精神崩溃,所以人们想到了分片上传。所谓分片上传就是把大文件切成小文件然后将小文件传到服务器,小文件传输有专门接口,小文件的命名都是 xxxxx_0.后缀。xxxxx_1.后缀,文件名都是相同的,让后端知道这些小文件是一个大文件的片段,但是为了做到这一点,首先要保证文件名唯一且固定,且同一个文件上传多次分片名也一样,就是由文件内容生成的hash,因为这样能保证同一个文件每次都会当成同一个文件对待,才能从根本上做到继续。就是干了一件事干了一半了,然后继续干这件事,这件事必须是唯一的。

生成内容 hash

首先有这么一个方法,利用FileReader类读取文件类型为buffer格式,然后利用sparkMd5.ArrayBuffer,使用文件内容生成hash,最后得到了文件hash和后缀


export const readFileBuffer = (file) => {
  return new Promise((resove, reject) => {
    const fileReader = new FileReader()
    fileReader.readAsArrayBuffer(file)
    fileReader.onload = (e) => {
      const buffer = e.target.result
      const spark = new sparkMd5.ArrayBuffer()
      spark.append(buffer)
      const HASH = spark.end()
      const suffix = /.([a-zA-Z0-9]+)$/.exec(file.name)[1]
      resove({ buffer, HASH, suffix, filename: `${HASH}.${suffix}` })
    }
    fileReader.onerror = (e) => {
      reject('错误')
    }
  })
}

获取已经上传的切片 和秒传

// 请求接口,通过文件hash,获取当前服务器上是否有该文件的切片或者有这个文件,如果有这个文件了,就直接返回成功,直接把文件地址返回,就是所谓的秒传 当然这里没做秒传成功的处理。

   let already = []
    try {
  
      const res = await uploadAlready({ params: { HASH } })
      if (res.code === 0) {
        already = res.fileList
      } else {
        throw new Error()
      }
    } catch {
      message.error('当前切片上传失败')
    }

文件切片的规格

首先默认切片大小为1Mb,然后根据这个规格算一下切片个数,如果超过了100个切片,那么就重新算一个单个切片的大小,不能传过多的切片。可能单个切片体积大一点,比较小的文件切片大小就是1M,大的就是100分之1

   // 文件切片
    let max = 1024 * 1024
    let count = Math.ceil(file.size / max)
    if (count > 100) {
      count = 100
      max = Math.ceil(file.size / count)
    }

切片

利用文件类型原型对象上的slice方法,将文件切分成上面的规格大小

 let chunks = []
    for (let i = 0; i < count; i++) {
      chunks.push({
        file: file.slice(i * max, (i + 1) * max),
        filename: `${HASH}_${i}.${suffix}`,
      })
    }
   
  
  

上传成功一个切片时要做的事

首先定义一个变量保存完成的切片的个数,完成一个就算一下完成的个数占总数的多少,算出进度,如果完成的个数和总数相同,那么,这个文件就传完了。

 let complateCount = 0
    const compalte = async () => {
      complateCount++
      const p = parseInt((complateCount / count) * 100) + '%'
      setProgress(p)
      if (complateCount === count) {
        setProgress('100%')
        try {
          const res = await uploadMerge({ HASH, count })
          if (res.code === 0) {
            message.success('上传成功')
            await delay()
            clearFile()
          } else {
            throw new Error('xxx')
          }
        } catch (error) {
          message.error('合并错误')
          await delay()
          clearFile()
        }
      }
    }

上传

有多少切片发送多少次上传请求,所以不能一下都发送了,不用说服务器受不了,浏览器也受不了。每次截取10个,就可以,当然高端操作是先测试一下,然后根据情况再决定数量,上传操作和普通上传一样

  while (chunks.length > 0) {
      const list = chunks.splice(0, 10)
      const promiseList = list.map(async (chunk, index) => {
        if (already.includes(chunk.filename)) {
          compalte()
          return
        }
        let formData = new FormData()
        formData.append('file', chunk.file)
        formData.append('filename', chunk.filename)
        try {
          const res = await uploadChunk(formData)
          if (res.code === 0) {
            compalte()
          } else {
            throw Error(res.codeText)
          }
        } catch (error) {
          message.error('上传失败,稍后重试')
          clearFile()
        }
      })
     const res= await promiseList()
     console.log(res);
    }