一文让你彻底会用对象存储OSS的前端直传,不懂就再看一遍!(bushi)

3,102 阅读7分钟

原由

在项目里有时候会碰到比如上传文件相关的,一般都是后端提供个接口,然后我们上传的时候后端再传到阿里OSS或者其他服务商的对象存储,然后把最终的url拿到存起来或者返回给前端,这种方式其实在上传图片的频率不高的业务场景中可能并无大碍,但是如果你的项目是相册类的,资源提供类的,总之就是有很频繁的上传文件的场景,可能服务器的带宽就有点扛不住了,那么有没有更好的解决方案呢?

服务端签名,客户端直传

其实像阿里、腾讯、七牛等云服务厂商都提供的有类似阿里的STS(Security Token Service)临时访问权限管理服务,这次就以阿里云为例,给大家介绍下如何使用STS Token,来实现在服务端签名出STS token,然后提供给前端,让前端直接用这个Token向阿里云直传文件

服务端签名,获取到STS token

我们这里直接以Node.js为例,其他语言的服务可以在阿里云的SDK参考(STS)文档里面找到,有Python、Java...

首先我们需要先装一个sts-sdk的npm包:@alicloud/sts-sdk(Nodejs version >= 8.5.0)

npm install @alicloud/sts-sdk

然后我们在utils新建一个文件oss-sts-server.js,用来生成STS Token提供给前端使用(这里只作为实例,后续大家可以自行封装)

const StsClient = require('@alicloud/sts-sdk');

/**
 * 生成STStoken
 * @param accessKeyId AccessKey ID
 * @param accessKeySecret 从STS服务获取的临时访问密钥AccessKey Secret
 * @param roleArn 指定角色的ARN
 * @param roleSessionName 时临时Token的会话名称,自己指定用于标识你的用户,或者用于区分Token颁发给谁
 * @param durationSeconds token 有效事件,单位:秒
 * @param policy 指定的授权策略 默认为null
 * @return
 *   RequestId, 请求id
 *   AssumedRoleUser: {
 *     Arn, ${roleArn}/${roleSessionName}
 *     AssumedRoleId
 *   },
 *   Credentials: {
 *     SecurityToken, sts token
 *     AccessKeyId, accessKeyId
 *     AccessKeySecret, accessKeySecret
 *     Expiration 过期时间
 *   }
 */
