Nestjs+Vue实现阿里云OSS服务端签名直传

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
.cn 域名,1个 12个月
简介: 阿里云 OSS 是常用的对象存储服务,借助其提供的 SDK,客户端(浏览器,小程序等)可以非常方便的将文件上传到 OSS 进行管理。

文件上传 OSS 方案

阿里云 OSS 是常用的对象存储服务,借助其提供的 SDK,客户端(浏览器,小程序等)可以非常方便的将文件上传到 OSS 进行管理,常用的方案有三种:

  • 客户端将文件上传到应用服务器,再由服务器将文件上传到 OSS
  • 客户端直接使用 SDK 上传文件到 OSS
  • 先由应用服务器向 OSS 请求生成一个临时签名,客户端再凭此签名将文件直传 OSS

这三种方案中,第一种对前端的影响最小,之前怎么上传文件,现在还怎么上传,上传 OSS 的任务交给服务端去做,但是这种方案会增加服务器的带宽压力,同时增加流量成本。

方案二也很简单,客户端只需要引入SDK,利用 SDK 提供的方法就能将文件直接上传到 OSS,文件上传成功后,会返回一些文件的信息,客户端再将这些信息提交给服务端保存即可。但是这种方案会将 AccessKeyId 和 AccessKeySecret 等私密数据暴露在前端,十分危险。

方案三最为理想,需要上传文件时,客户端先向由服务端发送请求,来获得一个临时签名,客户端使用这个临时签名就可以将文件直接上传到 OSS,既保证了安全,又减少了服务器的压力,节省了流量。

时序图

下面我们以 Nest + Vue 演示,如何在服务端生成签名,前端如何请求签名,并将文件上传到 OSS。

创建 OSS 存储桶

这一部分没什么好介绍的,自行访问阿里云 OSS 的控制台,开通 OSS 并且创建一个存储桶。

注意,在创建存储桶时,将读写权限设置为“公共读“,这样上传到 OSS 中的文件才能被用户访问到。如果是私有存储,需要指定用户才能访问到,可以选择”私有“,如果设置为”公共读写“,那么任意用户知道我们的存储桶地址后,都能上传文件,非常危险。

image-20230518010858342

存储桶的跨域设置

在我们自己的应用中通过 Ajax 请求向阿里云的 OSS 上传文件,必然存在跨域。OSS 的存储桶提供了 CORS 跨域的配置,可以轻松解决这个问题。

在存储桶中选择“数据安全” - “跨域设置”,创建一条跨域规则:

  • 开发阶段可以使用 * 允许任何域的请求,上线或者有真实域名的可以直接填写域名
  • 我们只使用 Post 方法来上传文件,允许 Post 即可
  • 允许 Headers 设置 * 即可

image-20230518013429883

创建 RAM 子用户

主账号的 AccessKey 具备访问账号下所用产品的权限,通常会为要使用的某种产品单独创建一个 RAM 子用户,这样可以大大提高安全性。

image-20230518011533510

访问 RAM 控制台,为 OSS 服务创建一个子用户,勾选 OpenAPI 选项,这样就能使用此 RAM 用户通过 API 的方法来操作 OSS。

image-20230518011848323

创建成功后,下载 CSV 文件进行保存,ID 和 Secret 只在创建时出现一次,后面就不能再查看,一定要保存好,防止泄露:

image-20230518012625365

为当前 RAM 用户添加权限:

image-20230518012754959

筛选出 OSS 相关的权限项,进行添加:

image-20230518012257185

到这里,我们就有了一个只能访问 OSS 的用户账号,他的 AccessKey 的 ID 和 Secret 都已经保存好了,下面就会用上。

服务端:生成签名

使用 Nest CLI 快速创建一个 Nest 应用,并设置路由和相应的方法,来处理客户端请求,生成临时签名,响应给客户端。

创建 Nest 项目:

$ nest new oss-server

创建一个 OSS 模块,服务和控制器,来处理 OSS 相关的逻辑:

