背景
素材文件存储从七牛云迁移到了阿里云,后续用户上传的文件由前端直接上传到阿里云。考虑到这个调试过程比较麻烦以及别的项目组也有上传的需求,为了节省其他人的开发时间,所以把上传这一块功能封装成了一个公用的组件。
接入 ali-oss
使用的 SDK
前端使用的是 Browserjs.sdk
支持的浏览器
- IE(>=10) 和 Edge
- 主流版本的 Chrome、Firefox、Safari
- 主流版本的 Android、iOS、WindowsPhone 系统默认浏览器
授权方式
使用 STS 临时授权,STS 有以下优势:
1、您无需透露您的长期密钥(AccessKey)给第三方应用,只需生成一个访问令牌并将令牌交给第三方应用。您可以自定义这个令牌的访问权限及有效期限。
2、您无需关心权限撤销问题,访问令牌过期后自动失效。
如何使用 Browserjs.sdk
- 安装 Browserjs.sdk
npm install ali-oss
- 安装完成后,即可使用 import 或 require 进行引用。由于浏览器中原生不支持 require 模式,因此需要在开发环境中配合相关的打包工具,例如 webpack、browserify 等。
let OSS = require('ali-oss');
let client = new OSS({
// yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
region: 'yourRegion',
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
accessKeyId: 'yourAccessKeyId',
accessKeySecret: 'yourAccessKeySecret',
// 从STS服务获取的安全令牌(SecurityToken)。
stsToken: 'yourSecurityToken',
// 填写Bucket名称。
bucket: 'examplebucket'
});
- 上传方式
因为有视频素材文件可能会比较大,所以选择了通过 MultipartUpload 接口进行分片上传。官网的建议是大于 100 MB 的文件,建议采用分片上传。
当需要上传的文件较大时,您可以通过 MultipartUpload 接口进行分片上传。分片上传是指将要上传的文件分成多个数据块(Part)来分别上传。当其中一些分片上传失败后,OSS 将保留上传进度记录,再次重传时只需要上传失败的分片,而不需要重新上传整个文件。一般大于100 MB的文件,建议采用分片上传的方法,通过断点续传和重试,提高上传成功率。
let OSS = require('ali-oss')
let client = new OSS({
// yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
region: 'yourRegion',
// 从STS服务获取的临时访问凭证。临时访问凭证包括临时访问密钥(AccessKeyId和AccessKeySecret)和安全令牌(SecurityToken)。
accessKeyId: 'yourAccessKeyId',
accessKeySecret: 'yourAccessKeySecret',
stsToken: 'yourSecurityToken',
// 填写Bucket名称。
bucket: 'examplebucket'
});
let client = new OSS(ossConfig);
let tempCheckpoint;
// 定义上传方法。
async function multipartUpload () {
try {
// 填写Object完整路径和本地文件的完整路径。Object完整路径中不能包含Bucket名称。
// 您可以通过自定义文件名(例如exampleobject.txt)或目录(例如mytestdoc/exampleobject.txt)的形式,实现将文件上传到当前Bucket或Bucket中的指定目录。
// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
let result = await client.multipartUpload('exampleobject.txt', 'D:\\localpath\\examplefile.txt', {
progress: function (p, checkpoint) {
// 断点记录点。浏览器重启后无法直接继续上传,您需要手动触发上传操作。
tempCheckpoint = checkpoint;
},
meta: { year: 2020, people: 'test' },
mime: 'text/plain'
})
} catch(e){
console.log(e);
}
}
// 开始分片上传。
multipartUpload();
// 暂停分片上传。
client.cancel();
// 恢复上传。
let resumeclient = new OSS(ossConfig);
async function resumeUpload () {
try {
let result = await resumeclient.multipartUpload('exampleobject.txt', 'D:\\localpath\\examplefile.txt', {
progress: function (p, checkpoint) {
tempCheckpoint = checkpoint;
},
checkpoint: tempCheckpoint,
meta: { year: 2020, people: 'test' },
mime: 'text/plain'
})
} catch (e) {
console.log(e);
}
}
resumeUpload();
- 上传的进度
配置 multipartUpload 第三个参数
const progress = function progress(p, checkpoint) {
console.log(p)
};
遇到的问题以及解决方案
问题1:创建跨域配置的时候跨域规则没有加上协议名导致跨域不成功
解决方案:加上协议名
此方法并不支持所有的视频格式,各个浏览器对视频格式的支持情况可以查看 视频格式
问题2:ConnectionTimeoutError 连接超时报错
解决方案:
1)在初始化 ali-oss 实例时,增加试错次数(retryMax)
// 假设浏览器环境当中已经引入OSS对象 可以是通过`script`或者`npm`方式引入
let store = new OSS({
accessKeyId: 'your access key',
accessKeySecret: 'your access secret',
bucket: 'your bucket name',
region: 'oss-cn-hangzhou',
retryMax: 3
});
2)配置分片上传配置信息
- 减小分片的大小(partSize)
- 增加超时限制(timeout)
- 增加一次处理片的数量(parallel)
// 定义上传方法。
async function multipartUpload () {
try {
let result = await client.multipartUpload('exampleobject.txt', 'D:\\localpath\\examplefile.txt', {
timeout: 2 * 60 * 1000,
partSize: 800 * 1024,
parallel: 6,
})
} catch(e){
console.log(e);
}
}
组件封装
封装思路
公司内部的项目用两套 UI 框架:ElementUI 和 AntDesign of Vue。最开始介入 OSS 的项目用的是 ElementUI, 因为 el-upload 已经是一个很成熟的上传组件了,所以最初的时候是直接使用 el-upload,在 http-request 钩子函数中传入 OSS 上传的处理逻辑。但是如果要兼容公司所有的项目的话,需要脱离 UI 框架,所以封装组件的思路是直接把 el-upload 的源码拉下来,然后在源码里面加入 OSS 上传逻辑处理。
el-upload 源码
源码结构
上传主要的方法
第一版组件
实现的功能
前端直接把文件上传到 OSS,并且把上传成功的文件信息(eg. 文件名、图片宽度高等)抛到组件外部。
实现方式
1、组件增加参数 STS 临时授权信息(stsConfig),分片上传配置信息(multipartUploadOptions),分片上传文件夹名称(ossPath)。
2、OSS 重名的文件名会发生覆盖(所以上传的文件名默认是用时间戳和随机数组成)。
3、直接把 OSS 上传逻辑写在自定义上传的方法 http-request 中。
4、上传发生错误的时候,调用 onError 钩子函数,向组件外部抛出错误;在分片上传配置方法中调用 onProgress 方法,向组件外部抛出上传进度;上传成功的话调用 onSuccess 钩子函数,向组件外部跑抛出上传成功的文件。
5、因为项目需要区分一下素材是文档、图片以及视频,文件类型判断实现的方式是增加了三个参数(数据类型都是数组):picAccept(分类为图片类型), videoAccept(分类为视频类型)以及 docAccept(分类为文档类型),不需要的文件类型置为空 []。
6、项目需要将素材的信息(eg. 视频的时长、宽高等)传给服务端,所以文件上传之后添加一下这一部分的处理逻辑。
存在的问题
CodeReview 的时候同事提出了以下问题
1、直接把 OSS 上传逻辑写在自定义上传方法中的话,后期需要使用自定义上传扩展会很麻烦。
2、文件类型处理那块不太灵活,万一以后增加一种新的文件类型还得增加参数
遇到的问题以及解决方案
问题1:无法直接获取视频时长,宽高等信息
解决方案:开通 视频点播服务 或者前端根据以下方法获取视频信息
function getVideoMsg(file) {
try {
return new Promise((resolve) => {
const VideoElement = document.createElement('video');
VideoElement.src = URL.createObjectURL(file);
VideoElement.addEventListener('loadedmetadata', () => {
resolve({
duration: VideoElement.duration,
height: VideoElement.videoHeight,
width: VideoElement.videoWidth
});
});
});
} catch (e) {
console.log(e);
}
}
第二版组件
新增功能
- 将文件上传到服务端,服务端把文件上传到 OSS
- 文件类型判断优化
实现方式
1、把第一版前端 OSS 上传逻辑从 http-request 方法中剥离出来。
2、组件增加一个参数 isServerUpload 是否服务端上传,服务端上传的话走 http-request,不是的话走 OSS 前端上传。
3、文件类型优化(这一部分和第一版不兼容)
新增加一个参数 type 表示接收文件的类型是, 这个参数的类型是数组, 默认值 ['pic', 'video', 'word'],删除第一版的参数 picAccept、videoAccept 以及 docAccept。具体类型判断方法如下流程图:
最终实现的效果
上传文件可以有两种方式:前端直接把文件上传到 OSS 和通过 http-request 钩子函数实现服务端上传文件到 OSS。