export default function generateSTSToken(accessKeyId, accessKeySecret, roleArn, roleSessionName = 'external-username', durationSeconds = 3600, policy = null) {
  const sts = new StsClient({
    endpoint: 'sts.aliyuncs.com', // check this from sts console
    accessKeyId, // check this from aliyun console
    accessKeySecret // check this from aliyun console
  });
  return res = await sts.assumeRole(roleArn, roleSessionName, policy, durationSeconds);

这个generateSTSToken函数的几个入参我来解释一下,通常我们在用阿里云或者腾讯云的时候通常会开一个RAM账户也是就子账户,我们用子账户登录到阿里云后台后,到对象存储(OSS)控制台页面,找到安全令牌(子账号授权),也就是下图中标记的地方,点击上面的前往RAM控制台按钮

image.png

随后点击开始授权按钮,之后你就可以得到accessKeyIdaccessKeySecretroleArnroleSessionName还有默认的过期时间DurationsSeconds,如下图所示,由于我之前授权过一次,所以会有左下角这个提示,这几个参数一定到保存好,不要泄露,一旦泄露,请更改RAM账户密码,并重新生成,使之前的失效

image.png

完善服务端提供的数据

这个时候其实已经拿到accessKeyIdaccessKeySecretstsTokenexpiration这四个参数了

但是客户端还需要bucket对象存储的命名空间regionbucket所在地域这两个参数

这个bucket其实就是对应的使用的那个bucket,这个可以在阿里云对象存储页面看到,有一个bucket列表,就是你要是用的那个bucket的名字

region就是某一个bucket所在的地域,比如我这个就是oss-cn-beijing

image.png

此时服务端的工作已经完结了,可以提供前端一个接口,通过鉴权之后,返回给前端这么几个参数,接下来,让我们把舞台交给我们的前端~

{
  accessKeyId,
  accessKeySecret,
  stsToken,
  bucket,
  region,
  expiration
}

前端的工作

好了,我们的后端同学的工作已经完成了~

前端er们来跟我 左边一起画个龙 在你右边 画一道彩虹(bushi)

首先我们也新建一个oss-sts-client.js/ts,然后安装一个ali-sdk/ali-oss: Aliyun OSS(open storage service) JavaScript SDK for the browser and Node.js (github.com)的包,对了不支持IE10和之前的IE版本啊

npm install ali-oss --save

然后复制下面的内容到这个文件中,用js的同学可以把ts相关的代码删掉(赶紧换到TS吧,再不换没人跟你玩了)

// 这个是服务端提供给前端的一个请求接口,返回上面我们提到的几个参数
import { getOssSTSToken } from "./request"; 
// @ts-ignore 忽略ts报错,ali-oss赶紧提供@types包吧,文档难看懂,库也没个文档,你们文档要是维护的好,我还用写这个?我都不想吐槽……(bushi)
import OSS from 'ali-oss'

type OssStsType = {
  accessKeyId: string
  accessKeySecret: string
  stsToken: string
  expiration: number // 这个是前端计算出的还有多少秒token过期
  region: string
  bucket: string
}

/**
 * 获取OSSClient
 * @param accessKeyId AccessKey ID
 * @param accessKeySecret 从STS服务获取的临时访问密钥AccessKey Secret
 * @param stsToken 从STS服务获取的安全令牌(SecurityToken)
 * @param region Bucket所在地域
 * @param bucket Bucket名称
 */
export default async function getOssClient () {
  const { code, data: params } = await getOssSTSToken();
  if (code !== 200) return false; // 如果请求出错,在上游处理
  const client = new OSS({
    ...params,
    refreshSTSTokenInterval: params.expiration,
    // 刷新临时访问凭证的时间间隔,单位为毫秒。
    //(这个refreshSTSToken是文档里的,为了保险各位可以在每次上传前先检查一次过期没有,不要依赖提供的这个方法)
    refreshSTSToken: async () => {
      const { code, data } = await getOssSTSToken(); // 过期后刷新token
      if (code === 200) {
        return data
      }
    },
  })
  return client
}

好了,到现在为止我们已经封装好了这个前端需要在上传文件的时候调用的方法了

前端维护STS Token

首先我们在前端页面第一次上传文件的时候,要调用这个getOssClient方法获取到oss-client这个对象实例,才能用这个实例进行上传操作,之后上传的时候需要先判断一下token过期了没有,如果没有过期,还是用这个实例进行上传操作,如果过期了,重新生成一个实例!

这里我们就拿一个简单的上传小文件为例(大文件分片上传,和上传成功回调(需要后端同学提供回调地址) 可以自己去看文档,我就不展开细说了)

async function uploadFileAction(file, client) {
  let newClient = client;
  // 伪代码:
  // if (!newClient || token is expired) { // 如果是没有实例对象或者token过期了就要重新生成
  //  newClient = await getOssClient(); // 调用上面我们封装好的一个方法
  // }
  const filePath = 'xxx/xxx/' // 最中在bucket中的存放的路径根据业务需要自行设置,文件名也是可以自行设置
  const { res, name, url } = await newClient.put(`${filePath}${file.name}`, file);
  if (res.status === 200) {
    // 这里拿到上传成功的文件的url
    return url
  }  
}

关于这里oss-client的维护策略,各位就仁者见仁智者见智吧,方案很多,怎么贴合业务怎么来,但是不推荐往localStoragesessionStorageindexDB里面存STS token等那些参数,你怎么就确定你的用户不是一名前端er呢?

CORS的问题

还没完啊,xdm 稍等一下,以上的都完了之后,我们在本地联调的时候如果没有开代理还是会有CORS的问题,这时候还是要去服务端去配置,找到跨域设置,进去创建一个规则,方法看你用什么就勾上什么,来源允许Headers 直接给干成*就完事了

image.png image.png

总结

笔者也是在接触阿里云的前端直传文档之后和后端同学看文档看到头皮发麻 麻了彻底麻了 麻中麻之后,总结一篇前后端流程都可以打通的OSS前端直传的文章,如果有问题,欢迎在评论里讨论,如果能帮助到你,给咱个三连吧

519ae95b7f18308175d22538de7aa8e6.jpeg