$ nest g mo oss
$ nest g s oss --no-spec
$ nest g co oss --no-spec

OssModule

使用 Nest CLI 创建的模块,控制器等,会自动完成导入和注册,现在 OSS 模块如下:

import {
   
    Module } from '@nestjs/common';
import {
   
    OssService } from './oss.service';
import {
   
    OssController } from './oss.controller';

@Module({
   
   
  providers: [OssService],
  controllers: [OssController]
})
export class OssModule {
   
   }

并且也自动导入了根模块 AppModule 之中:

import {
   
    OssModule } from './oss/oss.module';

@Module({
   
   
  imports: [OssModule]
})
export class AppModule {
   
   }

OssController

控制器中定义一个处理 Get 请求的方法,处理客户端请求签名:

import {
   
    Controller, Get } from '@nestjs/common';
import {
   
    OssService } from './oss.service';
@Controller('oss')
export class OssController {
   
   
  constructor(private oss: OssService) {
   
   }

  @Get('signature')
  getOssSignature() {
   
   
    return this.oss.getSignature();
  }
}

当客户端发送 Get /oss/signature 请求时,就会来到此控制器中进行处理。

OssService

在服务中,调用阿里云 OSS 的 SDK,创建临时签名,并响应给客户端。

需要先安装一个依赖:

$ pnpm add ali-oss

再安装一个处理日期时间的依赖:

$ pnpm add dayjs
$ pnpm add -D @types/dayjs
import {
   
    Injectable } from '@nestjs/common';
import * as Client from 'ali-oss';
import * as dayjs from 'dayjs';

@Injectable()
export class OssService {
   
   
  async getSignature() {
   
   
    const config = {
   
   
      // 填写你自己的 AccessKey 
      accessKeyId: 'accessKeyId',
      accessKeySecret: 'accessKeySecret',
      // 存储桶名字
      bucket: 'kw-tuku1',
      // 文件存储路径
      dir: 'images/',
    };

    const client = new Client(config);

    const date = new Date();
    // 时长加 1 天,作为签名的有限期
    date.setDate(date.getDate() + 1);

    const policy = {
   
   
      // 设置签名的有效期,格式为Unix时间戳
      expiration: date.toISOString(),
      conditions: [
        ['content-length-range', 0, 10485760000], // 设置上传文件的大小限制
      ],
    };

    // 生成签名,策略等信息
    const formData = await client.calculatePostSignature(policy);

    // 生成 bucket 域名,客户端将向此地址发送请求
    const location = await client.getBucketLocation();
    const host = `http://${config.bucket}.${location.location}.aliyuncs.com`;

    // 响应给客户端的签名和策略等信息
    return {
   
   
      expire: dayjs().add(1, 'days').unix().toString(),
      policy: formData.policy,
      signature: formData.Signature,
      accessId: formData.OSSAccessKeyId,
      host,
      dir: config.dir,
    };
  }
}

注意这几个参数:

参数 描述
host 客户端上传文件时的地址,host不支持自定义域名
dir 文件存储路径 需要和文件名拼接成一个完整的上传路径
signature 对 Policy 签名后得到的字符串,作为临时签名使用
policy 用户表单上传的策略(Policy),Policy是一个经过 Base64 编码过的字符串
accessId 用户请求的 AccessKey ID
expire 由服务器端指定的 Policy 过期时间,格式为Unix时间戳

开启 CORS 跨域

Nest 程序运行在 3000 端口,前端 Vue 项目运行在 5173 端口,存在跨域。Nest 在入口文件中可以轻松设置 CORS 跨域:

import {
   
    NestFactory } from '@nestjs/core';
import {
   
    AppModule } from './app.module';

async function bootstrap() {
   
   
  const app = await NestFactory.create(AppModule, {
   
   
    cors: true,
  });
  await app.listen(3000);
}
bootstrap();

接口测试

测试一下接口:

image-20230518134838241

前端:请求签名,上传文件

使用 create-vue 脚手架快速创建一个项目,TS,JSX,Router 等通通不需要:

