Files
atlas-seller-kit/docs/guides/minio-local-deployment.md

241 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 只换这一组值)
S3_ENDPOINT=http://localhost:9000
S3_REGION=us-east-1 # MinIO 不校验 region,任意合法值即可
S3_BUCKET=atlas-dev-assets
S3_ACCESS_KEY=atlas-dev
S3_SECRET_KEY=atlas-dev-secret
S3_FORCE_PATH_STYLE=true # MinIO 必须用 path-style,见第 6.3 节
```
## 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.S3_ENDPOINT!, // http://localhost:9000
region: process.env.S3_REGION!, // MinIO 任意值即可
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY!,
secretAccessKey: process.env.S3_SECRET_KEY!,
},
forcePathStyle: true, // MinIO 必须;Kodo 走虚拟主机式,配置驱动即可
});
// 预签名上传 URL:前端拿到后直传,浏览器不保存永久密钥
export async function presignUpload(objectKey: string, contentType: string) {
const cmd = new PutObjectCommand({ Bucket: process.env.S3_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.S3_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)。