前端上传文件到阿里云 OSS

Table of Contents

上传文件到阿里云 OSS 可以后端传,也可以前端传,我目前碰到的公司项目都是后端传的,现在我自己的项目也用到 OSS 了,但因为服务器带宽问题,我选择了前端上传。

首先,创建 bucket,这个直接创建就可以了,然后设置 bucket 的跨域设置,否则会出现无法上传的问题。

然后,参考这篇文章《客户端签名直传》。阿里文档里有不少文章,这篇比较全面。

但我一开始参考的这篇文章:《Browser.js授权访问》,所以……中间有些过程挺莫名其妙的。这里也只能记录一下服务端和客户端的处理。

服务端

首先,安装 ali-oss 这个包,前端也要这个包。然后服务端代码如下:

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Credentials, STS } from 'ali-oss';
import { createCache } from 'cache-manager';

const cache = createCache({
  // 缓存时间,毫秒
  ttl: 3000 * 1000,
  // 还有多长时间到期就刷新,毫秒
  refreshThreshold: 3000,
});

@Injectable()
export class OssService {
  constructor(private configService: ConfigService) {}

  async getSTSToken() {
    const cacheKey = 'sts-token';
    const cached = (await cache.get(cacheKey)) as { credentials: Credentials };

    if (cached) {
      return {
        AccessKeyId: cached.credentials.AccessKeyId,
        AccessKeySecret: cached.credentials.AccessKeySecret,
        SecurityToken: cached.credentials.SecurityToken,
      };
    }

    const sts = new STS({
      accessKeyId: this.configService.get('ALI_OSS_ACCESS_Key'),
      accessKeySecret: this.configService.get('ALI_OSS_SECRET'),
    });

    // roleArn填写步骤2获取的角色ARN,例如acs:ram::175708322470****:role/ramtest。
    // policy填写自定义权限策略,用于进一步限制STS临时访问凭证的权限。如果不指定Policy,则返回的STS临时访问凭证默认拥有指定角色的所有权限。
    // 3000为过期时间,单位为秒。
    // sessionName用于自定义角色会话名称,用来区分不同的令牌,例如填写为sessiontest。
    const result = await sts.assumeRole(
      'xxxxxxxx',
      ``,
      3000,
      'sessiontest',
    );

    cache.set(cacheKey, result);

    const data = {
      AccessKeyId: result.credentials.AccessKeyId,
      AccessKeySecret: result.credentials.AccessKeySecret,
      SecurityToken: result.credentials.SecurityToken,
    };

    return data;
  }
}

为了避免重复请求,我这里用缓存。相关包是 cache-manager。

客户端

客户端的代码如下:

import { apiBundle } from '@/boot/axios'
import type * as OSSTypes from './types'
import OSS from 'ali-oss'

/** 上传文件到 oss */
export async function uploadToAliOss(file: File, filename: string) {
  // 客户端不缓存凭证,因为客户端的时间不可靠,对比过期时间可能有问题
  const { data } = await apiBundle.API<OSSTypes.STSResponseData>({
    url: '/oss/sts/token',
    method: 'get',
  })
  const client = new OSS({
    secure: true,
    bucket: 'xxx',
    region: 'oss-cn-xxx',
    accessKeyId: data.AccessKeyId,
    accessKeySecret: data.AccessKeySecret,
    stsToken: data.SecurityToken,
  })

  const result = await client.put(filename, file)
  return result
}

阿里文章里是在客户端判断的过期时间,但因为客户端的时间不可控,所以我这里没有判断,反正服务端我已经做了缓存了。缺点就是每次上传前都要请求一下服务端。