MinIO 是什么?
MinIO 是一个开源的对象存储服务器,用于存储和检索大规模数据。它兼容 Amazon S3 API,因此可以与现有的 S3 工具和客户端库一起使用,无需修改代码。MinIO 的设计目标是简单、高性能和可扩展,可以在各种环境中部署,包括本地部署、私有云和公有云。如果你使用过阿里云的 OSS 的话,这个跟 OSS 基本一样。
MinIO 的特点
- 对象存储:MinIO 以对象的形式存储数据,每个对象由数据、元数据和唯一的标识符组成。这使得它非常适合存储大规模的非结构化数据,如图片、视频、日志等。
- 高性能:MinIO 被设计成具有出色的性能。它使用了一系列优化技术,包括并行化、多线程处理和异步 I/O,以实现快速的数据上传和下载速度。
- 可扩展性:MinIO 支持水平扩展,可以通过添加更多的服务器节点来增加存储容量和吞吐量。它使用了分布式架构和一致性哈希算法来实现数据的分布和负载均衡。
- 安全性:MinIO 提供了多种安全功能,包括数据加密、访问控制和身份验证。它支持 SSL/TLS 加密传输,并可以与 LDAP、OAuth 和 AWS IAM 等身份验证系统集成。
如何搭建一个 MinIO 服务
MinIO 的文档中提供了不同环境的安装方式。
Linux、Windows、MaxOS 都是下载安装包,直接安装。本文则是以 Docker 为例,只需要下载一个 MinIO 的镜像即可,然后启动容器即可。
在 Docker 中搭建 MinIO
首先,我们需要安装一下 Docker,直接去官网下载,然后安装就好了。
启动 Docker,在搜索栏中搜索 MinIO
,然后直接点击 pull
,拉取 MinIO 镜像
或者打开终端,在终端中输入 docker pull bitnami/minio
,这一步需要科学上网,而且 docker 的镜像源也不是很稳定,有时候可能会出现拉不下来镜像的问题。
镜像拉取完之后,在 docker 的 images 列表中,会有 bitnami/minio
这一项,点击 Actions 中的运行按钮,会弹出一个弹框。
容器名随便写就可以,端口号一般默认就行,这个是在访问 MinIO 的时候用到的。Volumes 是一个挂载卷,将本地的目录映射到 docker 容器中。环境变量只需要设置用户名和密码即可,这里要注意用户名的长度要在5位以上,用户密码的长度要在8位以上。填完点击运行按钮就,这时候就可以在 docker 的 Containers 中看到多了一条。
到这一步的时候,我们的 MinIO 服务就已经启动成功了,然后直接点击端口号就能访问。
这里要输入的用户名和密码就是刚刚在环境变量中设置的用户名和密码。登录进来之后发现啥也木有。
我们创建一个 bucket。
创建之后,在 Object Browser 中就多了一个bucket,我们上传的资源也都可以在这里查到。
进入这个 bucket 中,点击上传,可以上传文件和目录,我们随便上传一个。
点击分享,我们就可以看到文件的url,通过这个url,我们就可以访问到对应的文件。
但是你会发现,这个链接有很长一串。后边的部分其实是链接的访问权限以及有效期等信息,去掉这一部分之后,就访问不到该资源。
如果不想要后边这一串,需要在 MinIO 中设置一下 bucket 的权限。
这个设置完之后,不带后边那一串,也可以访问对应的资源。
到这里 MinIO 服务相关的内容就介绍的差不多了。
前端资源直传 MinIO
后端部分
这里先讲后端部分,技术栈用的是 Nest.js。
先使用 nest new uplad-test
创建一个 nest 项目。
要使用 MinIO,需要先安装一下 minio
,这个依赖。
然后在 AppService
中写相关的业务逻辑。
typescript
import * as Minio from 'minio'
@Injectable()
export class AppService {
private minioClient: Minio.Client
constructor() {
this.minioClient = new Minio.Client({
endPoint: localhost,
port: 9000,
useSSL: false,
accessKey: F8QzroGK9KGvA91mWJ49,
secretKey: M5weuarlqnDihjdtYEaVMyYRaUjVsDAZCvoS5CNE,
})
}
async getUploadSecretKey(fileType: string): Promise<UploadSecreteDto> {
const bucketName = fileType
// 获取所有的bucket,判断当前的bucket是否存在,如果不存在就创建,并且设置权限
const buckets = await this.minioClient.listBuckets()
const findBucket = buckets.find((bucket: Minio.BucketItemFromList) => bucket.name === bucketName)
if (!findBucket) {
await this.minioClient.makeBucket(bucketName, 'us-east-1')
const bucketPolicy = {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: '*', // 允许任何人访问
Action: ['s3:GetBucketLocation', 's3:ListBucket'],
Resource: `arn:aws:s3:::${bucketName}`
},
{
Effect: 'Allow',
Principal: '*', // 允许任何人访问
Action: 's3:GetObject',
Resource: `arn:aws:s3:::${bucketName}/*`
}
]
}
const bucketPolicyString = JSON.stringify(bucketPolicy)
await this.minioClient.setBucketPolicy(bucketName, bucketPolicyString)
}
// 上传的位置及文件名
const fileKey = `uploads/${Date.now()}-${Math.random().toString(36).substring(2)}.${fileType}`
const expiresIn = 60 * 60
// 生成一个临时的上传url,有效期为1小时
const presignedUrl = await this.minioClient.presignedPutObject(bucketName, fileKey, expiresIn)
return {
bucketName,
fileKey,
presignedUrl
}
}
}
实例化 minioClient 的时候需要传入 accessKey
和 secretKey
,这两个key,需要在 MinIO 中创建。
注意在创建之前要先将这两个 key 先保存一下,创建之后就不会再显示了。
我这里是将不同类型的文件放入不同的 bucket 中,所以在每次在上传之前都要先检查一下对应的 bucket 是否存在,如果不存在就创建,并且设置对应的权限,然后根据要上传的资源类型以及 bucket 名称生成一个临时上传链接。
在 AppController
中写入如下代码:
less
@Controller('')
export class AppController {
constructor(
private readonly appService: AppService
) { }
@Get('getSignature/:fileType')
async getSignature(@Param('fileType') fileType: string) {
return await this.appService.getAppSecretKey(fileType)
}
}
到这里后端的服务基本已经开发完成。
前端部分
前端使用 Vue3
、Element-Plus
、axios
,前端项目的创建流程就不介绍了,直接上代码。
xml
<template>
<el-upload
class="mt-20 ml-20"
:limit="1"
show-file-list
:http-request="toUpload"
accept="image/*"
>
<el-button type="primary">
upload
</el-button>
</el-upload>
<img :src="url" alt="" class="ml-20 mt-20">
</div>
</template>
<script setup lang="ts">
import { UploadRequestOptions } from 'element-plus'
import { ref } from 'vue'
import axios from 'axios'
const url = ref('')
async function toUpload(action: UploadRequestOptions) {
const file = action.file
const fileType = file.name.split('.')[file.name.split('.').length - 1]
const res = await axios.get(`http://localhost:3006/api/getSignature/${fileType}`)
await axios.put(res.data.presignedUrl, action.file, {
headers: {
'Content-Type': file.type
}
})
url.value = `http://localhost:9000/${res.data.bucketName}/${res.data.fileKey}`
}
</script>
其中nest服务的端口号为 3006
,http://localhost:9000/
为 MinIO 的资源服务地址。
我们先通过 getSignature
获取到 MinIO 的临时上传地址,然后使用这个临时上传地址上传对应的资源文件。
图片能够正常上传并可以通过链接正常预览到,MinIO 的 bucket 中,也存着我们刚上传的文件。
前端直传的好处
我们当然也可以让后端写一个上传的接口,把资源先上传给后端,后端拿到资源之后再根据需要放到 OSS、MinIO 或者其他什么位置,但是这么做在上传的时候会占用服务器的带宽,造成不必要的资源浪费,前端直传就可以减小服务器的压力。