OSS Practice of Direct File Transfer (1): Server

foreword

In daily development, the general process of uploading files from the client is as follows: the client sends the file to the server, and the server dumps the file to a dedicated storage server or a storage service of a cloud computing vendor (such as Alibaba Cloud OSS). One disadvantage of this is that the upload link occupies the bandwidth of the server, and single-digit concurrent uploads can fill up the bandwidth, resulting in poor user experience. This problem can be avoided by directly uploading files from the client to a third-party storage service.

This article takes Alibaba Cloud OSS (Object Storage Service) as an example to describe in detail the overall process of directly transferring files from the client to OSS, and provides a complete code demonstration.

Advantages and disadvantages

From the "client-server-OSS" transmission mode to the "client-OSS" mode, the biggest advantage is that the uploading server step is omitted, and the uploading efficiency is higher and the speed is faster (compared to The bandwidth of the general server can be considered as "almost unlimited" of the bandwidth of OSS).

Of course, this mode also has disadvantages, that is, it adds a lot of extra development work, mainly including 2 parts: ​

(1) The server side adds the code for generating and uploading OSS credentials.

(2) The client adds the code for obtaining and uploading OSS credentials from the server and adapts to the direct OSS.

On the whole, the direct transfer mode has almost no disadvantages from the architectural level, except for a little more development workload.

process

In fact, the whole process is very simple and consists of two steps: ​

(1) The client sends a request to the server to obtain the certificate for direct OSS.

(2) The client uploads the file to the OSS and carries the certificate.

logical disassembly

For how to generate a certificate (also called a signature), you can read the official document ( help.aliyun.com/document_de… ), but since the document was created relatively early, it is difficult for novices to understand it. This article will show you the whole process step by step.

The whole process of "generating and uploading OSS credentials" actually does the following:

(1) The upload credential authentication is policyprovided , which is generated according to the private configuration policy.

(2)由于上传环节脱离了开发者服务器,因此你可以在 policy 中定义各种限制,例如上传最大体积、文件名等。

(3)将 policy 转化为指定的格式。 ​

代码实现

我们先考虑将流程的每一步实现,然后再将流程代码封装成函数。 ​

OSS 配置

首先定义 OSS 的配置文件,关于配置项的内容,可以参考文档:help.aliyun.com/document_de…

/** OSS 配置项 */
const ossConfig = {
  bucket: 'xxxxxxxx',
  accessKeyId: 'xxxxxxxx',
  accessKeySecret: 'xxxxxxxx',

  /** OSS 绑定的域名 */
  url: 'xxxxxxxx',
}
复制代码

policy 内容

对于 policy ,有很多配置项,我们先考虑生成“写死”的模式,然后再优化为由函数参数传入配置项。以下是一个最基础的 policy 。 ​

有效期

首先定义一个有效时长(单位:毫秒),然后该凭证的有效截止时间则为“当前时间 + 有效时长”,最后需要转化为 ISO 时间字符串格式。 ​

/** 有效时长:例如 4 小时 */
const timeout = 4 * 60 * 60 * 1000

/** 到期时间:当前时间 + 有效时间 */
const expiration = new Date(Date.now() + timeout).toISOString()
复制代码

文件名

文件名建议使用 UUID(笔者习惯性使用去掉短横线的 UUID),避免重复。 ​

import { v4 as uuidv4 } from 'uuid'

/** 随机文件名(去掉短横线的 uuid) */
const filename = uuidv4().replace(/-/gu, '')
复制代码

一般建议按照不同的业务模块,将文件划分不同的目录,例如这里使用 file 目录,那么完整的 OSS 文件路径则为: ​

/** 目录名称 */
const dirname = 'file'

/** 文件路径 */
const key = dirname + '/' + filename
复制代码

需要注意的是,文件路径不能以 “/” 开头(OSS 本身的要求)。 ​

将以上内容整合,就形成了 policy 文本,以下是一个基础格式: ​

const policyText = {
  expiration: expiration,
  conditions: [
    ['eq', '$bucket', ossConfig.bucket],
    ['eq', '$key', key],
  ],
}
复制代码

