242 lines
11 KiB
Markdown
242 lines
11 KiB
Markdown
# MinIO 入门:本地对象存储的部署与使用
|
||
|
||
> 面向 Atlas Seller Kit 的学习文档。本文解释 MinIO 是什么、本项目为什么在本地开发使用它、如何用 Docker 部署,以及如何按本项目的 `object_key` 约定访问文件。`object_key` 与 URL 的概念区分见[对象存储入门](./object-storage-object-key.md)。
|
||
|
||
## 1. MinIO 是什么
|
||
|
||
MinIO 是一个开源的对象存储服务器,完整实现了 **Amazon S3 API**。它可以理解为"你自己跑的一个 S3":
|
||
|
||
| 项目 | MinIO | 云端对象存储(如七牛 Kodo) |
|
||
|---|---|---|
|
||
| 形态 | 单个容器/二进制,跑在自己机器或服务器上 | 托管服务,开箱即用 |
|
||
| 接口 | S3 兼容 API | S3 兼容 API(基础能力子集) |
|
||
| 数据位置 | 本机 Docker 卷 | 云端 |
|
||
| 运维 | 自己负责(本地开发无所谓) | 云厂商负责 |
|
||
| 费用 | 免费 | 按存储/流量计费 |
|
||
|
||
它只有三个核心概念(与所有 S3 兼容存储通用):
|
||
|
||
- **Bucket(存储桶)**:文件的顶层容器,例如 `atlas-dev-assets`;
|
||
- **Object Key(对象键)**:桶内文件的唯一键,例如 `workspaces/ws_123/products/p_456/original/asset_789.jpg`;
|
||
- **预签名 URL**:带签名和过期时间的临时访问地址,用于把私有文件的读写权限短暂借给浏览器。
|
||
|
||
## 2. 本项目为什么用 MinIO
|
||
|
||
Atlas 的技术选型是:**本地开发用 MinIO,staging/production 用七牛 Kodo**(见[技术栈与部署](../architecture/technology-and-deployment.md)第 4 节)。选择这个组合的原因:
|
||
|
||
1. **代码同构**:两者都讲 S3 方言。应用代码只写一套 S3 客户端逻辑,本地和生产只差 endpoint、凭证、桶名这几个配置项,不差代码。
|
||
2. **行为可验证**:私有桶权限、预签名 URL 过期、ETag/Content-Type、MIME 校验这些行为,在本地 MinIO 上验证过的逻辑,到 Kodo 上基本原样成立。如果本地直接读写磁盘目录,这些逻辑完全测不到。
|
||
3. **可随时重建**:MinIO 数据在 Docker 卷里,删掉卷重启就是全新的存储,适合配合版本化 seed 数据做开发。
|
||
4. **成本为零**:开发阶段不产生云存储费用,也不会误用生产 Bucket。
|
||
|
||
注意边界:MinIO 只进**本地开发环境**。首期不在普通单台云服务器上自建生产 MinIO——磁盘冗余、备份、故障恢复、TLS、监控都要自己做,"部署了 MinIO"不等于获得托管对象存储的可靠性。
|
||
|
||
## 3. 用 Docker 部署本地 MinIO
|
||
|
||
### 3.1 单命令快速启动
|
||
|
||
只想临时体验时:
|
||
|
||
```bash
|
||
docker run -d --name atlas-minio \
|
||
-p 9000:9000 -p 9001:9001 \
|
||
-v atlas-minio-data:/data \
|
||
minio/minio server /data --console-address ":9001"
|
||
```
|
||
|
||
两个端口各有用途:
|
||
|
||
| 端口 | 用途 |
|
||
|---|---|
|
||
| `9000` | **S3 API 端口**。应用代码(SDK、`mc` 客户端)访问这个。 |
|
||
| `9001` | **Web Console 端口**。浏览器打开 `http://localhost:9001` 管理文件。 |
|
||
|
||
### 3.2 接入项目的 Docker Compose
|
||
|
||
正式开发应把它写进项目根目录的 `docker-compose.yml`,与 `postgres`、`redis` 并列:
|
||
|
||
```yaml
|
||
services:
|
||
minio:
|
||
image: minio/minio:latest # 团队建议固定到具体 RELEASE tag,避免本地环境漂移
|
||
command: server /data --console-address ":9001"
|
||
ports:
|
||
- "9000:9000" # S3 API
|
||
- "9001:9001" # Web Console
|
||
environment:
|
||
MINIO_ROOT_USER: atlas-dev
|
||
MINIO_ROOT_PASSWORD: atlas-dev-secret # 仅本地开发用,不得用于任何共享环境
|
||
volumes:
|
||
- minio_data:/data
|
||
healthcheck:
|
||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||
interval: 10s
|
||
timeout: 3s
|
||
retries: 5
|
||
|
||
volumes:
|
||
minio_data:
|
||
```
|
||
|
||
说明:
|
||
|
||
- `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` 是这个实例的 root 账号,仅本地使用;真正的云端密钥永远走环境变量或密钥管理,不提交 Git。
|
||
- `-v minio_data:/data` 把数据落在命名卷里;`docker compose down` 不丢数据,`docker compose down -v` 会连数据一起清掉(重置开发环境的常用手段)。
|
||
- 建议把这两个值放进 `.env`(该文件已在 `.gitignore` 中),compose 里用 `MINIO_ROOT_USER: ${MINIO_ROOT_USER}` 引用。
|
||
|
||
### 3.3 应用读取的配置约定
|
||
|
||
NestJS API/Worker 通过环境变量拿到连接信息,本地 `.env` 示例:
|
||
|
||
```bash
|
||
# 对象存储(本地 MinIO / 生产 Kodo 只换这一组值)
|
||
OBJECT_STORAGE_ENDPOINT=http://localhost:9000
|
||
OBJECT_STORAGE_BUCKET=atlas-dev-assets
|
||
OBJECT_STORAGE_FORCE_PATH_STYLE=true
|
||
# 生产或共享环境必须提供专用最小权限凭证:
|
||
OBJECT_STORAGE_ACCESS_KEY=atlas-dev
|
||
OBJECT_STORAGE_SECRET_KEY=atlas-dev-secret
|
||
# 本地也可仅设置 MINIO_ROOT_USER / MINIO_ROOT_PASSWORD,由 API 作为兼容回退读取。
|
||
```
|
||
|
||
## 4. 创建 Bucket
|
||
|
||
MinIO 的桶默认是**私有**的,符合本项目"Bucket 默认私有"的规则。两种创建方式任选。
|
||
|
||
### 4.1 Web Console(直观)
|
||
|
||
1. 打开 `http://localhost:9001`,用 root 账号登录;
|
||
2. 左侧 **Object Browser → Create Bucket**;
|
||
3. 名称填 `atlas-dev-assets`,保持默认(不勾选公开访问),确认创建。
|
||
|
||
### 4.2 mc 命令行(可脚本化)
|
||
|
||
`mc` 是 MinIO 官方 CLI,也可以用来管理任何 S3 兼容存储。用一次性容器执行,不必本机安装:
|
||
|
||
```bash
|
||
# 1. 给本地实例起个别名
|
||
docker run --rm --network host minio/mc alias set local http://localhost:9000 atlas-dev atlas-dev-secret
|
||
|
||
# 2. 创建桶
|
||
docker run --rm --network host minio/mc mb local/atlas-dev-assets
|
||
|
||
# 3. 确认桶是私有的(输出应为 none,即不允许匿名访问)
|
||
docker run --rm --network host minio/mc anonymous get local/atlas-dev-assets
|
||
```
|
||
|
||
首次启动时"确保桶存在"也可以由 API 启动逻辑完成(本地环境 `head bucket` 失败就 `create bucket`),这样新同事 `docker compose up` 后无需手工步骤。
|
||
|
||
## 5. 日常使用
|
||
|
||
### 5.1 Console 里的常见操作
|
||
|
||
| 操作 | 位置 |
|
||
|---|---|
|
||
| 浏览/搜索对象 | Object Browser,按前缀逐级进入 |
|
||
| 手动上传/下载 | 进入桶后右上角 Upload / 对象行 Download |
|
||
| 查看对象元数据 | 对象行右侧菜单 → Object info(大小、ETag、Content-Type) |
|
||
| 生成临时分享链接 | 对象行菜单 → Share(本质是预签名 GET URL,可设过期时间) |
|
||
| 管理访问密钥 | Identity → Access Keys(可给本地工具发独立密钥,不共用 root) |
|
||
|
||
### 5.2 mc 常用命令速查
|
||
|
||
```bash
|
||
alias set local http://localhost:9000 USER PASS # 配置连接别名
|
||
mb local/atlas-dev-assets # 创建桶
|
||
ls local/atlas-dev-assets/workspaces/ # 列出前缀
|
||
cp ./test.jpg local/atlas-dev-assets/tmp/test.jpg # 上传
|
||
cp local/atlas-dev-assets/tmp/test.jpg ./ # 下载
|
||
stat local/atlas-dev-assets/tmp/test.jpg # 查看元数据
|
||
rm local/atlas-dev-assets/tmp/test.jpg # 删除对象
|
||
du local/atlas-dev-assets # 桶占用
|
||
```
|
||
|
||
### 5.3 应用代码访问(NestJS / Node)
|
||
|
||
本项目统一使用 AWS S3 SDK v3 指向自定义 endpoint,本地 MinIO 与生产 Kodo 走同一条代码路径:
|
||
|
||
```ts
|
||
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
|
||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||
|
||
const s3 = new S3Client({
|
||
endpoint: process.env.OBJECT_STORAGE_ENDPOINT!, // http://localhost:9000
|
||
region: 'us-east-1',
|
||
credentials: {
|
||
accessKeyId: process.env.OBJECT_STORAGE_ACCESS_KEY!,
|
||
secretAccessKey: process.env.OBJECT_STORAGE_SECRET_KEY!,
|
||
},
|
||
forcePathStyle: process.env.OBJECT_STORAGE_FORCE_PATH_STYLE === 'true',
|
||
});
|
||
|
||
// 预签名上传 URL:前端拿到后直传,浏览器不保存永久密钥
|
||
export async function presignUpload(objectKey: string, contentType: string) {
|
||
const cmd = new PutObjectCommand({ Bucket: process.env.OBJECT_STORAGE_BUCKET, Key: objectKey, ContentType: contentType });
|
||
return getSignedUrl(s3, cmd, { expiresIn: 600 }); // 10 分钟
|
||
}
|
||
|
||
// 预签名下载 URL:预览/下载私有文件
|
||
export async function presignDownload(objectKey: string) {
|
||
const cmd = new GetObjectCommand({ Bucket: process.env.OBJECT_STORAGE_BUCKET, Key: objectKey });
|
||
return getSignedUrl(s3, cmd, { expiresIn: 900 }); // 15 分钟
|
||
}
|
||
```
|
||
|
||
服务端直接读写(如 Worker 转存采集素材)则不经过预签名,直接调 `PutObjectCommand` 等。
|
||
|
||
### 5.4 与 Atlas 的 object_key 约定结合
|
||
|
||
本地桶内对象键沿用[技术文档第 4.2 节](../architecture/technology-and-deployment.md)的分层:
|
||
|
||
```text
|
||
atlas-dev-assets/
|
||
workspaces/{workspaceId}/
|
||
watermarks/{assetId}/
|
||
products/{productId}/original/{assetId}.jpg
|
||
products/{productId}/processed/{assetId}.jpg
|
||
products/{productId}/ai-generated/{assetId}.jpg
|
||
imports/{taskId}/{filename}
|
||
```
|
||
|
||
配套规则(详见[对象存储入门](./object-storage-object-key.md)):
|
||
|
||
- `media_assets` 表保存 `storage_provider='local_minio'` + `bucket` + `object_key`,**任何 URL 不入库**;
|
||
- 预览/下载 URL 由服务端按 `object_key` 短时签发,过期即失效;
|
||
- 来源站的原始图片 URL 只保存在采集证据里,转存成功后文件身份以 `object_key` 为准。
|
||
|
||
## 6. 常见问题
|
||
|
||
### 6.1 SDK 报错找不到桶或 DNS 异常
|
||
|
||
MinIO 默认不支持 `bucket.endpoint` 虚拟主机域名形式,SDK 必须开启 **path-style**(地址形如 `http://localhost:9000/atlas-dev-assets/...`)。AWS SDK v3 对应 `forcePathStyle: true`。该项目配置驱动这一点:本地为 `true`,Kodo 按其要求配置。
|
||
|
||
### 6.2 预签名 URL 在浏览器里打不开
|
||
|
||
预签名 URL 的 host 就是 `S3_ENDPOINT`。`http://localhost:9000` 只在**本机浏览器**可用;如果要给局域网同事访问,endpoint 需要用局域网 IP 并让 MinIO 监听对应地址。签名与 host 绑定,换了 host 签名即失效。
|
||
|
||
### 6.3 上传后 Content-Type 或文件名不对
|
||
|
||
预签名 `PutObjectCommand` 时设置的 `ContentType` 必须与前端实际上传时携带的 `Content-Type` 一致,否则 S3 签名校验会拒绝。项目在 Worker/服务端二次校验 MIME 与魔数,不信任浏览器声明。
|
||
|
||
### 6.4 想清空本地存储重来
|
||
|
||
```bash
|
||
docker compose down -v # -v 连同 minio_data 卷一起删除
|
||
docker compose up -d
|
||
```
|
||
|
||
然后重建桶(或让应用启动逻辑自动创建),即可配合 seed 数据得到干净环境。
|
||
|
||
### 6.5 时钟偏差报错(RequestTimeTooSkewed)
|
||
|
||
S3 签名对时间敏感。宿主机睡眠/恢复后 Docker 容器时钟可能漂移,重启容器或同步系统时间即可。
|
||
|
||
## 7. 本项目边界
|
||
|
||
- MinIO 仅用于 **local** 环境;staging/production 使用七牛 Kodo 独立 Bucket,严禁混用。
|
||
- Bucket 保持私有;原图、水印模板图、AI 生成图和导入文件不设公开读。
|
||
- 浏览器只持有短时上传/下载凭证;永久密钥只存在于服务端环境配置。
|
||
- 对象物理删除前必须确认无数据库引用;由后台清理任务在软删除保留期结束后执行。
|
||
|
||
相关文档:[对象存储与 object_key](./object-storage-object-key.md)、[技术栈、异步任务与部署方案](../architecture/technology-and-deployment.md)、[浏览器采集插件需求](../requirements/collector-browser-extension.md)。
|