分片上传的原理就是在前端将文件分片,然后一片一片的传给服务端,由服务端对分片的文件进行合并,从而完成大文件的上传。分片上传可以解决文件上传过程中超时、传输中断造成的上传失败,而且一旦上传失败后,已经上传的分片不用再次上传,不用重新上传整个文件,因此采用分片上传可以实现断点续传以及跨浏览器上传文件。
至于Element的引入就不多做赘述,直接贴代码。
<template>
<div>
<el-upload
action
:auto-upload="false"
:show-file-list="false"
:on-change="changeFile"
>
<el-button size="small" type="primary">选择文件</el-button>
<div slot="tip" class="el-upload__tip">
1.上传文件不超过100M<br />2.只能上传一个文件<br />3.等待进度条出现√才是上传完成
</div>
</el-upload>
<!-- PROGRESS -->
<div class="progress">
<span>上传进度:{{ total | totalText }}%</span>
<el-link
type="primary"
v-if="total > 0 && total < 100"
@click="handleBtn"
>{{ btn | btnText }}</el-link
>
</div>
</div>
</template>
上传的页面代码部分,有上传、暂停、继续等基础功能。
<script>
import SparkMD5 from "spark-md5";
import { fileParse } from "@/public/utils.js";
import { getStorage } from "lesso-common/public/utils";
import axios from "axios";
export default {
data() {
return {
total: 0,
btn: false,
abort: false,
uploadSuc: false,
slicesNum: null,
chunkNumber: null,
userInfo: {
userName: getStorage("userData").user.employeeName,
userId: getStorage("userData").user.userId,
groupName: "AVM",
},
};
},
filters: {
btnText(btn) {
return btn ? "继续" : "暂停";
},
totalText(total) {
return total > 100 ? 100 : total;
},
},
methods: {
// 分片上传
async changeFile(file) {
if (!file) return;
file = file.raw;
this.total = 0;
this.abort = false;
this.btn = false;
let buffer = await fileParse(file, "buffer"),
spark = new SparkMD5.ArrayBuffer(),
hash,
suffix;
console.log(file, "file");
spark.append(buffer);
hash = spark.end();
suffix = /\.([0-9a-zA-Z]+)$/i.exec(file.name)[1];
let multiple = [];
for (let i = 1; i < file.size / 2; i++) {
if (file.size % i == 0 && i <= 100) {
multiple.push(i);
}
}
// 切片个数
this.slicesNum = multiple.pop();
// 创建切片
let partList = [],
partsize = file.size / this.slicesNum,
cur = 0;
for (let i = 1; i <= this.slicesNum; i++) {
let item = {
chunk: file.slice(cur, cur + partsize),
filename: `${hash}_${i}.${suffix}`,
chunkNumber: `${i}`,
file: file.slice(cur, cur + partsize),
};
cur += partsize;
partList.push(item);
}
this.requestData = {
groupName: this.userInfo.groupName,
fileMd5: hash,
name: file.name,
size: file.size,
totalChunks: this.slicesNum,
chunkSize: partsize,
uid: this.userInfo.userId,
uname: this.userInfo.userName,
};
this.partList = partList;
this.sendRequest();
},
async sendRequest() {
this.uploadSuc = false;
// 根据100个切片创造100个请求集合
let requestList = [];
this.partList.forEach((item, index) => {
// 每一个函数都发送一个切片请求
let fn = async (chunkNumber) => {
let formData = new FormData(),
shardFile = new SparkMD5.ArrayBuffer(),
shardFileBuffer = await fileParse(item.chunk, "buffer"),
shardFileHash;
shardFile.append(shardFileBuffer);
shardFileHash = shardFile.end();
formData.append(
"chunkNumber",
chunkNumber ? chunkNumber : item.chunkNumber
);
formData.append("groupName", this.requestData.groupName);
formData.append("file", item.file);
formData.append("fileMd5", this.requestData.fileMd5);
formData.append("name", this.requestData.name);
formData.append("size", this.requestData.size);
formData.append("totalChunks", this.requestData.totalChunks);
formData.append("chunkSize", this.requestData.chunkSize);
formData.append("chunkMd5", shardFileHash);
formData.append("uid", this.requestData.uid);
formData.append("uname", this.requestData.uname);
return axios
.post(
"http://iotupdateapi.lesso.com/uploadFastdfs/uploadPart",
formData,
{
headers: { "Content-Type": "multipart/form-data" },
}
)
.then((res) => {
const { code, data } = res.data;
if (code == 206) {
this.total += parseInt(100 / this.slicesNum);
// 传完的切片我们把它移除掉
if (chunkNumber) {
this.partList.splice(data.chunkNumber - 1, 1);
}
return data.chunkNumber;
} else if (code == 200) {
this.uploadSuc = true;
}
});
};
requestList.push(fn);
});
let i = 0;
let send = async () => {
if (this.abort) return;
if (i >= requestList.length && !this.uploadSuc) {
this.$message.warning("上传失败,请重新上传");
this.total = 0;
return;
}
if (this.uploadSuc) {
this.$message.success("上传成功");
this.total = 100;
return;
}
await requestList[i](this.chunkNumber).then((res) => {
this.chunkNumber = res;
});
i++;
send();
};
send();
},
handleBtn() {
if (this.btn) {
this.abort = false;
this.btn = false;
this.sendRequest();
return;
}
this.btn = true;
this.abort = true;
},
},
};
</script>
本来是想每次都直接创建100个切片进行上传,后来接口的参数要求每片切片的大小必须要是整数,所有就又做了一次处理,算出文件可以整除的数字,取最接近小于等于100的的数字进行切片处理。
把每个切片转换成每个请求放进数组,根据每次调接口的返回值来处理数组实现分片上传和断点续传。
把每个切片转换成每个请求放进数组,根据每次调接口的返回值来处理数组实现分片上传和断点续传。
utils的fileParse方法
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);
};
});
};