又学一招!大文件切片上传和断点续传

438 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

在日常的开发中,项目中基本出现的最多的应该就是上传文件,对于文件的上传,我们可以分为多种形式,单文件上传、多文件上传、进度管控、拖拽上传、大文件上传。

今天就把大文件上传来说一下,对于大文件的上传,我们需要知道有俩种做法,一种是切片上传,还有一种是断点续传:切片上传,顾名思义就是把我们的文件按大小或者数量进行分割切成小文件,然后按片进行上传。断点续传则是当我们的文件上传一半的时候,突然断网或者刷新页面,下次再上传该文件的时候,会跳过已经上传的文件片段,从上一次上传的地方继续上传。

为什么要用hash命名文件

上传到服务器的文件,如果俩个用户上传的文件名称相同的话,可能会被识别到文件已经存在,导致后端服务不会去再次上传新的图片,这样就会造成新图片无法上传,并且显示文件已经存在。hash是对上传文件的buffer进行读取,然后通过spkmd5插件来进行转换成hash值,如果文件上传的是一样的,那么buffer的值是一样,转成出来的hash值自然也是一样的,那么上传的时候同一个文件就不会因为hash名不同而存在服务端俩次。虽然文件hash处理一般是在服务端做,但也有可能存在某些情况需要前端来做这件事情。

实现过程

<div class="container">
        <div class="item">
            <h3>大文件上传</h3>
            <section ref="upload7" class="upload_box" id="upload7">
                <input  type="file" class="upload_inp" ref="upload_inp">
                <div class="upload_button_box">
                    <button class="upload_button select" ref="upload_button_select">上传图片</button>
                </div>
                <div class="upload_progress" ref="upload_progress">
                    <div class="value" ref="upload_progress_value"></div>
                </div>
            </section>
        </div>
    </div>

image.png

切片操作

let   upload7 = ref(null),
      upload_inp = ref(null),
      upload_button_select = ref(null),
      upload_progress = ref(null),
      upload_progress_value = ref(null)
function init(){
      upload7 = upload7.value
      upload_inp = upload_inp.value
      upload_button_select = upload_button_select.value
      upload_progress = upload_progress.value
      upload_progress_value = upload_progress_value.value
      upload_button_select.addEventListener('click',function(){
        upload_inp.click()
      })
      upload_inp.addEventListener('change',async function(){
        let file = upload_inp.files[0];
        if (!file) return;
        //实现切片上传 
        //思路,先按照最大大小切片 如果大于100片 则最多只能切100片
        let max = 1024*10,
            size = file.size,
            count = Math.ceil(size/max),
            index = 0,
            chunks = [],
            {
                HASH,
                suffix
            } = await changeBuffer(file);
        if(count>100){
          max = size/100
          count = 100
        }
        
      })

当我们获取完页面的节点后,监听上传文件的变化事件,获取file进行切片,这里的file可以打印出来看下,其实file的原型上存在着Blob,对file的切片,实际上是对Blob的切片

image.png

切片的话我们分为数量和大小进行切片,写按规定大小进行切片,如果切完数量超过最大数量,则按最大数量来进行切片

转换Buffer

const changeBuffer = file => {
    return new Promise(resolve => {
        let fileReader = new FileReader();
        fileReader.readAsArrayBuffer(file);//读取文件转成Buffer数据
        fileReader.onload = ev => {
            let buffer = ev.target.result,
                spark = new SparkMD5.ArrayBuffer(),
                HASH,
                suffix;
            spark.append(buffer);
            HASH = spark.end();
            suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1];
            resolve({
                buffer,
                HASH,
                suffix,
                filename: `${HASH}.${suffix}`
            });
        };  
    });
  }; 

使用readAsArrayBuffer对文件转换成buffer image.png 利用SparkMD5插件来对文件的内容转换成hash

image.png

转换后即使文件名换了但是图片内容不换,生成的hash都是同一个值,因为这是根据文件的内容生成的hash,这点可以有效的防止同一张图片多次存在服务器中。

存储切片

//存切片    
        while(index<count){
          chunks.push({
            file: file.slice(index * max, (index + 1) * max),
            filename: `${HASH}_${index+1}.${suffix}`
          })
          index++
        }
        chunks.forEach(chunk => {
          let fm = new FormData()
          fm.append('file', chunk.file);
          fm.append('filename', chunk.filename);
          instance.post('/upload_chunk', fm).then(data => {
              if (+data.code === 0) {
                  complate(count,HASH);
                  return;
              }
              return Promise.reject(data.codeText);
          }).catch(() => {
              alert('当前切片上传失败,请您稍后再试~~');
              // clear();
          });
        });

接着上面的步骤,使用while循环把切片数据放到一个数组中,并且每次push都按照规定的切片大小进行切割,文件名自然是使用已经产生好的hash来拼接命名,当数组循环上传之后,我们可以看到浏览器网络会一个一个的发送请求

image.png

合并切片

后端通过接收切片后放到一个文件夹中,作为一个切片文件夹 image.png

前端循环切片上传到最后一个切片的时候,就去调用后端的合并切片接口,把对应hash下面的切片进行合并,生成一个正式的图片,这里后端部分暂时不做讲解,其实也是通过一些api对切片进行合并

image.png

断点续传

如果您已经理解了上面部分的内容,那么对于断点续传其实也不难理解

断点续传是针对切片上传的文件,对已经上传的切片不再重新进行上传,做法其实很简单,需要从服务端拿到已经上传的切片数组,比如服务端返回了1~10的切片,而要重新上传的文件也有一个切片数组1-100,那么上传时切片会判断1-10存在服务端,则跳过上传,做法如下

already表示已上传的切片

// 已经上传的无需在上传
if (already.length > 0 && already.includes(chunk.filename)) {
    complate();
    return;
}

最后

本文主要是讲清楚大文件切片上传和断点上传的内容及其实现过程,讲解了在前端方面的大文件上传的知识,本文的技术代码来自b站视频,笔者只是学习后留作笔记使用

如果对您有帮助的话,请给我点给赞吧~文章不足之处还请各位不吝赐教