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

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 部分
<input type="file" id="input" >
// 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 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!

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

叶阳辉

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 多线程

发表回复