$ pnpm create vue oss-client

安装 axios 请求库:

$ pnpm add axios

在 App.vue 中写一个简单的上传文件的逻辑。客户端使用 Post 方法向 OSS 上传文件 ,要使用表单上传的方式,可以使用 FormData 来模拟 form 表单元素。

重点注意几个参数的作用的位置,必须携带的参数有 keyOSSAccessKeyIdpolicysignaturefile,在代码注释中也做了说明。

<script setup>
import { ref } from 'vue';
import axios from 'axios'

const fileRef = ref()

// 获取上传签名
const getOssData = async () => {
  // 这是上面 Nest 服务端生成签名信息的接口地址
  const res = await axios.get('http://localhost:3000/oss/signature')
  return res.data
}

// 生成文件名,作为 key 使用
const generateFileName = (ossData, file) => {
  const suffix = file.name.slice(file.name.lastIndexOf('.'));
  const filename = Date.now() + suffix;
  return ossData.dir + filename;
}

// 使用 OSS 上传图片
const handleUpload = async () => {
  const file = fileRef.value.files[0]

  const ossData = await getOssData()

  const key = generateFileName(ossData, file)

  const formdata = new FormData()

  // 注意参数的顺序,key 必须是第一位,表示OSS存储文件的路径
  formdata.append('key', key)
  formdata.append('OSSAccessKeyId', ossData.accessId)
  formdata.append('policy', ossData.policy)
  formdata.append('signature', ossData.signature)
  // 文件上传成功默认返回 204 状态码,可根据需要修改为 200
  formdata.append('success_action_status', '200')
  // file 必须放在最后一位
  formdata.append('file', file)

  const res = await axios.post(ossData.host, formdata);
  if(res.status === 200) {
    alert('文件上传成功')
  }
}

</script>

<template>
  <div>
    <input type="file" ref="fileRef">
    <br>
    <button @click="handleUpload">上传</button>
  </div>
</template>

选择一个文件,进行上传:

image-20230518133859425

来到 OSS 存储桶中,看看刚上传的文件:

image-20230518141555800

获取文件链接

通过签名直传的方式,OSS 并不会返回上传文件后的信息,包括文件名,大小,访问地址等。

先来看下 axios 发送 Post 请求将文件上传到 OSS 后响应的结果:

const handleUpload = async () => {
   
   
  // ......

  const res = await axios.post(ossData.host, formdata);
  console.log(res)
}

响应状态码是 203,表示服务器已成功处理了请求,但返回的信息可能来自另一个源。并且 res.data 是一个 XML 字符串:

image-20230520024956542

我们可以在前端,通过拼接 host(域名) + key(文件路径) 来获得:

<script setup>
// ...
const imgUrl = ref('')

const handleUpload = async () => {
  // ...

  const res = await axios.post(ossData.host, formdata);
  if(res.status === 200) {
    alert('文件上传成功')
    imgUrl.value = ossData.host + '/' + key
  }
}

<template>
  <div>
    <input type="file" ref="fileRef">
    <br>
    <button @click="handleUpload">上传</button>
    <br>
    <img :src="imgUrl" v-if="imgUrl" style="width: 300px;">
  </div>
</template>

再上传一张图片测试一下:

image-20230518170801213

总结

代码已上传,点击这里查看

回顾下我们做了哪些事:

  1. 创建存储桶,并设置读写权限和跨域策略

  2. 创建 RAM 子用户,添加 OSS 操作权限,提高安全性

  3. Nest 后端使用 ali-oss,生成临时签名等信息
  4. Vue 前端,选择文件后,请求服务器生成签名信息,并携带相关参数上传文件到 OSS 中

整个过程不算是很复杂,文档也有比较详细的说明,大家可以结合 ElementPlus 的 Upload 组件实际操作一下。

