夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Node基础

  • 《MySQL》学习笔记

  • Midway

  • Nest

    • 开篇词
    • 学习理由
    • nest概念扫盲
    • 快速掌握 nestcli
    • 5种http数据传输方式
    • IoC 解决了什么痛点问题?
    • 如何调试 Nest 项目
    • Provider注入对象
    • 全局模块和生命周期
    • AOP 架构有什么好处?
    • 一网打尽 Nest 全部装饰器
    • Nest如何自定义装饰器
    • Metadata和Reflector
    • ExecutionContext切换上下文
    • Module和Provider的循环依赖处理
    • 如何创建动态模块
    • Nest和Express,fastify
    • Nest的Middleware
    • RxJS和Interceptor
    • 内置Pipe和自定义Pipe
    • ValidationPipe验证post请求参数
    • 如何自定义 Exception Filter
    • 图解串一串 Nest 核心概念
    • 接口如何实现多版本共存
    • Express如何使用multer实现文件上传
    • Nest使用multer实现文件上传
    • 图书管理系统
    • 大文件分片上传
    • 最完美的 OSS 上传方案
      • OSS 服务
      • 其他云储存方式
      • 使用阿里云 OSS 服务
        • 创建 Bucket
        • CDN 加速
        • 上传文件
        • 创建子应用管理
        • 安全策略
        • 大文件切片
        • 文件上传链路
      • 示例:网页上传文件中至 OSS
      • 总结
    • Nest里如何打印日志
    • 为什么Node里要用Winston打印日志
    • Nest 集成日志框架 Winston
    • 通过Desktop学Docker也太简单了
    • 你的第一个 Dockerfile
  • 其他

  • 服务端
  • Nest
神说要有光
2025-03-10
目录

最完美的 OSS 上传方案

文件上传是常见需求,一般我们不会把文件直接上传到应用服务器,因为单台服务器存储空间是有限的,不好扩展。

我们会用单独的 OSS (Object Storage Service)对象存储服务来上传下载文件。

比如一般会买阿里云的 OSS 服务。

# OSS 服务

我们本地文件存储是目录-文件的组织方式:

而 OSS 服务的存储结构是这样的:

一个桶里放一些文件。

阿里云 OSS 的控制台也提到了对象存储没有目录层级结构:

但下面明明是支持目录的呀:

这其实只是模拟实现的。

Object 会存储 id、文件内容、元数据三部分信息:

阿里云 OSS 只是用元信息部分模拟实现了目录。

就像打了个 tag 一样,并不是说文件存储在这个 tag 下,只是你可以用这个 tag 来检索文件。

# 其他云储存方式

除了对象存储 OSS,阿里云也提供了文件存储和块存储的方式:

块存储就是把整块磁盘给你用,你需要自己格式化,存储容量有限。

文件存储就是有目录层次结构,你可以上传下载文件,存储容量有限。

对象存储就是 key-value 存储,分布式的方式实现的,存储容量无限。

这些简单了解就行,绝大多数情况下,我们都是用 OSS 对象存储。

# 使用阿里云 OSS 服务

我们买个阿里云的 OSS 服务 (opens new window)来试试看:

我买了 40G 的 OSS 国内通用资源包,花了 5 块钱。

# 创建 Bucket

然后我们创建个 Bucket(桶):

在北京创建了一个 Bucket,文件就会存储在那里的服务器上。

设置公共读,也就是这些文件大家都可以直接访问。

不然私有的方式,你访问每个文件都要带上一些身份信息:

有的同学说,不是静态文件要在全国各地都能访问到么?存在北京的服务器会不会访问速度慢?

这是 CDN 的活:

接入 CDN 后,访问该域名会走到云服务的 DNS,然后返回一台最近的缓存服务器的地址,这台服务器会从源站拿文件来缓存,之后就不再访问源站。

这里的源站就可以是 OSS 服务。

创建 Bucket 之后,我们上传个文件试试:

上传完之后在文件列表就可以看到这个文件了:

点开可以看到文件详情,用这个 URL 就可以访问:

# CDN 加速

当然,生产环境下我们不会直接用 OSS 的 URL 访问,而是会开启 CDN,用网站域名访问,最终回源到 OSS 服务:

