序文
日常の開発では、クライアントからファイルをアップロードする一般的なプロセスは次のとおりです。クライアントはファイルをサーバーに送信し、サーバーはファイルを専用のストレージサーバーまたはクラウドコンピューティングベンダー(Alibabaなど)のストレージサービスにダンプします。クラウドOSS)。これの欠点の1つは、アップロードリンクがサーバーの帯域幅を占有し、1桁の同時アップロードによって帯域幅がいっぱいになり、ユーザーエクスペリエンスが低下する可能性があることです。この問題は、クライアントからサードパーティのストレージサービスにファイルを直接アップロードすることで回避できます。。
この記事では、Alibaba Cloud OSS(Object Storage Service)を例として取り上げ、クライアントからOSSにファイルを直接転送するプロセス全体を詳細に説明し、完全なコードデモンストレーションを提供します。。
長所と短所
「client-server-OSS」送信モードから「client-OSS」モードへの最大の利点は、アップロードサーバーの手順が省略され、アップロード効率が高く、速度が速いことです(一般的なサーバーは、OSSの帯域幅の「ほぼ無制限」と見なすことができます。。
もちろん、このモードには欠点もあります。つまり、主に2つの部分を含む多くの追加の開発作業が追加されます。
(1)サーバー側は、OSSクレデンシャルを生成およびアップロードするためのコードを追加します。
(2)クライアントは、サーバーからOSSクレデンシャルを取得してアップロードするためのコードを追加し、直接OSSに適応します。。
全体として、直接転送モードには、開発ワークロードが少し増えることを除いて、アーキテクチャレベルからの不利な点はほとんどありません。。
処理する
実際、プロセス全体は非常に単純で、2つのステップで構成されています:
(1)クライアントは、直接OSSの証明書を取得するようにサーバーに要求を送信します。
(2)クライアントはファイルをOSSにアップロードし、証明書を携帯します。。
論理的分解
証明書(署名とも呼ばれます)の生成方法については、公式ドキュメント(help.aliyun.com/document_de…)を読むことができますが、ドキュメントは比較的早く作成されたため、初心者には理解しにくいです。この記事プロセス全体を段階的に表示します。。
「OSSクレデンシャルの生成とアップロード」のプロセス全体は、実際には次のことを行います。
(1)アップロード資格認証は、によってpolicy
提供さます。これは、プライベート構成に従って生成されます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 中。