Vue 进阶系列丨大文件切片上传
Posted HFun TM
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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的文件来说,我们如果还是按小文件来处理的话,就显得不是很恰当了。本篇文章就是告诉大家如何实现大文件上传的,包括切片上传、断点续传、进度把控等方面,废话不多说,开始吧。
大文件上传思路
这里先简单说一下整体的思路:
针对文件内容生成的唯一的hash值,作为文件名称。确保相同文件,不同的文件名的情况下,生成的hash值是一样的。
将大文件切成若干个小的分片,并且每一个分片的命名格式为 “文件hash值+当前切片索引值”,方便后端合并这些切片。
上传之前,将当前文件的hash值传递给后台,拿到当前文件在数据库里已经存在的切片,如果有切片已经存在,那么前端就不传递这些了,这就是断点续传的原理。
上传过程中,通过监听当前传递的切片数除以切片总数,获取当前上传进度。当进度条百分百时,自动通知后端将这些文件合并。
上传完成。
下面进行代码展示:
生成文件名
这里我们使用一个第三方库: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 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!
叶阳辉
HFun 前端攻城狮
往期精彩:
Vue 进阶系列丨虚拟DOM和VNode
提供与真实 DOM 节点所对应的虚拟节点 vnode
将新的虚拟节点和旧的虚拟节点进行对比,然后更新页面
class VNode {
constructor (
tag,
data,
children,
text,
elm,
context,
componentOptions,
asyncFactory
) {
this.tag = tag;
this.data = data;
this.children = children;
this.text = text;
this.elm = elm;
this.ns = undefined;
this.context = context;
this.fnContext = undefined;
this.fnOptions = undefined;
this.fnScopeId = undefined;
this.key = data && data.key;
this.componentOptions = componentOptions;
this.componentInstance = undefined;
this.parent = undefined;
this.raw = false;
this.isStatic = false;
this.isRootInsert = true;
this.isComment = false;
this.isCloned = false;
this.isOnce = false;
this.asyncFactory = asyncFactory;
this.asyncMeta = undefined;
this.isAsyncPlaceholder = false;
}
get child () {
return this.componentInstance
}
}
注释节点
const createEmptyVNode = (text = '') => {
const node = new VNode();
node.text = text;
node.isComment = true;
return node
};
我们可以看到注释节点只有两个有效属性,text 和 isComment,其余属性都是默认的 undefined 或者 false。
例如,一个真实的注释节点:
<!-- 注释节点 -->
所对应的 vnode 是这样的:
{
text:'注释节点',
isComment:true
}
文本节点
function createTextVNode (val) {
return new VNode(undefined, undefined, undefined, String(val))
}
文本节点只有一个 text 属性,它的 vnode 是这样的:
{
text:'我是文本内容',
}
克隆节点
function cloneVNode (vnode) {
const cloned = new VNode(
vnode.tag,
vnode.data,
vnode.children && vnode.children.slice(),
vnode.text,
vnode.elm,
vnode.context,
vnode.componentOptions,
vnode.asyncFactory
);
cloned.ns = vnode.ns;
cloned.isStatic = vnode.isStatic;
cloned.key = vnode.key;
cloned.isComment = vnode.isComment;
cloned.fnContext = vnode.fnContext;
cloned.fnOptions = vnode.fnOptions;
cloned.fnScopeId = vnode.fnScopeId;
cloned.asyncMeta = vnode.asyncMeta;
cloned.isCloned = true;
return cloned
}
克隆节点就是将一个节点的内容全部复制到新的节点中。主要用来优化静态节点和插槽节点。当静态节点需要重新渲染的时候,并不需要重新再次走一遍渲染函数,然后重新生成 vnode,只需要使用克隆节点的方法将之前的 vnode 克隆一份即可,一定程度上优化了程序的性能。
克隆节点和被克隆节点的唯一区别是 isCloned 属性,克隆节点的 isCloned为 true,被克隆的原始节点的 isCloned 为 false。
元素节点
元素节点一般包含 4 个有效属性:
tag:就是节点的名称,例如 div、p、img
data:该属性包含一些节点上的数据,例如 class、style
children:该节点的子节点列表
context:当前组件的 vue.js 实例
通常,函数式组件的 vnode 是这样子的:
{
children:[VNode,VNode],
context:{...},
data:{...},
tag:{...}
}
组件节点
和元素节点类似,有两个独有的属性 componentInstance 和 componentOptions。
通常,函数式组件的 vnode 是这样子的:
{
{...}, :
{...}, :
context:{...},
data:{...},
tag:{...}
}
函数式组件
和组件节点类似,有两个独有的属性 functionalContext 和 functionalOptions。
通常,函数式组件的 vnode 是这样子的:
{
functionalContext :{...},
functionalOptions:{...},
context:{...},
data:{...},
tag:{...}
}
以上是关于Vue 进阶系列丨大文件切片上传的主要内容,如果未能解决你的问题,请参考以下文章