深入掌握 OSS:最完美的 OSS 上传方案!

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

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

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

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

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

一个桶里放一些文件。

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

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

这其实只是模拟实现的。

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

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

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

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

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

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

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

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

我们买个阿里云的 OSS 服务来试试看:

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

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

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

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

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

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

这是 CDN 的活:

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

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

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

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

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

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

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

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

bash 复制代码
mkdir oss-test
cd oss-test
npm init -y

安装用到的包:

复制代码
npm install ali-oss

写下代码:

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

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 方法分成一个个小的片,然后全部上传完之后请求一个接口合并分片。

阿里云的大文件分片上传也是这样实现的:

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

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

可以经过也可以不经过。

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

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

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

那如果不经过呢?

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

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

各有各的坏处。

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

有。

阿里云的文档里也提到了这个问题。

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

代码是这样的:

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

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

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

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

创建个 index.html

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>

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

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

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

跑个静态服务器:

vbscript 复制代码
npx http-server .

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

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

然后再试下:

上传成功了!

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

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

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

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

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

案例代码上传了 github:github.com/QuarkGluonP...

总结

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

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

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

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

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

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

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

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

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

相关推荐
Jerry2 分钟前
Jetpack Compose 中的状态
前端
dae bal1 小时前
关于RSA和AES加密
前端·vue.js
柳杉1 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog1 小时前
低端设备加载webp ANR
前端·算法
LKAI.1 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi
刺客-Andy2 小时前
React 第七十节 Router中matchRoutes的使用详解及注意事项
前端·javascript·react.js
前端工作日常2 小时前
我对eslint的进一步学习
前端·eslint
禁止摆烂_才浅3 小时前
VsCode 概览尺、装订线、代码块高亮设置
前端·visual studio code
程序员猫哥3 小时前
vue跳转页面的几种方法(推荐)
前端
代码老y4 小时前
十年回望:Vue 与 React 的设计哲学、演进轨迹与生态博弈
前端·vue.js·react.js