一、目的:
本文仅记录一次实践尝试,仅前端调试成功,后端未反馈问题,不过后来接口废弃了。
二、前期博客浏览参考:
本文链接:前端大文件切片上传,断点续传、秒传等解决方案,vue中使用实例,react同理_前端文件切片_刘伟坤的博客-CSDN博客
三、原理:
分片上传的整个流程大致如下:
(1) 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
(2) 向服务端发送上传请求,上传时携带完整文件的唯一标识(建议使用MD5生成编码值),服务端判断该文件是否存在,不存在是返回同意上传信息。
(3) 接收到服务端同意上传后,按照一定的策略(串行或并行)发送各个分片数据块,每个分片携带分片信息(分片总数,分片索引,分片唯一索引,分片数据等),采用并行可实现分片秒传的功能,采用串行更好实现断点续传的功能。
(4) 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件。
四、手动实践:
前端后端可能都是新手菜-_-
1. 后端提供一个文件上传接口
- 文件相关属性 传参:
file fileName chunkNumber totalChunks 文件 文件名 当前序号 分片总数 - 返回值:文件路径 filePath
- 接口逻辑:最后一个切片上传成功后,会返回服务器上存储的文件路径
2. 页面准备
<el-upload
class="upload-demo"
action
:show-file-list="false"
accept=".rar, .zip"
:http-request="uploadFile"
>
<el-button type="primary">选择文件</el-button>
</el-upload>
<el-button type="primary" @click="confirmFile">确定</el-button>
chunkSize: 4 * 1024 * 1024, // 4MB
chunks: [], //切片存放数组
totalChunks: 0, //切片总数量
// 上传至页面
uploadFile(file) {
this.fileForm.fileName = file.file.name;
this.fileForm.file = file.file;
},
// 上传至服务器
confirmFile() {
this.$refs.fileForm.validate((valid) => {
if (valid) {
let loading = this.$loading({
lock: true,
text: "上传中,请稍后...",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.5)",
});
// 切片上传 + 文件业务
} else {
return false;
}
});
},
3. 核心:切割
(方式1)使用Blob.splice(),但是最后打印的chunks,我看不懂,不知道是不是对应的file文件。
const fileSize = this.fileForm.file.size;
this.chunks = []; //不能省
this.totalChunks = Math.ceil(fileSize / this.chunkSize);
for (let i = 0; i < this.totalChunks; i++) {
let start = i * this.chunkSize;
let end = Math.min(fileSize, start + this.chunkSize);
let blob = this.fileForm.file.slice(start, end);
this.chunks.push(blob);
}
// console.log('切割后文件数组',this.chunks);
(方式2)使用fileReader ,但是没有实践,前期准备的时候 搜罗的代码
// const fileReader = new FileReader();
// fileReader.onload = () => {
// const chunk = fileReader.result;
// // TODO: Upload chunk to server
// chunkIndex++;
// if (chunkIndex < numChunks) {
// loadNext();
// }
// }
// const loadNext = () => {
// const start = chunkIndex * chunkSize;
// const end = Math.min(start + chunkSize, fileSize);
// fileReader.readAsArrayBuffer(this.selectedFile.slice(start, end));
// }
4.切片上传
为适应接口,采用promise包裹分片上传的接口,最终使用peomise.all获取最后一个切片的返回值
// 分片上传
let p = [];
this.chunks.forEach((item, index) => {
p[index] = new Promise((resolve, reject) => {
let fd = new FormData();
fd.append("file", item);
fd.append("fileName", this.fileForm.fileName);
fd.append("chunkNumber", index + 1);
fd.append("totalChunks", this.totalChunks);
spliceFileUpload(fd).then(
({ data: { code, result: { filePath } } }) => {
if (code == 200) {
resolve(filePath);
} else {
reject("上传失败");
}
}
);
});
});
5. 上传成功调用业务接口
// 上传成功则导入
Promise.all(p)
.then((paths) => {
//调用业务接口(paths.at(-1))
})
.catch((err) => {
// 文件上传失败 业务失败
console.log(err);
this.$message.error(err);
loading.close();
});