tsui66/blog

基于阿里云 OSS 文件上传统一服务

Opened this issue · 0 comments

当一个相同的项目功能存在于三个以上的项目的时候,我们就应该重新考虑是否该把他独立成单服务,比如我们最常见的阿里云 OSS 文件上传服务。本文就「基于阿里云 OSS 文件上传统一服务」相对简单的工程做一个介绍。效率工程的探索我们应该跳出代码、框架聚焦于约定,这些实现都需要多方配合,如前端、后端以及运维人员。本文仅提供参考,细节上会有很多瑕疵。你可以从本文学到:

  • 阿里云 OSS STS 临时授权访问机制实现
  • 前后端组件化思路
  • 代码工程实现

STS 原理

阿里云 OSS 可以通过阿里云 STS(Security Token Service)进行临时授权访问。通过 STS,您可以为第三方应用或子用户(即用户身份由您自己管理的用户)颁发一个自定义时效和权限的访问凭证。

img

服务实现

凭证服务

// 创建临时 oss policy, 用户客户端直传凭证
  createSTSPolicy(expiration, accessKeyId, accessKeySecret, region, bucket) {
    const dirPath = `${bucket}/`;
    let policyString = {
      expiration, // 设置该Policy的失效时间,超过这个失效时间之后,就没有办法通过这个policy上传文件了
      conditions: [
        [ 'content-length-range', 0, 1048576000 ],
        [ 'starts-with', '$key', dirPath ],
      ],
    };
    policyString = JSON.stringify(policyString);
    const policy = new Buffer(policyString).toString('base64');
    const signature = crypto.createHmac('sha1', accessKeySecret).update(policy).digest('base64');
    const new_multipart_params = {
      OSSAccessKeyId: accessKeyId,
      host: `https://${bucket}.${region}`,
      policy,
      signature,
      expiration,
      startsWith: dirPath,
    };
    return new_multipart_params;
  }
// 假设调用该服务 API 为: http://127.0.0.1:7001/api/oss/credentials?bucket=xxx
// bucket 一个消费方服务对应一个

使用方式

import { Injectable, HttpService } from '@nestjs/common';
import { ConfigService } from 'nestjs-config';
@Injectable()
export class OssService {
  constructor(
    private readonly httpService: HttpService,
    private readonly configService: ConfigService
  ) {}
  async findAll(): Promise<any> {
    const res = await this.httpService.get(`http://127.0.0.1:7001/api/oss/credentials?bucket=workshop`).toPromise();
    return res.data;
  }
}
// 返回值说明, 以下字段阿里云文档都有说明,传送门在文末「引用」
{
    "code": 200,
    "data": {
        "OSSAccessKeyId": "STS.NTr85byvDqhXVNYej3HASP7v8",
        "host": "https://${bucket}.oss-cn-hangzhou.aliyuncs.com",
        "policy": "eyJleHBpcmF0aW9uIjoiMjAyMS0wNS0xNVQxMDoxMjoyNVoiLCJjb25kaXRpb25zIjpbWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsImRjLXdvcmtzaG9wLyJdXX0=",
        "signature": "WpkqHH4ori4NI1Du1U6lV37xcWI=",
        "expiration": "2021-05-15T10:12:25Z",
        "startsWith": "${bucket}/",
        "SecurityToken": "CAIS/QF1q6Ft5B2yfSjIr5fHc4/WlKllxqqzVGjogWpmRM5/v/Ld1Tz2IHhIfXdtB+wYsv8+nmxS6/oTlqp6U4cdtbUh+TU3vPpt6gqET9frma7ctM4p6vCMHWyUFGSIvqv7aPn4S9XwY+qkb0u++AZ43br9c0fJPTXnS+rr76RqddMKRAK1QCNbDdNNXGtYpdQdKGHaOITGUHeooBKJXBMx5lIj0D4it/Xvk5PM0HeE0g2mkN1yjp/qP52pY/NrOJpCSNqv1IR0DPGZjHcOtkYWqvst3fYYo2qb78vuCl1Q8giANPHP7tpsIQl2a643AadYq+Lmkvl1qhwBQyxuTeUmGoABaRdUV6l+4Ec/rgFXFl7vVvrLzmjukcGoSNkdxH3Eee9UfZvfruEcciRXy6vQAM8rMjeFsNUYIvtBmW5W3OztoG3TCscQAYH0x7YGCF/dygdnGjh4sD67C9S3B0R0fVD5LI1OQPbQd+JR1ePjAJfPpDrjSaBOvZX+fEsnPr8eLU4="
    },
    "message": "ok"
}

前端实现

 async ({ filename, file, onSuccess, onError }) => {
    const { data } = await getOssCredentials();
    const { host, policy, signature, startsWith, OSSAccessKeyId, SecurityToken } = data;

    const key = `${
      startsWith + String(new Date().getTime()) + parseInt(String(Math.random() * 100), 10)
    }.png`;
    const formdata = new FormData();
    formdata.append('name', filename);
    formdata.append('key', key);
    formdata.append('policy', policy);
    formdata.append('OSSAccessKeyId', OSSAccessKeyId);
    formdata.append('success_action_status', 200);
    formdata.append('Signature', signature);
    formdata.append('x-oss-security-token', SecurityToken);
    formdata.append('file', file);

    request.post(`${host}`, { body: formdata }).then(() => {
      const url = `${host}/${key}`; // url 即为文件链接
      const { onChange } = this.props;
      onSuccess({ url });
      if (onChange) {
        onChange(url, file);
      }
    }, onError);
  };

引用