断点续传作用:当上传文件时,大文件上传时耗时过长,如果遇到网络卡顿、断网等情况,再重新开始上传的体验感非常不好。前端优化分片上传文件,上传时把大文件分成很多个小文件,等到网络状况恢复了之后,即从之前上传断点处继续上传,提高用户体验。
实现思路:点击上传时获取上传文件,解析切片数据,设置切片大小,处理成多个切片数据,判断每个切片数据是否上传成功,未上传成功的部分继续上传,如果所有切片上传成功,合并全部已上传文件给后端接收。
vue+element ui 的上传 demo
<template>
<div class="appUpload">
<el-upload drag action
:auto-upload="false"
:show-file-list="false"
:on-change="changeFile"
:before-upload='beforeload'>
<i class="el-icon-upload"></i>
<div class="el-upload__text">
将文件拖到此处,或
<em>点击上传</em>
</div>
</el-upload>
<!-- PROGRESS -->
<div class="progress">
<el-progress :percentage="totalText"></el-progress>
<el-link type="primary" v-if="totalText>0 && totalText<100" @click="handleBtn">
{{btn===true?'暂停上传':'继续上传'}}</el-link>
</div>
</div>
</template>
js逻辑部分
接口部分自行封装引入自己项目的接口
//引入computeSlice函数(js封装的切片后每个切片的返回数据 传入原始文件和切片大小)
//引入beforeUpload函数上传之前校验
import { computeSlice ,beforeUpload} from '@/utils/fileutils'
beforeload(file) {
beforeUpload(file)//上传前校验 根据自己的项目需求校验
},
//点击上传
async changeFile(file) {
this.uploadFile = file;
let size = 1024 * 1024 * 1;//设置切片大小
let fileInfo =await computeSlice(file,size);//处理切片内容 获取切片fileInfo对象
this.nSize = fileInfo.files.length;
this.fileMd5 = fileInfo.fileMd5
if( await this.getcheckFileMd5(fileInfo.info.suffix)){//如果该文件存在于数据库/之前上传过 则弹出提示 //终止以下程序
return;
}
for(let i = 0; i < fileInfo.files.length; i++) {
let status=await this.FileMd5(i,fileInfo.fileMd5)
if(status && this.btn){
await this.sendRequest(i,fileInfo.fileMd5,fileInfo.files[i])
}
}
if(this.total == this.nSize){
this.getComplete(fileInfo.fileMd5);
}
},
//验证是否上传过该文件
async getcheckFileMd5(suffix){
let data={
checkFileMd5:this.fileMd5,
suffix:suffix
}
var status;
//checkFileMd5为封装后判断该视频是否上传接口 自行导入自己项目的后端接口即可
await checkFileMd5(data).then((res) => {
if (res.data) {
this.$message({
message: '该视频已上传',
});
}
status= res.data;
});
return status;
},
//判断分片是否上传接口
async FileMd5(num,value){
let status;
//checkChunk为封装后判断该视频每块分片是否上传接口 自行导入自己项目的后端接口即可
await checkChunk({fileIndex:num,fileMd5:value}).then((res) => {
if(res.data == false){
status= true;
}else{
this.total++;
status= false;
}
});
return status;
},
//上传接口
async sendRequest(num,value,item) {
let chunkFile = new FormData();
chunkFile.append("chunkFile", item);
//chunkFileUpload为封装后上传接口 自行导入自己项目的后端接口即可
await chunkFileUpload(num,value,chunkFile).then((res) => {
if (res.code == 200) {
this.total ++;
this.totalText = Math.floor(this.total / this.nSize *100)
}
});
},
//合并上传接口
getComplete(fileMd5){
let params= {
chunkTotal:this.nSize,
fileMd5: fileMd5,
}
//mergeChunk为封装后合并上传接口 自行导入自己项目的后端接口即可
mergeChunk(params).then((res) => {
if (res.code == 200) {
this.$message({
message: '上传成功',
type: 'success'
});
this.total = 0;
this.totalText = 100;
}
});
},
//点击该按钮 暂停或继续上传分片 未上传或上传完毕后该按钮隐藏
handleBtn() {
if (this.btn) {
// 断点续传
this.btn = false;
return;
}else{
this.btn = true;
this.total = 0;
this.totalText= 0;
this.changeFile(this.uploadFile);
}
}
utils目录下fileutils文件封装 需要用到spark-md5的MD5值 要npm引入
npm install --save spark-md5
let SparkMD5 = require('spark-md5')
// let SparkMD5 = require('spark-md5')
import SparkMD5 from 'spark-md5'//引入spark-md
//解析数据
export function fileParse(file, type = "base64"){
return new Promise(resolve => {
let fileRead = new FileReader();
if (type === "base64") {
fileRead.readAsDataURL(file);
} else if (type === "buffer") {
fileRead.readAsArrayBuffer(file);
}
fileRead.onload = (ev) => {
resolve(ev.target.result);
};
});
}
//计算切片方法 异步改成同步方法
export async function computeSlice(file,chunkSize){
if (!file) return;
var fileInfo={
fileMd5:'',
files:[],
info:{}
};
fileInfo.info.filename=file.name;
file = file.raw;
// 解析成buffer数据
// 切片处理,把文件切成多个部分(固定数量/固定大小)
// 每一个切片都有自己的部分数据和自己的名字
let buffer ;
await fileParse(file, "buffer").then((res)=>{
buffer =res;
});
let spark = new SparkMD5.ArrayBuffer();
spark.append(buffer);//元素后面追加一个字符串
fileInfo.fileMd5= spark.end();
fileInfo.info.suffix = '.'+/\.([0-9a-zA-Z]+)$/i.exec(file.name)[1];
let cur = 0;
let nSize =Math.ceil(file.size / chunkSize)
for(let i = 0; i < nSize; i++) {
fileInfo.files.push(file.slice(cur, cur + chunkSize));
cur += chunkSize;
}
return fileInfo;//输出一个切片后对象 fileInfo.files为切片后数组
}
//上传前校验 自行根据自己项目的需求进行校验
export function beforeUpload(file) {
// 格式校验
let { type, size } = file;
console.log(type)
// if (!/(png|gif|jpeg|jpg)/i.test(type)) {
// this.$message("文件合适不正确~~");
// return false;
// }
if (type != "video/mp4") {
this.$message("文件合适不正确~~");
return false;
}
if (size > 200 * 1024 * 1024) {
this.$message("文件过大,请上传小于200MB的文件~~");
return false;
}
return true;
}