转化 policy

policyText 转化为 Base64 格式后,就是要求的 policy 了。

// 将 policyText 转化为 Base64 格式
const policy = Buffer.from(JSON.stringify(policyText)).toString('base64')
复制代码

然后对 policy 使用 OSS 密钥使用 HmacSha1 算法签名签名。

import * as crypto from 'crypto'

// 使用 HmacSha1 算法签名
const signature = crypto.createHmac('sha1', ossConfig.accessKeySecret).update(policy, 'utf8').digest('base64')
复制代码

最后将上述流程中的相关字段返回给客户端,即为“上传凭证”。 ​

进一步分析

以上完整演示了整个流程,我们进一步分析,如何将其封装为一个通用性的函数。 ​

(1)凭证的有效时长可以根据不同的业务模块分别定义,于是做成函数配置项。

(2)目录名称也可以做成配置项。

(3) policy 还有更多的配置内容(见文档 help.aliyun.com/document_de…),可以抽取一部分做成配置项,例如“允许上传的最大体积”。 ​

完整代码

以下是封装为“服务”的使用 Nest.js Web 框架的相关代码,来源自笔者的线上项目(略有调整和删改),供参考。 ​

import { Injectable } from '@nestjs/common'
import * as crypto from 'crypto'
import { v4 as uuidv4 } from 'uuid'

export interface GenerateClientTokenConfig {
  /** 目录名称 */
  dirname: string

  /** 有效时间,单位:小时 */
  expiration?: number

  /** 上传最大体积,单位:MB */
  maxSize?: number
}

/** 直传凭证 */
export interface ClientToken {
  key: string
  policy: string
  signature: string
  OSSAccessKeyId: string
  url: string
}

export interface OssConfig {
  bucket: string
  accessKeyId: string
  accessKeySecret: string
  url: string
}

@Injectable()
export class OssService {
  private readonly ossConfig: OssConfig

  constructor() {
    this.ossConfig = {
      bucket: 'xxxxxxxx',
      accessKeyId: 'xxxxxxxx',
      accessKeySecret: 'xxxxxxxx',

      /** OSS 绑定的域名 */
      url: 'xxxxxxxx',
    }
  }

  /**
   * 生成一个可用于客户端直传 OSS 的调用凭证
   *
   * @param config 配置项
   *
   * @see [配置内容](https://help.aliyun.com/document_detail/31988.html#title-6w1-wj7-q4e)
   */
  generateClientToken(config: GenerateClientTokenConfig): ClientToken {
    /** 目录名称 */
    const dirname = config.dirname

    /** 有效时间:默认 4 小时 */
    const timeout = (config.expiration || 4) * 60 * 60 * 1000

    /** 上传最大体积,默认 100M */
    const maxSize = (config.maxSize || 100) * 1024 * 1024

    /** 随机文件名(去掉短横线的 uuid) */
    const filename = uuidv4().replace(/-/gu, '')

    /** 文件路径 */
    const key = dirname + '/' + filename

    /** 到期时间:当前时间 + 有效时间 */
    const expiration = new Date(Date.now() + timeout).toISOString()

    const { bucket, url, accessKeyId } = this.ossConfig

    const policyText = {
      expiration: expiration,
      conditions: [
        ['eq', '$bucket', bucket],
        ['eq', '$key', key],
        ['content-length-range', 0, maxSize],
      ],
    }

    // 将 policyText 转化为 Base64 格式
    const policy = Buffer.from(JSON.stringify(policyText)).toString('base64')

    // 使用 HmacSha1 算法签名
    const signature = crypto.createHmac('sha1', this.ossConfig.accessKeySecret).update(policy, 'utf8').digest('base64')

    return { key, policy, signature, OSSAccessKeyId: accessKeyId, url }
  }
}
复制代码

在完整以上服务方法后,后续就可以在“控制器”层调用该方法用于分发上传凭证,客户端可直接使用该上传凭证将文件直传至 OSS 中。 ​

2021-08-17.png

Guess you like

Origin juejin.im/post/6997224305306107918