使用阿里云OSS实现文件上传(普通上传和分片上传),前端实现
第一次接触这种文件上传,当时接到这个需求我都是无从下手的,后端大哥告诉我:项目有上传图片和好几个g的大文件,这就需要使用到分片上传,就丢给了我几个阿里云的链接...... 作为一名勤奋好学的CV工程师,立马去网上查了相关资料,总结了这次需求:
- 什么时候用普通上传或者分片上传?(参考链接)
- 无论普通上传还是分片上传我都需要把上传的回调发送给阿里云告诉它我已经上传完了文件(参考链接)
- 上传过程中显示上传进度
- 上传完成后返回文件对应的下载地址
- 获取视频或音频的时间
引入aliyun-oss SDK
我这边是直接使用npm下载
npm install ali-oss
HTML部分
<el-upload
class="upload-demo"
drag
action="#"
multiple
:http-request="uploadFile"
:show-file-list="false"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
<el-table height="50vh" :data="tableData" style="width: 100%">
<el-table-column
prop="fileName"
label="文件名称"
width="180"
align="center"
>
</el-table-column>
<el-table-column prop="progress" label="进度" width="180" align="center">
<template slot-scope="{ row }">
<el-progress v-show="row.showValue == 1" :percentage="row.progress" />
</template>
</el-table-column>
<el-table-column prop="fileType" label="文件类型" align="center">
</el-table-column>
<el-table-column prop="fileSize" label="文件大小" align="center">
</el-table-column>
<el-table-column prop="status" label="上传状态" align="center">
<template slot-scope="{ row }">
<span>{{ load_status[row.status].title }}</span>
</template>
</el-table-column>
</el-table>
引入stsToken接口,工具类函数和定义变量
readVisitUrl函数:获取文件访问路径 chinese2Spell函数:将简体中文转拼音 getDateFolder函数:获取文件上传时间(年月日)
import { getStsToken } from "@/api/upload";
import { readVisitUrl, chinese2Spell, getDateFolder } from "@/utils/component";
const OSS = require("ali-oss");
var credentials = null; // STS凭证
var ossClient = null; // oss客户端实例
const partSize = 1024 \* 1024 \* 60; // 每个分片大小(byte)-我设置了60兆
const parallel = 4; // 同时上传的分片数
const checkpoints = {}; // 所有分片上传文件的检查点
上传文件事件 获取STS凭证,创建OSS Client
uploadFile({ file }, fileLists) {
this.$message({
type: "info",
message: "上传中",
});
getStsToken()
.then(({ data, errCode }) => {
if (errCode === 200) {
credentials = data;
let {
AccessKeyId,
AccessKeySecret,
SecurityToken,
Bucket,
OssRegion,
} = credentials;
ossClient = new OSS({
accessKeyId: AccessKeyId,
accessKeySecret: AccessKeySecret,
stsToken: SecurityToken,
bucket: Bucket,
secure: true,
region: OssRegion,
folder: "test",
timeout: 1000 * 600,
});
if (ossClient) {
// 如果文件小于分片大小,使用普通上传,否则使用分片上传
let fileInfo = {
fileName:
getDateFolder() +
"/" +
new Date().getTime() +
"_" +
chinese2Spell(file.name),
fileType: file.type,
progress: 0,
fileSize: file.size,
status: 1, //上传状态
};
file.attrInfo = fileInfo;
if (file.size < partSize) {
fileInfo.showValue = 1;
this.tableData.push(fileInfo);
this.commonUpload(file, fileInfo);
} else {
fileInfo.showValue = 1;
this.tableData.push(fileInfo);
this.multipartUpload(file, fileInfo);
}
} else {
this.$message({
type: "warning",
message: "initOSSClient异常空,请刷新重试或联系管理员",
});
}
} else {
this.statusMsg = "sts临时凭证获取失败,请刷新重试或联系管理员";
}
})
.catch((err) => {
console.error(err);
this.$message({
type: "warning",
message: "initOSSClient异常,请刷新重试或联系管理员",
});
});
},
普通上传(60MB以内的文件)
文档: 通常在文件大于100 MB的情况下,建议采用分片上传的方法,通过断点续传和重试,提高上传成功率。如果在文件小于100 MB的情况下使用分片上传,且partSize设置不合理的情况下,可能会出现无法完整显示上传进度的情况。对于小于100 MB的文件,建议使用简单上传的方式。
commonUpload(file, fileInfo) {
var headers = {};
var fileName = chinese2Spell(file.name);
return ossClient
.put(
getDateFolder() + "/" + new Date().getTime() + "_" + fileName,
file,
{
headers,
progress: this.onMultipartUploadProgress,
callback: {
// 设置回调请求的服务器地址。
url: process.env.VUE_APP_API_URL + "/admin/ossCallback",
// 设置回调请求消息头中Host的值,即您的服务器配置Host的值。
host: process.env.VUE_APP_API_HOST,
/* eslint no-template-curly-in-string: [0] */
// 设置发起回调时请求body的值。
body: "bucket=${bucket}&object=${object}&size=${size}&mimeType=${mimeType}&imageInfo.height=${imageInfo.height}&imageInfo.width=${imageInfo.width}&imageInfo.format=${imageInfo.format}&my_var=${x:my_var}",
// 设置发起回调请求的Content-Type。
contentType: "application/x-www-form-urlencoded",
// 设置发起回调请求的自定义参数。
customValue: {
"x:my_var": "value1",
},
},
}
)
.then((result) => {
console.log(
`Common upload ${file.name} succeeded, result === `,
result
);
this.$message({
type: "success",
message: "上传成功",
});
fileInfo.fileName = result.name;
fileInfo.status = 2;
fileInfo.progress = 100;
readVisitUrl(result.url);
this.$emit("change", result.url);
this.$emit("iconUrl", result.url);
})
.catch((err) => {
this.$message({
type: "warning",
message: "上传失败",
});
this.$emit("change", "");
console.log(`Common upload ${file.name} failed === `, err);
fileInfo.status = 0;
fileInfo.showValue = 0;
});
},
分片上传
multipartUpload(file, fileInfo) {
var headers = {};
var dateFolder = getDateFolder();
var dateStr = new Date().getTime();
var fileName = chinese2Spell(file.name);
return ossClient
.multipartUpload(dateFolder + "/" + dateStr + "_" + fileName, file, {
headers,
parallel,
partSize,
progress: this.onMultipartUploadProgress,
callback: {
// 设置回调请求的服务器地址。
url: process.env.VUE_APP_API_URL + "/admin/ossCallback",
// 设置回调请求消息头中Host的值,即您的服务器配置Host的值。
host: process.env.VUE_APP_API_HOST,
/* eslint no-template-curly-in-string: [0] */
// 设置发起回调时请求body的值。
body: "bucket=${bucket}&object=${object}&size=${size}&mimeType=${mimeType}&imageInfo.height=${imageInfo.height}&imageInfo.width=${imageInfo.width}&imageInfo.format=${imageInfo.format}&my_var=${x:my_var}",
// 设置发起回调请求的Content-Type。
contentType: "application/x-www-form-urlencoded",
// 设置发起回调请求的自定义参数。
customValue: {
"x:my_var": "value1",
},
},
})
.then((result) => {
this.$message({
type: "warning",
message: "上传成功",
});
// this.loading = false;
fileInfo.status = 2;
var url = `${dateFolder}/${dateStr}_${fileName}`;
console.log("文件名称", url);
readVisitUrl(result.url);
fileInfo.fileName = url;
this.$emit("change", url);
this.imgSrc = url;
// this.fileType(file, url);
this.getAudionTime(file);
})
.catch((err) => {
this.$message({
type: "warning",
message: "上传失败",
});
this.$emit("change", "");
console.log(`Multipart upload ${file.name} failed === `, err);
this.progressValue = 0;
fileInfo.showValue = 0;
fileInfo.status = 0;
});
},
上传进度
onMultipartUploadProgress(progress, checkpoint) {
console.log(`${checkpoint.file.name} 上传进度 ${progress}`);
checkpoints[checkpoint.uploadId] = checkpoint;
checkpoint.file.attrInfo.progress = Math.round(progress * 100);
},
获取音频/视频时长
getAudionTime(file) {
let fileUrl = URL.createObjectURL(file);
let audioElement = new Audio(fileUrl);
let time;
audioElement.addEventListener("loadedmetadata", function (_event) {
time = Math.round(audioElement.duration);
});
},
最后呈现的界面效果:
遇到的问题:
按照文档写完,测试了一小波,刚开始上传了一些小文件,普通上传没问题,去网上下载了500多兆,700多兆和2GB大小的视频,刚开始上传500多兆和700多兆的视频,ok提示上传成功,感觉胜利就在眼前,好家伙,上个厕所回来发现2G的视频提示上传失败,控制台显示 ConnectionTimeoutError: Connect timeout for 60000ms... 连接超时,联想到办公室的网络确实被我们吐槽了好几遍.无奈,只能去看看文档有没有对应的解决方法,没想到还真有:
如果通过标准OSS访问域名(例如oss-cn-hangzhou.aliyuncs.com)访问OSS的数据,且这是通过运营商网络对OSS进行访问,由于网络环境的复杂性,例如在一些弱网络环境下或网络不稳定的情况,就会在上传过程中遇到ConnectionTimeOut的网络错误
对应的解决方法:
- 上传方式采用分片断点上传,每个分片的大小不超过1MB。
- 添加重传机制,保证某一个分片上传失败后还可以继续上传。
- 增大超时时间。
- 通过CDN全站加速服务来提升OSS传输
我试了第三个方法,在分片上传的时候延长了时间:
timeout: 120000
重新上传了2G的视频,发现没有上次的错误了,不死心的我又下载了3G和4G的视频,发现也没有问题,perfect!
转载自:https://juejin.cn/post/7238507402834968637