# 上传文件

在控制台里上传很简单,那如果想在代码里上传呢?

官方文档里有示例代码,我们试试看:

mkdir oss-test
cd oss-test
npm init -y
1
2
3

安装用到的包:

npm install ali-oss
1

写下代码:

const OSS = require('ali-oss')

const client = new OSS({
    region: 'oss-cn-beijing',
    bucket: 'guang-333',
    accessKeyId: '',
    accessKeySecret: '',
});

async function put () {
  try {
    const result = await client.put('cat.png', './mao.png');
    console.log(result);
  } catch (e) {
    console.log(e);
  }
}

put();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

region 在概览里可以看到:

这里的 accessKeyId 和 acessKeySecret 是什么呢?

本来我们身份认证都是通过用户名密码:

但这样不够安全,所以我们创建了 accessKey 用来代表身份,用它来做身份认证,就算泄漏了,也不影响别的:

我们创建个 accesKey:

创建完成后,拿到 accesKeyId 和 accessKeySecret 后运行代码:

这里的 mao.png 是这样的:

在控制台可以看到上传成功了:

这就是 OSS 用 api 上传文件的用法。

只是我们刚刚用的 accessKey 不够安全。

打开 accessKey 管理页面的时候就提示了:

让我们不要直接用 accessKey,而是创建一个子用户再创建 accessKey。

# 创建子应用管理

那我们就创建个子用户:

然后用这个 accessKey 的 id 和 secret 就好了:

但你直接换上它还不行:

会提示你 403,没有权限。

需要你授权下:

新增一个授权:

把 OSS 的管理和读取权限给这个子用户:

然后再试下:

这时候就上传成功了。

# 安全策略

回过头来看下,不得不说阿里云在安全这一块设计的就很巧妙。

如果我们直接用用户名密码验证呢:

那万一泄漏了不就完蛋了么?

但是如果创建个 accessKey 用它来做身份认证:

就算泄漏了我也可以禁用啊:

再进一步,直接用这个 accessKey 它是有所有权限的。

我们先创建个 RAM 子用户,再分配给他某些权限,这样就算泄漏了,是不是能做的事情就更少了?

当然就更安全。

所以说,阿里云这套 accessKey 和 RAM 子用户的身份认证方式,还是很不错的。

# 大文件切片

再说回 OSS,一般的文件直接上传就行,涉及到大文件就要分片上传了。

分片上传实现原理是前端常见面试题了,大家都能答上来,上节我们刚实现过。

就是把文件用 slice 方法分成一个个小的片,然后全部上传完之后请求一个接口合并分片。

阿里云的大文件分片上传 (opens new window)也是这样实现的:

具体怎么用直接看文档就好了,这里我们就不试了:

# 文件上传链路

有了 OSS 服务之后,我们上传文件还需要经过应用服务器么?

可以经过也可以不经过。

如果经过应用服务器,那就要客户端上传文件之后,我们在服务里接受文件,上传 OSS:

这样当然是可以的,还能保护 accessKey 不被人窃取。

只是会浪费应用服务器的流量。

那如果不经过呢?

在客户端用 accessKey 把文件传到 OSS,之后把 URL 传给应用服务器就好了。

这样减少了应用服务器的流量消耗,但是增加了 accessKey 暴露的风险。

各有各的坏处。

那有没有啥两全其美的办法呢?

有。

阿里云的文档 (opens new window)里也提到了这个问题。

它给出的解决方案就是生成一个临时的签名来用。

代码是这样的:

const OSS = require('ali-oss')

async function main() {

    const config = {
        region: 'oss-cn-beijing',
        bucket: 'guang-333',
        accessKeyId: 'LTAI5tDemEBPwQkTx65jZCdy',
        accessKeySecret: 'I0vHYOoqIC78lH7A5c5XB1H7Pev7bp',
    }

    const client = new OSS(config);
    
    const date = new Date();
    
    date.setDate(date.getDate() + 1);
    
    const res = client.calculatePostSignature({
        expiration: date.toISOString(),
        conditions: [
            ["content-length-range", 0, 1048576000], //设置上传文件的大小限制。      
        ]
    });
    
    console.log(res);
    
    const location = await client.getBucketLocation();
    
    const host = `http://${config.bucket}.${location.location}.aliyuncs.com`;

    console.log(host);
}

