阿里云OSS实践文件直传基于服务端

作者:赖一鸣 时间:2024-05-13 09:35:27 

前言

在日常开发中,客户端上传文件的一般流程是:客户端向服务端发送文件,再由服务端将文件转储到专门的存储服务器或云计算厂商的储存服务(例如阿里云 OSS)上,这样做的一个弊端是上传环节占用服务器的带宽,个位数的并发上传就能把带宽占满,从而导致用户体验下降。而如果直接将文件从客户端直传到第三方的存储服务上,就可以避免这个问题。

本文以阿里云 OSS(Object Storage Service,对象存储服务)为例,详细说明将文件从客户端直传 OSS 的整体流程,并提供了完整的代码演示。

优缺点

从“客户端 — 服务器 — OSS”的传输模式,变更为“客户端 — OSS”的模式,最大的好处是,省掉了上传服务器的这一步,上传效率更高,速度更快(相比较于一般服务器的带宽,可以认为 OSS 的宽带是“几乎无限”的)。

当然该模式也有缺点,那就是增加了很多额外的开发工作量,主要包含 2 部分:

(1)服务端增加生成上传 OSS 凭证的代码。

(2)客户端增加从服务端获取上传 OSS 凭证的代码和对直传 OSS 进行适配。

整体而言,直传模式除了增加一点开发工作量以外,从架构层面,几乎没有任何缺点。

流程

实际上,整个流程非常简单,包含了两步:

(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 中。 ?

来源:https://juejin.cn/post/6997224305306107918

标签:服务端,OSS,文件直传
0
投稿

猜你喜欢

  • 结合OpenCV与TensorFlow进行人脸识别的实现

    2021-07-06 08:44:28
  • 23条科学设计你网站的方法

    2008-03-23 14:12:00
  • Python接口自动化浅析pymysql数据库操作流程

    2024-01-16 16:33:02
  • 详解Python+Matplotlib绘制面积图&热力图

    2021-10-10 10:16:29
  • 用css+js给网页背景插入flash播放器

    2007-10-21 09:27:00
  • 使用bandit对目标python代码进行安全函数扫描的案例分析

    2021-04-07 02:01:22
  • 用python将word文档合并实例代码

    2021-08-13 06:17:01
  • js定时器怎么写?就是在特定时间执行某段程序

    2024-04-22 12:54:00
  • MySQL 备份还原数据库批处理

    2024-01-16 21:03:52
  • python爬取豆瓣评论制作词云代码

    2023-03-14 04:31:40
  • 解决Python复杂zip文件的解压问题

    2021-08-11 05:04:09
  • [翻译]寻找圣杯 In Search of the Holy Grail

    2008-11-12 13:10:00
  • 让你一文弄懂Pandas文本数据处理

    2023-07-17 19:12:08
  • 使用Gitee自动化部署python脚本的详细过程

    2022-03-30 07:04:55
  • Access保留字和变量名列表

    2007-10-15 12:43:00
  • 在Oracle中向视图中插入数据的方法

    2009-02-28 10:42:00
  • vue 二维码长按保存和复制内容操作

    2024-04-27 16:04:39
  • caffe的python接口生成配置文件学习

    2023-07-09 04:46:41
  • 深度剖析Golang中的数组,字符串和切片

    2024-02-17 08:53:13
  • 使用Python的Twisted框架编写非阻塞程序的代码示例

    2021-01-22 16:20:51
  • asp之家 网络编程 m.aspxhome.com