Vue 进阶系列丨大文件切片上传

Vue 进阶系列丨大文件切片上传_第1张图片

Vue 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!


2013年7月28日,尤雨溪第一次在 GItHub 上为 Vue.js 提交代码;2015年10月26日,Vue.js 1.0.0版本发布;2016年10月1日,Vue.js 2.0发布。

最早的 Vue.js 只做视图层,没有路由, 没有状态管理,也没有官方的构建工具,只有一个库,放到网页里就可以直接用了。

后来,Vue.js 慢慢开始加入了一些官方的辅助工具,比如路由(Router)、状态管理方案(Vuex)和构建工具(Vue-cli)等。此时,Vue.js 的定位是:The Progressive Framework。翻译成中文,就是渐进式框架。

Vue.js2.0 引入了很多特性,比如虚拟 DOM,支持 JSX 和 TypeScript,支持流式服务端渲染,提供了跨平台的能力等。Vue.js 在国内的用户有阿里巴巴、百度、腾讯、新浪、网易、滴滴出行、360、美团等等。

Vue 已是一名前端工程师必备的技能,现在就让我们开始深入学习 Vue.js 内部的核心技术原理吧!


文件上传

在前端中,对于文件上传是一个很常见的功能,我们可以上传图片、文件等到服务器,对于一般的小型的文件, 例如小图片,我们可以选择基于 formdata 进行文件上传,也可以基于 base64 格式去上传,直接发送表单请求即可。

但是对于大文件来说,比如几个G甚至几十G的文件来说,我们如果还是按小文件来处理的话,就显得不是很恰当了。本篇文章就是告诉大家如何实现大文件上传的,包括切片上传、断点续传、进度把控等方面,废话不多说,开始吧。


大文件上传思路

这里先简单说一下整体的思路:

  1. 针对文件内容生成的唯一的hash值,作为文件名称。确保相同文件,不同的文件名的情况下,生成的hash值是一样的。

  2. 将大文件切成若干个小的分片,并且每一个分片的命名格式为 “文件hash值+当前切片索引值”,方便后端合并这些切片。

  3. 上传之前,将当前文件的hash值传递给后台,拿到当前文件在数据库里已经存在的切片,如果有切片已经存在,那么前端就不传递这些了,这就是断点续传的原理。

  4. 上传过程中,通过监听当前传递的切片数除以切片总数,获取当前上传进度。当进度条百分百时,自动通知后端将这些文件合并。

  5. 上传完成。



下面进行代码展示:

  • 生成文件名

这里我们使用一个第三方库:spark-md5,根据文件二进制流,生成hash值。

在vue中,通过:npm i spark-md5 安装

import SparkMD5 from 'spark-md5'


/**
 * @description: 拿到文件的hash值
 * @Author: hfunteam
 * @param {*} file 文件,File类型
 * @return {
 *   buffer: 文件的二进制流
 *   hash: 生成的文件hash值
 *   suffix:文件后缀名
 *   filename: 文件名称 hash + suffix
 * } 
 */ 
function changeBuffer(file: File) {
  return new Promise(resolve => {
    let fileReader = new FileReader();
    fileReader.readAsArrayBuffer(file);
    fileReader.onload = ev => {
      let buffer: ArrayBuffer = ev.target!.result as ArrayBuffer,
        spark = new SparkMD5.ArrayBuffer(),
        HASH,
        suffix;
      spark.append(buffer);
      // 拿到文件的hash值
      hash = spark.end();
      // 匹配文件后缀名
      suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)![1];
      resolve({
        buffer,
        hash,
        suffix,
        filename: `${HASH}.${suffix}`
      });
    };
  });
};
  • 分片上传+断点续传

// html 部分



// js 部分
import axios from 'axios'
// 监听input按钮事件
document.getElementById('input').addEventListener('change', async function () {
    // 获取文件的HASH
    let alreadyUploadChunks = [], // 当前已经上传的切片
        {
            hash,
            suffix
        } = changeBuffer(file); 
    
    // 获取已经上传的切片信息
     let data = await axios.get('/upload_already', {
          params: {
              hash
          }
      });
     // 从后端拿到已经上传的切片列表
     alreadyUploadChunks = data.fileList
    
    // 实现文件切片处理 「固定数量 或者 固定大小」
    let max = 1024 * 100, // 切片大小
        count = Math.ceil(file.size / max), // 切片总数
        index = 0,   // 当前上传的切片索引值
        chunks = []; // 存放切片的数组
    if (count > 100) {
        max = file.size / 100;
        count = 100;
    }
    // 存放切片,注意此处的切片名称,hash+index+suffix
    while (index < count) {
        chunks.push({
            file: file.slice(index * max, (index + 1) * max),
            filename: `${HASH}_${index+1}.${suffix}`
        });
        index++;
    }
    // 把每一个切片都上传到服务器上
    chunks.forEach(chunk => {
        // 这里进行断点续传:已经上传的无需在上传
        if (alreadyUploadChunks.length > 0 && alreadyUploadChunks.includes(chunk.filename)) {
            // 管控进度条
            manageProgress();
            return;
        }
        let formdata = new FormData();
        formdata.append('file', chunk.file);
        formdata.append('filename', chunk.filename);
        instance.post('/upload_chunk', formdata).then(data => {
            // 管控进度条
            manageProgress();
        }).catch(() => {            
            alert('当前切片上传失败,请您稍后再试');
        });
    });
})
  • 进度条控制+通知合并

当进度条到达百分之百时,调用通知后台合并切片接口,合并成功,则上传成功。

async function manageProgress(){
    // 管控进度
    document.getElementById('progress').style.width = `${index/count*100}%`;
    // 当所有切片都上传成功,我们合并切片
    if (index < count) return;
    document.getElementById('progress').style.width = `100%`;
    try {
        data = await instance.post('/upload_merge', {
            hash,
            count
        }, {
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        });
        if (data.code === 0) {
            alert(`恭喜您,文件上传成功`);
            return;
        }
        throw data.codeText;
    } catch (err) {
        alert('切片合并失败,请您稍后再试');
    }
};

Vue 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!

656a020e458579b98cfad9cbf44cec4c.png

叶阳辉

HFun 前端攻城狮

往期精彩:

  • Vue 进阶系列丨Object 的变化侦测

  • Vue 进阶系列丨Array 的变化侦测

  • Vue 进阶系列丨虚拟DOM和VNode

  • Vue 进阶系列丨Patch 和模板编译

  • Vue 进阶系列丨事件相关的实例方法

  • Vue 进阶系列丨生命周期相关的实例方法

  • Vue 进阶系列丨生命周期

  • Vue 进阶系列丨自定义指令

  • Vue 进阶系列丨最佳实践

  • Vue 进阶系列丨Mixin 混入

  • Vue 进阶系列丨权限控制 addRoute()

  • Vue 进阶系列丨npm发布vue组件

  • Vue 进阶系列丨Vuex+TS 代码提示

  • Vue 进阶系列丨自定义指令实现按钮权限功能

  • Vue 进阶系列丨Pinia 的基本使用

  • Vue 进阶系列丨vue2和vue3定义插件的区别

  • Vue 进阶系列丨vuex持久化

  • Vue 进阶系列丨webWorker 多线程

你可能感兴趣的:(vue进阶系列,vue.js,前端,javascript,ecmascript,前端框架)