main();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

上传 OSS 的地址,用的临时 signature 和 policy 都有了:

这些代码不用记,文档里都有:

这样就能在网页里用这些来上传文件到 OSS 了:

# 示例:网页上传文件中至 OSS

创建个 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/axios@1.6.5/dist/axios.min.js"></script>
</head>
<body>
    <input id="fileInput" type="file"/>
    
    <script>
        const fileInput = document.getElementById('fileInput');

        async function getOSSInfo() {
            await '请求应用服务器拿到临时凭证';
            return {
                OSSAccessKeyId: 'LTAI5tDemEBPwQkTx65jZCdy',
                Signature: 'NfXgq/qLIR2/v87j/XC9sjrASOA=',
                policy: 'eyJleHBpcmF0aW9uIjoiMjAyNC0wMS0yMFQwMzoyNjowOC4xMDZaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF1dfQ==',
                host: 'http://guang-333.oss-cn-beijing.aliyuncs.com'
            }
        }

        fileInput.onchange = async () => {
            const file = fileInput.files[0];

            const ossInfo = await getOSSInfo();


            const formdata = new FormData()
 
            formdata.append('key', file.name);
            formdata.append('OSSAccessKeyId', ossInfo.OSSAccessKeyId)
            formdata.append('policy', ossInfo.policy)
            formdata.append('signature', ossInfo.Signature)
            formdata.append('success_action_status', '200')
            formdata.append('file', file)

            const res = await axios.post(ossInfo.host, formdata);
            if(res.status === 200) {
                
                const img = document.createElement('img');
                img.src = ossInfo.host + '/' + file.name
                document.body.append(img);

                alert('上传成功');
            }
        }
    </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

这里 getOSSInfo 应该是请求服务端的接口,拿到刚才我们控制台输出的那些东西。

这里就简化下,直接写死在代码里了。

引入 axios,用这些信息来上传文件。

跑个静态服务器:

npx http-server .
1

这时候你上传文件的时候会提示跨域错误:

我们在控制台开启下跨域:

然后再试下:

上传成功了!

控制台文件列表也可以看到这个文件:

这就是完美的 OSS 上传方案。

服务端用 RAM 子用户的 accessKey 来生成临时签名,然后返回给客户端,客户端用这个来直传文件到 OSS。

因为临时的签名过期时间很短,我们设置的是一天,所以暴露的风险也不大。

这样服务端就根本没有接受文件的压力,只要等客户端上传完之后,带上 URL 就好了。

案例代码上传了小册仓库 (opens new window)

# 总结

上传文件一般不会直接存在服务器目录下,这样不好扩展,一般我们会用阿里云的 OSS,它会自己做弹性扩展,所以存储空间是无限的。

OSS 对象存储是在一个 bucket 桶下,存放多个文件。

它是用 key-value 存储的,没有目录的概念,阿里云 OSS 的目录只是用元信息来模拟实现的。

我们在测试了在控制台的文件上传,也测试过了 node 里用 ali-oss 包来上传、在网页里直传 OSS 这三种上传方式。

不管在哪里上传,都需要 acessKeyId 和 acessKeySecret。

这个是阿里云的安全策略,因为直接用用户名密码,一旦泄漏就很麻烦,而 acessKey 泄漏了也可以禁用。而且建议用 RAM 子用户的方式生成 accessKey,这样可以最小化权限,进一步减少泄漏的风险。

客户端直传 OSS 的方式不需要消耗服务器的资源,但是会有泄漏 acessKey 的风险,所以一般都是用服务端生成临时的签名等信息,然后用这些信息来上传。

这种方案就是最完美的 OSS 上传方案了。

掌握了这些,就完全足够应对工作中的 OSS 使用了。

编辑 (opens new window)
上次更新: 2025/5/14 16:47:16
大文件分片上传
Nest里如何打印日志

← 大文件分片上传 Nest里如何打印日志→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式