相关实践学习
借助OSS搭建在线教育视频课程分享网站
本教程介绍如何基于云服务器ECS和对象存储OSS,搭建一个在线教育视频课程分享网站。
目录
相关文章
|
5天前
|
安全 对象存储
OSS对象存储JavaV4签名
本文介绍了如何使用阿里云OSS-SDK生成V4版本的签名URL和Header签名。通过设置时间、访问密钥等参数,代码示例展示了如何创建带有V4签名的请求,适用于安全访问对象存储服务。相关文档链接提供了更多详细信息。
21 7
|
5月前
|
机器学习/深度学习 人工智能 专有云
人工智能平台PAI使用问题之怎么将DLC的数据写入到另一个阿里云主账号的OSS中
阿里云人工智能平台PAI是一个功能强大、易于使用的AI开发平台,旨在降低AI开发门槛,加速创新,助力企业和开发者高效构建、部署和管理人工智能应用。其中包含了一系列相互协同的产品与服务,共同构成一个完整的人工智能开发与应用生态系统。以下是对PAI产品使用合集的概述,涵盖数据处理、模型开发、训练加速、模型部署及管理等多个环节。
|
1月前
|
分布式计算 Java 开发工具
阿里云MaxCompute-XGBoost on Spark 极限梯度提升算法的分布式训练与模型持久化oss的实现与代码浅析
本文介绍了XGBoost在MaxCompute+OSS架构下模型持久化遇到的问题及其解决方案。首先简要介绍了XGBoost的特点和应用场景,随后详细描述了客户在将XGBoost on Spark任务从HDFS迁移到OSS时遇到的异常情况。通过分析异常堆栈和源代码,发现使用的`nativeBooster.saveModel`方法不支持OSS路径,而使用`write.overwrite().save`方法则能成功保存模型。最后提供了完整的Scala代码示例、Maven配置和提交命令,帮助用户顺利迁移模型存储路径。
|
4月前
|
存储 机器学习/深度学习 弹性计算
阿里云EMR数据湖文件系统问题之OSS-HDFS全托管服务的问题如何解决
阿里云EMR数据湖文件系统问题之OSS-HDFS全托管服务的问题如何解决
|
5月前
|
消息中间件 分布式计算 DataWorks
DataWorks产品使用合集之如何使用Python和阿里云SDK读取OSS中的文件
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
5月前
|
存储 运维 安全
阿里云OSS的优势
【7月更文挑战第19天】阿里云OSS的优势
235 2
|
5月前
|
存储 API 开发工具
阿里云OSS
【7月更文挑战第19天】阿里云OSS
203 1
|
5月前
|
存储 弹性计算 对象存储
预留空间是什么?阿里云OSS对象存储预留空间说明
阿里云OSS预留空间是预付费存储产品,提供折扣价以锁定特定容量,适用于抵扣有地域属性的Bucket标准存储费用及ECS快照费。通过购买预留空间,如500GB通用预留+100GB标准-本地冗余存储包,用户可优化成本。
225 4
|
5月前
|
人工智能 对象存储
【阿里云AI助理】自家产品提供错误答案。阿里云OSS 资源包类型: 下行流量 地域: 中国内地通用 下行流量包规格: 300 GB 套餐: 下行流量包(中国内地) ,包1年。那么这个是每月300GB,1年是3600GB的流量;还是1年只有300GB的流量?
自家产品提供错误答案。阿里云OSS 资源包类型: 下行流量 地域: 中国内地通用 下行流量包规格: 300 GB 套餐: 下行流量包(中国内地) ,包1年。那么这个是每月300GB,1年是3600GB的流量;还是1年只有300GB的流量?
137 1
|
6月前
|
监控 Serverless 持续交付
阿里云云效产品使用问题之如何让流水线支持构建 flutter web 应用到 OSS
云效作为一款全面覆盖研发全生命周期管理的云端效能平台,致力于帮助企业实现高效协同、敏捷研发和持续交付。本合集收集整理了用户在使用云效过程中遇到的常见问题,问题涉及项目创建与管理、需求规划与迭代、代码托管与版本控制、自动化测试、持续集成与发布等方面。