Vue2.0结合webuploader实现⽂件分⽚上传
Vue项⽬中遇到了⼤⽂件分⽚上传的问题,之前⽤过webuploader,索性就把Vue2.0与webuploader结合起来使⽤,封装了⼀个vue的上传组件,使⽤起来也⽐较舒爽。
上传就上传吧,为什么搞得那么⿇烦,⽤分⽚上传?
分⽚与并发结合,将⼀个⼤⽂件分割成多块,并发上传,极⼤地提⾼⼤⽂件的上传速度。
当⽹络问题导致传输错误时,只需要重传出错分⽚,⽽不是整个⽂件。另外分⽚传输能够更加实时的跟踪上传进度。
实现后的界⾯:
在项⽬中引⼊webuploader
1. 先在系统中引⼊jquery(插件基于jq,坑爹啊!),如果你不知道放哪,那就放到index.html中。
2. 在上下载Uploader.swf 和webuploader.min.js,可以放到项⽬静态⽬录static下⾯;在index.html中引⼊webuploader.min.js。
(⽆需单独再引⼊webuploader.css,因为没有⼏⾏css,我们可以复制到vue组件中。)
<script src="/static/lib/jquery-2.2.3.min.js"></script>
<script src="/static/lib/webuploader/webuploader.min.js"></script>
需要注意的点:
1. 在vue组件中,通过import './webuploader';的⽅式引⼊webuploader,会报''caller', 'callee', and 'arguments' properties may not
be accessed on strict mode ...'的错,这是因为你的babel使⽤了严格模式,⽽caller这些在严格模式下禁⽌使⽤。所以可以
直接在index.html中引⼊webuploader.js,或者⼿动去解决babel中'use strict'的问题。
基于webuploader封装Vue组件
封装好的组件upload.vue如下,接⼝可以根据具体的业务进⾏扩展。
注意:功能和ui分离,此组建封装好了基本的功能,没有提供ui,ui在具体的页⾯上去实现。
<template>
<div class="upload">
</div>
</template>
<script>
export default {
name: 'vue-upload',
props: {
accept: {
type: Object,
default: null,
},
// 上传地址
url: {
type: String,
default: '',
},
// 上传最⼤数量默认为100
fileNumLimit: {
type: Number,
default: 100,
},
/
/ ⼤⼩限制默认2M
fileSingleSizeLimit: {
type: Number,
default: 2048000,
},
// 上传时传给后端的参数,⼀般为token,key等
formData: {
type: Object,
default: null
},
// ⽣成formData中⽂件的key,下⾯只是个例⼦,具体哪种形式和后端商议
keyGenerator: {
type: Function,
default(file) {
const currentTime = new Date().getTime();
const key = `${currentTime}.${file.name}`;
return key;
},
multiple: {
type: Boolean,
default: false,
},
/
/ 上传按钮ID
uploadButton: {
type: String,
default: '',
},
},
data() {
return {
uploader: null
};
},
mounted() {
this.initWebUpload();
},
methods: {
initWebUpload() {
this.uploader = ate({
auto: true, // 选完⽂件后,是否⾃动上传
swf: '/static/lib/webuploader/Uploader.swf',  // swf⽂件路径
server: this.url,  // ⽂件接收服务端
pick: {
id: this.uploadButton,    // 选择⽂件的按钮
multiple: this.multiple,  // 是否多⽂件上传默认false
label: '',
},
accept: Accept(this.accept),  // 允许选择⽂件格式。
threads: 3,
fileNumLimit: this.fileNumLimit, // 限制上传个数
//fileSingleSizeLimit: this.fileSingleSizeLimit, // 限制单个上传图⽚的⼤⼩
formData: this.formData,  // 上传所需参数
chunked: true,          //分⽚上传
chunkSize: 2048000,    //分⽚⼤⼩
duplicate: true,  // 重复上传
});
// 当有⽂件被添加进队列的时候,添加到页⾯预览
('fileQueued', (file) => {
this.$emit('fileChange', file);
});
('uploadStart', (file) => {
// 在这⾥可以准备好formData的数据
//this.uploader.options.formData.key = this.keyGenerator(file);
});
// ⽂件上传过程中创建进度条实时显⽰。
('uploadProgress', (file, percentage) => {
this.$emit('progress', file, percentage);
});
('uploadSuccess', (file, response) => {
this.$emit('success', file, response);
});
('uploadError', (file, reason) => {
<(reason);
this.$emit('uploadError', file, reason);
});
('error', (type) => {
let errorMessage = '';
if (type === 'F_EXCEED_SIZE') {
errorMessage = `⽂件⼤⼩不能超过${this.fileSingleSizeLimit / (1024 * 1000)}M`;                    } else if (type === 'Q_EXCEED_NUM_LIMIT') {
errorMessage = '⽂件上传已达到最⼤上限数';
} else {
errorMessage = `上传出错!请检查后重新上传!错误代码${type}`;
}
<(errorMessage);
this.$emit('error', errorMessage);
});
('uploadComplete', (file, response) => {
this.$emit('complete', file, response);
});
upload(file) {
this.uploader.upload(file);
},
stop(file) {大文件发送
this.uploader.stop(file);
},
// 取消并中断⽂件上传
cancelFile(file) {
this.uploader.cancelFile(file);
},
/
/ 在队列中移除⽂件
removeFile(file, bool) {
veFile(file, bool);
},
getAccept(accept) {
switch (accept) {
case 'text':
return {
title: 'Texts',
exteensions: 'doc,docx,xls,xlsx,ppt,pptx,pdf,txt',
mimeTypes: '.doc,docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt'
};
break;
case 'video':
return {
title: 'Videos',
exteensions: 'mp4',
mimeTypes: '.mp4'
};
break;
case 'image':
return {
title: 'Images',
exteensions: 'gif,jpg,jpeg,bmp,png',
mimeTypes: '.gif,.jpg,.jpeg,.bmp,.png'
};
break;
default: return accept
}
},
},
};
</script>
<style lang="scss">
// 直接把官⽅的css粘过来就⾏了
</style>
使⽤封装好的上传组件
新建页⾯,使⽤例⼦如下:
ui需要⾃⼰去实现。。
<vue-upload
ref="uploader"
url="xxxxxx"
uploadButton="#filePicker"
multiple
@fileChange="fileChange"
@progress="onProgress"
@success="onSuccess"
></vue-upload>
分⽚的原理及流程
当我们上传⼀个⼤⽂件时,会被插件分⽚,ajax请求如下:
1. 多个upload请求均为分⽚的请求,把⼤⽂件分成多个⼩份⼀次⼀次向服务器传递
2. 分⽚完成后,即upload完成后,需要向服务器传递⼀个merge请求,让服务器将多个分⽚⽂件合成⼀个⽂件
分⽚
可以看到发起了多次upload的请求,我们来看看upload发送的具体参数:
第⼀个配置(content-disposition)中的guid和第⼆个配置中的access_token,是我们通过webuploader配置⾥的formData,即传递给服务器的参数
后⾯⼏个配置是⽂件内容,id、name、type、size等
其中chunks为总分⽚数,chunk为当前第⼏个分⽚。图⽚中分别为12和9。当你看到chunk是11的upload请求时,代表这是最后⼀个upload 请求了。
合并
分⽚后,⽂件还未整合,数据⼤概是下⾯这个样⼦:
做完了分⽚后,其实⼯作还没完,我们还要再发送个ajax请求给服务器,告诉他把我们上传的⼏个分⽚合并成⼀个完整的⽂件。
我怎么知道分⽚上传完了,我在何时做合并?
webuploader插件有⼀个事件是uploadSuccess,包含两个参数,file和后台返回的response;当所有分⽚上传完毕,该事件会被触发,
我们可以通过服务器返回的字段来判断是否要做合并了。
⽐如后台返回了needMerge,我们看到它是true的时候,就可以发送合并的请求了。
存在的已知问题
在做单⽂件暂停与继续上传时,发现了这个插件的bug:
1、当设置的threads>1,使⽤单⽂件上传功能,即stop⽅法传⼊file时,会报错Uncaught TypeError: Cannot read property 'file' of undefined
出错的源码如下:这是因为暂停时为了让下⼀个⽂件继续传输,会将当前的pool池中pop掉暂停的⽂件流。这⾥做了循环,最后⼀次循环的时候,v是undefined的。
2、设置的threads为1,能正常暂停,但是暂停后再继续上传是失败的。
原理和上⼀个⼀样,暂停时把当前⽂件流在pool中全部pop了,当⽂件开始upload的时候,会检查当期pool,⽽此时已经没有之前暂停的⽂件流了。
如果是针对所有⽂件整体的暂停和继续,功能是正常的。
如果想实现单⽂件的暂停和继续功能,需要修改源码(我改了下源码,发现耦合度较⾼,⼯程量⽐想象的⼤,遂放弃)
后记
因为单⽂件暂停的bug,我最后放弃了这个插件,⽽且官⽅已经不再维护这个插件,github上issue成,所以不太推荐⼤家⽤这个插件
后来我⽤vue-uploader(simple-uploader)⽆痛实现了⽂件分⽚上传、秒传及断点续传,⼤家想看的话我可以重新写⼀篇⽂章
这篇⽂章没有把⼀些知识点写全,其实思路是共通的:
1 在“加⼊⽂件”的回调中,通过FileReader读取⽂件,⽣成MD5,发给后台
2.1 如果后台直接返回了“跳过上传”字段和⽂件的url,则跳过上传,这是秒传;
2.2 如果后台返回了分⽚信息,这是断点续传。后台会在每个分⽚中标识这个分⽚是否上传过,你需要在分⽚上传校验的回调中判断,如
果true则跳过该分⽚。
3 每个分⽚上传成功,后台都会返回⼀个字段判断是否需要合并;在“上传完成”的回调中,如果这个字段为true,则需要给后台发⼀个请
求合并的ajax请求