切片上传
我们在工作中,经常要实现图片,视频等上传功能,如果上传的文件很小,那么我们可以直接上传;但是,如果文件很大,比如500MB呢,我们还能直接上传吗?
那肯定是不行的,加入我们直接上传,就会发现这个上传的接口,响应时间很长,最终无法生效以至于报错。所以,在这里,我们就需要借助切片上传。
所谓的切片上传,就是把一个视频剪成几个小视频一个一个上传。比如:一个视频500MB,我们可以按照5MB一个文件流的大小,切成100份,异步上传到服务器,然后,后台再根据MD5一一识别,最后,我们再调用合并接口,让他们在后台进行合并。
提示:
1、切片上传需要后端写3个接口:
①、直接上传文件的接口;
②、切片后,每个文件上传的地址;
③、切片合并接口
2、需要引入MD5
3、为了方便,我这里使用Axios的写法
上面附上代码,这里的我的上传,每次只能上传一个文件,如果想要修改的话,自己去看element-uid的upload组件里面的属性。
最后,代码比较乱,很多地方未做优化,请多多见谅,毕竟,本人刚入行不久,写这个博客,也只是为了日后方便自己再次使用。
<template>
<div class="component-upload-image">
<el-upload
ref="upload"
:limit="1"
:accept="accept"
:headers="upload.headers"
:action="upload.url"
:disabled="upload.isUploading"
:auto-upload="false"
:http-request="uploadFile"
:on-change="changeStatus"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">
将文件拖到此处,或
<em>点击上传</em>
</div>
<div class="el-upload__tip" style="color: red" slot="tip">提示:仅允许导入图片、视频、文本!</div>
</el-upload>
<div v-show="progressFlag" class="head-img">
<el-progress
color="#f56c6c"
:text-inside="true"
:stroke-width="24"
:percentage="progressPercent"
status="success"
:format="format"
></el-progress>
</div>
<div slot="footer" class="dialog-footer" style="margin-top:10px">
<el-button
type="primary"
@click="submitFileForm"
:loading="bLoading"
:disabled="blDisabled"
>确定上传</el-button>
</div>
</div>
</template>
<script>
import { getToken } from "@/utils/auth";
import SparkMD5 from 'spark-md5'
import Axios from "axios";
export default {
props: {},
data() {
return {
// 可以上传的文件类型
accept: ".pdf, .txt, .text, .pptx, .ppt, .doc, .docx, .mp4, .jpg, .jpeg, .png",
// 文件直接上传的地址
uploadUrl: url1,// 文件直接上传的地址
chunkUpload: url2,// 文件切片后,每个切片上传的地址
composeFiles: url3,// 切片合并的地址
//分片大小 这里设置为20MB
chunkSize: 20 * 1024 * 1024,
// 加载按钮
bLoading: false,
blDisabled: true,
// 上传进度条
progressFlag: false,
progressPercent: 0,
// 文件弹窗
filePop: false,
// 上传 文件列表
fileList: [],
fileform: {},
fileformRules: {
value: [
{ required: true, message: "文件名称不能为空", trigger: "change" },
],
},
// 文件流
fileRow: new FormData(),
upload: {
// 是否显示弹出层(用户导入)
open: false,
// 弹出层标题(用户导入)
title: "上传",
// 是否禁用上传
isUploading: false,
// 设置上传的请求头部,一般要加上token
headers: { Authorization: getToken() },
// 上传的地址
url: "",
},
};
},
methods: {
changeStatus() {
this.blDisabled = false
},
/**
* @desc: 上传进度条到 100时显示 合成
*/
format(percentage) {
return percentage === 100 ? '正在合并文件,请稍等...' : `${percentage} % `;
},
/**
* @desc: 上传文件
*/
async uploadFile(e) {
const that = this
const file = e.file;
const fileSize = file.size
const mineType = file.type
// chunkSize是20MB
// console.log('切片视频', that.chunkSize);
that.fileRow.append("file", file);
let query = {1,2};// 这里是你上传接口应该传的参数
that.upload.isUploading = true;
that.bLoading = true
that.progressFlag = true
// 如果视频小于20MB,直传
if (fileSize <= that.chunkSize) {
// console.log("上传的文件大于20m才能分片上传")
Axios({
timeout: 1000,
method: "post",
url: that.uploadUrl,
headers: that.upload.headers,
params: query,
data: that.fileRow,
onUploadProgress: progressEvent => {
that.progressPercent = ((progressEvent.loaded / progressEvent.total) * 100).toFixed(0) | 0
},
}).then((res) => {
console.log(res.data);
that.upload.isUploading = false;
if (res.data.code == 200) {
that.$message.success("上传成功");
e.file.path = res.data.data.fileKey
this.blDisabled = true
console.log(154, e.file);
this.$emit('getFile', e.file)
that.progressFlag = false
that.progressPercent = 0
that.bLoading = false
that.upload.open = false;
that.$refs.upload.clearFiles();
that.fileReset();
that.fileRow.delete("file")
} else {
that.progressFlag = false
that.progressPercent = 0
that.$message.error(res.msg);
}
});
} else {
const chunkCount = Math.ceil(fileSize / that.chunkSize)
console.log("文件大小:", (file.size / 1024 / 1024) + "Mb", "分片数:", chunkCount)
//获取文件md5
const fileMd5 = await that.getFileMd5(file);
console.log("文件md5:", fileMd5)
console.log("向后端请求本次分片上传初始化")
//向后端请求本次分片上传初始化
const initUploadParams = JSON.stringify({ chunkCount: chunkCount, fileMd5: fileMd5 })
Axios({
timeout: 1000 * 60 * 60 * 1,
method: "post",
url: that.chunkUpload,
data: initUploadParams,
headers: that.upload.headers,
processData: true,
}).then(res => {
console.log(262, res.data);
//code = 0 文件在之前已经上传完成,直接走秒传逻辑;code = 1 文件上传过,但未完成,走续传逻辑;code = 200 则仅需要合并文件
if (res.data.code == 200) {
console.log("当前文件上传情况:所有分片已在之前上传完成,仅需合并")
that.composeFile(fileMd5, file.name, mineType)
return;
} else if (res.data.code == 0) {
console.log("当前文件上传情况:秒传")
return
} else if (res.data.code == 500) {
that.$alert("该文件上传出错,请重试!", '提示', {
confirmButtonText: '确定',
}).then(() => {
that.clearFile()
return
})
return
} else if (res.data.code == 401) {
console.log("请求401,表示没有权限");
that.$alert(res.data.msg, '提示', {
confirmButtonText: '确定',
}).then(() => {
that.clearFile()
return
})
} else if (res.data.code == 1) {
console.log("当前文件上传情况:初次上传 或 断点续传")
let UpData = res.data.data
let chunkNum = 0
let chunkLength = UpData.length
//若使用并发上传方式,当前分片上传完成后打印出来的完成提示是不准确的,但这并不影响最终运行结果;原因是由ajax请求本身是异步导致的
UpData.forEach((item) => {
//分片开始位置
let start = (item.partNumber - 1) * that.chunkSize
//分片结束位置
let end = Math.min(fileSize, start + that.chunkSize)
//取文件指定范围内的byte,从而得到分片数据
let _chunkFile = file.slice(start, end)
console.log("开始上传第" + item.partNumber + "个分片")
Axios({
timeout: 1000 * 60 * 60 * 1,
method: "put",
url: item.uploadUrl,
data: _chunkFile,
headers: { contentType: false },
processData: false,
}).then(res => {
console.log("第" + item.partNumber + "个分片上传完成", res)
chunkNum++
that.progressPercent = ((chunkNum / chunkLength) * 100).toFixed(0) | 0
console.log(that.progressPercent);
if (chunkNum == chunkLength) {
//请求后端合并文件
that.composeFile(fileMd5, file.name, mineType)
}
})
})
}
})
}
},
/**
* @desc:请求后端合并文件
*/
composeFile(fileMd5, fileName, mineType) {
const that = this
console.log("开始请求后端合并文件")
//注意:bucketName请填写你自己的存储桶名称,如果没有,就先创建一个写在这
const composeParams = JSON.stringify({
fileMd5: fileMd5,
fileName: fileName,
bucketName: "ruoyi",
mineType: mineType,
businessInstanceId: that.fileId,
businessType: "student_course"
})
Axios({
timeout: 1000 * 60 * 60 * 1,
method: "post",
url: that.composeFiles,
data: composeParams,
headers: that.upload.headers,
processData: false,
}).then(res => {
console.log(347, res);
that.upload.isUploading = false;
if (res.data.code == 200) {
console.log('视频合并完成!!!');
that.$message.success("上传成功");
e.file.path = res.data.data.fileKey
this.$emit('getFile', e.file)
this.blDisabled = true
that.progressFlag = false
that.progressPercent = 0
that.bLoading = false
that.upload.open = false;
that.$refs.upload.clearFiles();
that.fileReset();
} else {
that.progressFlag = false
that.progressPercent = 0
that.$message.error(res.data.msg);
}
})
},
/**
* 获取文件MD5
*/
getFileMd5(file) {
console.log('file', file);
let fileReader = new FileReader()
fileReader.readAsBinaryString(file)
let spark = new SparkMD5()
return new Promise((resolve) => {
fileReader.onload = (e) => {
spark.appendBinary(e.target.result)
resolve(spark.end())
}
})
},
/**
* @desc: 确定按钮,上传视频
*/
submitFileForm() {
// 获取文件尾缀
let fileName = this.$refs.upload.uploadFiles[0].name
let testmsg = (fileName.substring(fileName.lastIndexOf('.') + 1)).toLowerCase()
let size = this.$refs.upload.uploadFiles[0].size
// 判断尾缀的判断条件
const formatList = ["jpg", "jpeg", "png", "pdf", "txt", "text", "pptx", "ppt", "doc", "docx", "mp4"]
// const extension = ()
if (formatList.indexOf(testmsg) !== -1 && size < 512000000) {
this.$refs.upload.submit();
} else {
this.$alert("对不起!您导入的文件格式不对,或者导入的文件大小超过500MB!")
}
},
// 表单重置
fileReset() {
this.fileform = {
value: undefined,
};
this.resetForm("fileform");
},
/**
* @desc: 上传失败,清空上传表单
*/
clearFile() {
this.upload.isUploading = false;
this.upload.open = false;
this.$refs.upload.clearFiles();
this.progressFlag = false
this.progressPercent = 0
this.bLoading = false
},
}
};
</script>
<style scoped lang="scss">
// .el-upload--picture-card 控制加号部分
::v-deep.hide .el-upload--picture-card {
display: none;
}
// 去掉动画效果
::v-deep .el-list-enter-active,
::v-deep .el-list-leave-active {
transition: all 0s;
}
::v-deep .el-list-enter,
.el-list-leave-active {
opacity: 0;
transform: translateY(0);
}
</style>