基于 el-upload 和 ali-oss 的上传组件

背景

素材文件存储从七牛云迁移到了阿里云,后续用户上传的文件由前端直接上传到阿里云。考虑到这个调试过程比较麻烦以及别的项目组也有上传的需求,为了节省其他人的开发时间,所以把上传这一块功能封装成了一个公用的组件。

接入 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 源码

源码结构

el-upload-code-frame

上传主要的方法

--2021-07-19---1.57.26

第一版组件

实现的功能

前端直接把文件上传到 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。具体类型判断方法如下流程图:
--2021-07-19---4.26.50

最终实现的效果

上传文件可以有两种方式:前端直接把文件上传到 OSS 和通过 http-request 钩子函数实现服务端上传文件到 OSS。

------_032359a4-e754-4d6d-8606-17097a7ff313

引用

Browserjs.sdk 官方文档

展示评论