写点什么

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

作者:昆吾kw
  • 2023-06-17
    天津
  • 本文字数:4190 字

    阅读完需:约 14 分钟

文件上传 OSS 方案

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


  • 客户端将文件上传到应用服务器,再由服务器将文件上传到 OSS

  • 客户端直接使用 SDK 上传文件到 OSS

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


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


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


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



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

创建 OSS 存储桶

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


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


存储桶的跨域设置

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


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


  • 开发阶段可以使用 * 允许任何域的请求,上线或者有真实域名的可以直接填写域名

  • 我们只使用 Post 方法来上传文件,允许 Post 即可

  • 允许 Headers 设置 * 即可


创建 RAM 子用户

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



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



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



为当前 RAM 用户添加权限:



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



到这里,我们就有了一个只能访问 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, }; }}
复制代码


注意这几个参数:


开启 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();
复制代码

接口测试

测试一下接口:


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

使用 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>
复制代码


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



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


获取文件链接

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


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


const handleUpload = async () => {  // ......    const res = await axios.post(ossData.host, formdata);  console.log(res)}
复制代码


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



我们可以在前端,通过拼接 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>
复制代码


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


总结

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


回顾下我们做了哪些事:


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

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

  3. Nest 后端使用 ali-oss,生成临时签名等信息

  4. Vue 前端,选择文件后,请求服务器生成签名信息,并携带相关参数上传文件到 OSS 中


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

发布于: 刚刚阅读数: 2
用户头像

昆吾kw

关注

还未添加个人签名 2020-10-21 加入

还未添加个人简介

评论

发布
暂无评论
Nestjs+Vue实现阿里云OSS服务端签名直传_Vue_昆吾kw_InfoQ写作社区