前言
写这个组件之前,本来打算用webUploader的,结果官方不再维护了,而且在进行一系列的测试及老大的要求下,改用vue-simple-uploader,中间遇到了无数的问题,对于我这个脑子不好使的人来说,简直太难了。。。还好基本都解决了
1.关于vue-simple-uploader
vue-simple-uploader是基于 simple-uploader.js 封装的vue上传插件。它的优点包括且不限于以下几种:
① 支持文件、多文件、文件夹上传;支持拖拽文件、文件夹上传
② 可暂停、继续上传
③ 错误处理
④ 支持“秒传”,通过文件判断服务端是否已存在从而实现“秒传”
⑤ 分块上传
⑥ 支持进度、预估剩余时间、出错自动重试、重传等操作
在使用vue-simple-uploader之前,建议大家先看一下simple-uploader,里面有更详细的介绍,对于使用vue-simple-uploader有很大帮助
链接地址:simple-uploder.js文档
2.安装使用vue-simple-uploader
安装:npm install vue-simple-uploader --save
在中使用
import uploader from 'vue-simple-uploader';
Vue.use(uploader);
3.封装全局上传组件
main.js中引入后,开始封装(叫什么名字都可以)组件,在这里我自定义了上传组件的样式,但是我觉得应该符合大部分人的审美吧???
template部分如下:
<div class="global-uploader">
<uploader
ref="uploader"
class="uploader-app"
:options="options"
:autoStart="false"
@file-added="onFileAdded"
@file-success="onFileSuccess"
@file-error="onFileError">
<uploader-btn id="`uploadButton${getTime}`" class="uploader-btn">
选择文件
</uploader-btn>
// 上传列表
<uploader-list v-show="panelShow">
<div class="file-panel" slot-scope="props">
<div class="file-title">
<div style="display: flex;align-items: center; justify-content: space-between;">
<h2>文件列表</h2>
// 总数量和已上传数量
<span>({{count}} / {{$refs.uploader ? $refs.uploader.fileList.length : 0}})</span>
</div>
<div class="operate">
<el-button type="text" icon="el-icon-close" @click="close" title="关闭"></el-button>
</div>
</div>
<ul class="file-list">
<li v-for="file in props.fileList" :key="file.id">
<uploader-file :file="file" :list="true"></uploader-file>
</li>
<div v-show="!props.fileList.length" class="no-file"></div>
</ul>
</div>
</uploader-list>
</uploader>
</div>
组件中的data部分:
export default {
name: 'SimpleUploader',
data () {
// 选择文件后显示
panelShow: false,
options: {
// 目标上传地址 写自己要上传到的地址即可
target: 'xxxxxx',
// 分块大小
chunkSize: '5242800',
// 是否开启服务器分片校验
testChunks: true,
// 服务器分片校验函数 秒传及断点续传的基础
checkChunkUploadedByResponse: (chunk, message) => {
let objMessage = JSON.parse(message);
// 这里根据实际业务来 uploaded是后端返给我的字段 用来判断哪些片已经上传过了 不用再重复上传了
return (objMessage.data.uploaded || []).indexOf(chunk.offset + 1) >= 0;
}
},
getTime: (new Date().getTime()) / 1000
}
}
可以封装成全局组件使用 也可在需要的地方引用即可
<simpleUploader ref="uploaderRef" @uploaderSuccess="uploaderSuccess"></simpleUploader>
4.上传流程预览
① 触发选择文件按钮
我这里是在使用组件的地方通过ref调用组件里面uploader-btn这个按钮的上传方法
this.$refs.uploaderRef.selectFile();
这里贴出selectFile这一上传方法的代码
selectFile () {
// 这样就通过外边的方法触发组件里面上传按钮的上传方法
// 这里按钮用了时间戳 是为了解决如果有同一页面用了好几个上传组件id不唯一问题
// 如果页面中只用了一个上传组件 则忽略时间戳
document.getElementById(`uploadButton${this.getTime}`);
}
这样就打开了选择文件的弹框 开始选择文件
② 选择文件后 打开上传列表弹框 开始计算MD5(文件唯一标识)
这里用到了 方法
onfileAdded (file) {
this.panelShow = true;
// 计算MD5 下边会提到
this.computeMD5();
}
这里有个前提,我在uploader中将设为了false,为什么要这么做?
在选择文件之后,我要计算MD5,以此来实现断点续传及秒传的功能,所以选择文件后直接开始上传肯定不行,要等MD5计算完毕之后,再开始文件上传的操作。
③ 文件上传成功后的回调
// 四个参数
onFileSuccess (rootFile, file, response, chunk) {
// 这里我调了后端的接口 然后传参数(axios请求举例 按实际情况来)
this.$axios({
url: 'http://xxxx',
method:'post',
// params: params (参数-根据后端情况传参)
}).then(res => {
console.log(res);
});
// 成功后触发使用地方的方法 即uploaderSuccess
this.$emit('uploaderSuccess');
}
5.文件分片
vue-simple-uploader自动将文件进行分片,在options的chunkSize中可以设置每个分片的大小。对于大文件来说,会发送多个请求,在设置testChunks为true后(在插件中默认就是true),会发送与服务器进行分片校验的请求
第一个就是该请求 下边的就是post上传文件请求
看一下参数 调接口时默认带的参数 不是自己传的
6.MD5的计算过程
断点续传及秒传的基础就是MD5的计算,是文件的唯一标识,然后服务器根据MD5判断是秒传还是断点续传 在onFileAdded这个方法中开始计算,然后放到参数里传给后端,具体的步骤
1.把uploader组件的autoStart设为false,即选择文件后不会自动开始上传
2.先通过 file.pause()暂停文件,然后通过H5的FileReader接口读取文件
3.将异步读取文件的结果进行MD5,这里我用的加密工具是spark-md5,你可以通过
npm install spark-md5 --save
来安装,也可以使用其他MD5加密工具。
4.file有个属性是uniqueIdentifier,代表文件唯一标示,我们把计算出来的MD5赋值给这个属性 file.uniqueIdentifier = md5,这就实现了我们最终的目的。
5.通过file.resume()开始/继续文件上传。
上代码
computeMD5(file) {
let fileReader = new FileReader();
let time = new Date().getTime();
let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
let currentChunk = 0;
const chunkSize = 10 * 1024 * 1000;
let chunks = Math.ceil(file.size / chunkSize);
let spark = new SparkMD5.ArrayBuffer();
file.pause();
loadNext();
fileReader.onload = (e => {
spark.append(e.target.result);
if (currentChunk < chunks) {
currentChunk++;
loadNext();
// 实时展示MD5的计算进度
this.$nextTick(() => {
$(`.myStatus_${file.id}`).text('校验MD5 '+ ((currentChunk/chunks)*100).toFixed(0)+'%')
})
} else {
let md5 = spark.end();
this.computeMD5Success(md5, file);
console.log(`MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${new Date().getTime() - time} ms`);
}
});
fileReader.onerror = function () {
this.error(`文件${file.name}读取出错,请检查该文件`)
file.cancel();
};
function loadNext() {
let start = currentChunk * chunkSize;
let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
}
},
computeMD5Success(md5, file) {
// 文件的唯一标识
file.uniqueIdentifier = md5;
file.resume();
},
给file的uniqueIdentifier 属性赋值后,请求中的identifier即是我们计算出来的MD5
7.上传失败方法
失败都会在onFileError这个方法里 和上传成功的方法一样返回四个参数,去做自己想要的错误提示即可
8.秒传及断点续传
在计算完MD5后,我们就能谈断点续传及秒传的概念了。
服务器根据前端传过来的MD5去判断是否可以进行秒传或断点续传:
** a. 服务器发现文件已经完全上传成功,则直接返回秒传的标识。**
b. 服务器发现文件上传过分片信息,则返回这些分片信息,告诉前端继续上传,即断点续传。
8.1 前端做分片校验
在代码中由options中的checkChunkUploadedByResponse控制,它会根据 XHR 响应内容检测每个块是否上传成功了,成功的分片直接跳过上传 你要在这个函数中进行处理,可以跳过的情况下返回true即可
checkChunkUploadedByResponse: (chunk, message) => {
let objMessage = JSON.parse(message);
// 这里根据实际业务来 uploaded是后端返给我的字段 用来判断哪些片已经上传过了 不用再重复上传了
return (objMessage.data.uploaded || []).indexOf(chunk.offset + 1) >= 0;
}
这里我大概讲了一下流程 具体的还要在业务中具体分析,之后再一一完善