阿里云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


猜你喜欢
- python查找多层嵌套字典的值def find_dic(item, key): if isinstance(it
- 本文适用场景:想用Tkinter开发界面程序并屏幕居中,但没找到相应的API。这两天玩了玩Tkinter,感觉不错,就是屏幕居中这个问题在网
- 一、powershell中 python创建虚拟环境无法激活 二、管理员模式运行powershell,执行策略更改: S
- new fun的执行过程分析,学习面向对象的朋友可以参考下。(1)创建一个新的对象,并让this指针指向它;(2)将函数的prototype
- python基于新浪sae开发的微信公众平台,实现功能:输入段子---回复笑话输入开源+文章---发送消息到开源中国输入快递+订单号---查
- 前言正常图片转化成素描图片无非对图片像素的处理,矩阵变化而已。目前很多拍照修图App都有这一功能,核心代码不超30行。如下利用 Python
- 本文记录了PyCharm安装的图文教程,供大家参考,具体内容如下PyCharm的官网 1.在官网下载安装包2.选择Windows系
- 主要记录Python-OpenCV中的VideoCapture类的使用;官方文档;VideoCapture()是用于从视频文件、图片序列、摄
- 叨叨几句哈喽兄弟们,今天实现一下人脸识别。先问大家一个问题什么是百度Aip模块?百度AI平台提供了很多的API接口供开发者快速的调用运用在项
- SQL Server2005扩展函数已经不是一件什么新鲜的事了,但是我看网上的大部分都是说聚合函数,例子也比较浅,那么这里就讲讲我运用扩展函
- 我们来看看MD5加密码的实现:注意看一下他数据库里的加密位数!先在通用处申明:Private Const BITS_TO
- 第一节:WAP的潜能 这些日子,我们常听到WAP技术,一种手机上网的技术。从技术上讲,移动电话不可能和PC来竞争,移动电话的屏幕只能容下很少
- eval()函数可以将字符串型的list、tuple、dict等等转换为原有的数据类型即使用eval可以实现从元组,列表,字典型的字符串到元
- 本文实例讲述了Golang算法问题之整数拆分实现方法。分享给大家供大家参考,具体如下:一个整数总可以拆分为2的幂的和,例如:7=1+2+47
- DRF 框架,全称为 Django Rest Framework,是 Django 内置模块的扩展,用于创建标准化 RESTful API;
- 先发官方文档的地址:官方文档学习使用的书籍是Python网络数据采集(Ryan Mitchell著),大约是一些笔记的整理。Beautifu
- 在本文上两篇中,我们学习了脚本语言 VBScript 的变量、函数、过程和条件语句,本篇将继续给大家介绍 VBScipt 的循环语句,并对脚
- 任何一个交互过程的操作,对于用户来说都有学习成本,谁也不能保证所有人都可以准确无误地走完一个流程。交互设计师在设计时应该考虑适时地给用户相应
- vue使用swiper5官网使用方法 详情 :Swiper使用方法 - Swiper中文网 在vue中使用 首先 npm inst
- Python最大的优点之一就是语法简洁,好的代码就像伪代码一样,干净、整洁、一目了然。要写出 Pythonic(优雅的、地道的、整洁的)代码