上传视频的时候,如果视频体积过大,一次性上传就会出现各种问题,或者由于网络不佳的原因导致上传失败,这时候就需要大文件上传的常见方法,如切片上传,顾名思义,切片上传就是将一个大文件切成一份一份的小文件,再去将每一个小文件上传。
首先还是借助iview的Upload上传组件,绑定before-upload(上传文件之前的钩子)获取文件数据,将上传action地址设置为空,并return false取消自动上传。
<Upload
:show-upload-list="false"
:on-success="upVideoPicture"
:before-upload="handeleBeforeUpVideo"
:action="''"
:data="contLaunchUrl"
name="file"
type="drag"
style="display: inline-block; width: 150px"
>
<div style="width: 150px; height: 105px; line-height: 105px">
<Icon type="ios-camera" size="20"></Icon>
</div>
// iview组件 Spin加载中
<Spin fix v-if="uploadVideoLoading">
<Icon type="ios-loading" size=18 class="demo-spin-icon-load"></Icon>
<div>Loading</div>
</Spin>
</Upload>
在before-upload(上传文件之前的钩子)的函数中可以获取到文件数据,拿到数据开始切片,大小就以1M为例。
注意:上传服务器的文件名称最好设置为英文,中文可能会出现问题,所以我设置名称为时间戳+四位随机数。
时间戳:new Date().getTime()
四位随机数:Math.floor(Math.random()*(9999-1000))+1000
// 文件切片
fileChunk(file){
let that = this
// 文件名称设置为时间戳+四位随机数
let name = new Date().getTime() + '' + (Math.floor(Math.random()*(9999-1000))+1000);
let fileName = name // 获取文件名
let fileType = `${file.name.split('.')[length]}` // 文件类型后缀
let chunkSize = 1 * 1024 * 1024 // 示例1M
let chunkList = [] // 创建一个数组用来存储每一片文件流数据
if (file.size < chunkSize) { // 如果文件大小小于1M就只有一片,不用切
chunkList.push(file.slice(0)) // 文件流从第一个字节直接截取到最后就可以了
} else { // 如果文件大小大于1M 就需要切片了
var start = 0, end = 0 // 创建两个变量 开始位置 结束位置
while (true) { // 循环
end += chunkSize // 结束为止 = 结束位置 + 每片大小
let blob = file.slice(start, end) // 文件流从开始位置截取到结束位置
start += chunkSize // 截取完,开始位置后移
if (!blob.size) { // 如果截取不到了就退出
break;
}
chunkList.push(blob) // 把截取的每一片数据保存到数组
}
}
that.transformFileType(chunkList,fileName,fileType); // 文件类型转换
},
以下图片是切片完成后的数据,数据类型为blob
由于切完后的数据是blob类型,而我们接口上传需要文件类型,所以需要转换一下文件类型的,这里就根据后端给的接口需要什么类型参数去转换。
// 文件类型转换
async transformFileType(list,name,type){ // list,name,type是上传时需要的参数,可根据情况而定
let that = this
let uploadFileNum = 0; // 已经上传的文件数量
let num = localStorage.getItem("uploadFileNum")?localStorage.getItem("uploadFileNum"):0 // 已经上传文件数量
// 如果已经全部上传 直接合并
if(num == list.length) return that.mergeFileFun(name,list.length,type)
for (var i = num; i < list.length; i++) {
// 这么写是因为文件转换是异步任务
let transToFile = async(blob, fileName, fileType) => {
return new window.File([blob], fileName, {type: fileType})
}
// 转换完成后可以将file对象传给接口 fileFormData 是需要传递的参数
let fileFormData = new FormData();
const transToFileRes = await transToFile(list[i], "video", type)
fileFormData.append('file',transToFileRes)
fileFormData.append('fileName',name)
fileFormData.append('fileNum',Number(i)+1)
fileFormData.append('filePath','/video')
await that.uploadFileMethods(fileFormData,i,name,list,type) // 上传文件
}
},
数据类型转换完成后就可以调上传视频的接口了,再上传文件成功后往缓存里设置一个已上传文件数量标识,如遇到网络不佳,没有及时全部上传成功可利用这个标识找到没有上传的部分继续上传,加快上传速度。
cookie,sessionStorage,localStorage区别
有效时间:cookie在关闭浏览器后失效,sessionStorage在关闭页面后失效,localStorage需要手动清除才会失效;
数据存储大小:cookie只有4K左右,sessionStorage和LocalStorage一般有5M。
cookie会在请求头中一起发送给服务器,另外两个则不参与通信。
// 上传文件
async uploadFileMethods(fileFormData,uploadFileNum,name,list,type) {
let that = this
const instance = axios.create({
withCredentials: true
})
await instance({
url: this.uploadFile, // 上传文件地址
method: 'POST',
data: fileFormData,
}).then(res => {
// console.log(res,"res文件上传")
if(res.data.code != 200){
that.uploadVideoLoading = false;
that.$Message.error(res.data.message);
}
uploadFileNum = Number(uploadFileNum) + 1
localStorage.setItem("uploadFileNum", uploadFileNum); // 已经上传文件数量
// 如果已经上传数量uploadFileNum与切片长度一致,就开始合并文件
if(uploadFileNum == list.length){
that.mergeFileFun(name,list.length,type);
}
})
},
上传完成后调用合并文件的接口,在合并完成后,清除标记。
// 合并文件
mergeFileFun(name,fileNo,type){
let that = this
let fileFormData = new FormData(); // 合并文件接口参数
fileFormData.append('fileName',name) // 合并的该套文件的文件名
fileFormData.append('fileTotalNum',fileNo) // 该套文件总数量
fileFormData.append('fileSuffix',type) // 文件类型后缀
fileFormData.append('filePath','/video') // 该套文件存放一级路径
const instance = axios.create({
withCredentials: true
})
instance({
url: this.mergeFile, // 合并文件地址
method: 'POST',
data: fileFormData,
}).then(res => {
if(res.data.code != 200){
that.uploadVideoLoading = false;
that.$Message.error(res.data.message);
}
that.uploadVideoLoading = false;
let fileUrl = res.data.message; // 后端返回的文件路径
localStorage.removeItem('uploadFileNum') // 合并成功后清除已经上传文件数量标记
})
},