前端大文件分片上传
文件上传超时:原因是前端请求框架限制最大请求时长,后端设置了接口访问的超时时间,或者是 nginx(或其它代理/网关) 限制了最大请求时长。
文件大小超限:原因在于后端对单个请求大小做了限制,一般 nginx 和 server 都会做这个限制。
上传时间过久(想想10个g的文件上传,这不得花个几个小时的时间)
由于各种网络原因上传失败,且失败之后需要从头开始
整体思路
前端根据代码中设置好的分片大小将上传的文件切成若干个小文件,分多次请求依次上传,后端再将文件碎片拼接为一个完整的文件,即使某个碎片上传失败,也不会影响其它文件碎片,只需要重新上传失败的部分就可以了。而且多个请求一起发送文件,提高了传输速度的上限。
(前端切片的核心是利用 Blob.prototype.slice 方法,和数组的 slice 方法相似,文件的 slice 方法可以返回原文件的某个切片)
前端代码
<template>
<div style="height: 100%">
<!-- test-->
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>大文件上传和下载</el-breadcrumb-item>
<el-breadcrumb-item>大文件上传</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片视图区域 -->
<el-card style="height: 100%">
<!--行-->
<el-row>
<!--列-->
<el-col style="width:0">
<el-button type="primary" @click="dialogTableVisible = true">上传文件</el-button>
</el-col>
</el-row>
</el-card>
<el-dialog title="上传文件" :visible.sync="dialogTableVisible">
<el-upload
style="width: 0"
class="upload-demo"
action=""
:before-upload="beforeUpload"
:multiple="false">
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<el-table :data="fileList">
<el-table-column property="fileName" label="文件名"></el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button @click="onCancel">取消</el-button>
<el-button type="primary" @click="onOk">上传</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
dialogTableVisible: false,
// 列表展示的
fileList: [],
// 真正上传的
uploadList: [],
size: 50 * 1024 * 1024,
file: {}
}
},
mounted() {
this.init()
},
methods: {
// 初始化
init() {
},
// 上传前调用,阻止上传
beforeUpload(file) {
this.file = file;
// 切片生成
const fileChunkList = this.createFileChunk(file);
this.uploadList = fileChunkList.map(({file, fileName}, index) => ({
chunk: file,
// 这里的hash为文件名 + 切片序号,
hash: fileName + "-" + index
}));
this.fileList = [{
fileName: file.name
}];
return false;
},
// 生成切片文件
createFileChunk(file) {
const fileChunkList = [];
let cur = 0;
// 判断这个文件大小需不需去切割
while (cur < file.size) {
fileChunkList.push({
file: file.slice(cur, cur + this.size),
fileName: file.name
});
cur += this.size;
}
return fileChunkList;
},
// 调接口的方法 直接返回一个promise对象
uploadFiles(formData) {
return this.$axios.post('roles', formData, {headers: {'Content-Type': 'multipart/form-data'}});
},
// 取消
onCancel() {
},
// 确认
async onOk() {
// 使用表单形式
const requestList = this.uploadList.map(({chunk, hash}) => {
const formData = new FormData();
formData.append("chunk", chunk);
formData.append("hash", hash);
formData.append("filename", this.file.name);
return {formData};
}).map(async ({formData}) => {
this.uploadFiles(formData)
});
// 将分隔好的文件段一起向后端请求
await this.$axios.all(requestList)
// 分段请求后向后端发起合并请求,让后端将文件拼接
await this.$axios('merge', {
file: this.file.name,
size: this.file.size
});
}
}
};
</script>
<style lang="less">
.treeTable {
margin-top: 15px
}
</style>