710 lines
15 KiB
Markdown
710 lines
15 KiB
Markdown
# MinIO 文件存储接口文档
|
||
|
||
## 一、概述
|
||
|
||
本项目已集成 MinIO 对象存储,并在 Django 后端提供统一的文件存储 API。
|
||
|
||
### MinIO 服务信息
|
||
|
||
| 项目 | 值 |
|
||
|------|-----|
|
||
| API 地址 | `http://127.0.0.1:9000` |
|
||
| Web 控制台 | `http://127.0.0.1:9001` |
|
||
| 用户名 | `admin` |
|
||
| 密码 | `zhengsl2026` |
|
||
| 默认 Bucket | `ai-trainprediction` |
|
||
|
||
### Django 配置
|
||
|
||
```python
|
||
MINIO_ENDPOINT = os.environ.get("MINIO_ENDPOINT", "127.0.0.1:9000")
|
||
MINIO_ACCESS_KEY = os.environ.get("MINIO_ACCESS_KEY", "admin")
|
||
MINIO_SECRET_KEY = os.environ.get("MINIO_SECRET_KEY", "zhengsl2026")
|
||
MINIO_SECURE = _env_bool("MINIO_SECURE", default=False)
|
||
MINIO_DEFAULT_BUCKET = os.environ.get("MINIO_DEFAULT_BUCKET", "ai-trainprediction")
|
||
MINIO_PRESIGN_EXPIRES = int(os.environ.get("MINIO_PRESIGN_EXPIRES", "3600"))
|
||
```
|
||
|
||
### 认证要求
|
||
|
||
所有 MinIO 接口均要求 JWT 认证:
|
||
|
||
```http
|
||
Authorization: Bearer {access_token}
|
||
```
|
||
|
||
### 接口前缀
|
||
|
||
```text
|
||
/server/minio/
|
||
```
|
||
|
||
---
|
||
|
||
## 二、接口总览
|
||
|
||
| 方法 | 路径 | 功能 |
|
||
|------|------|------|
|
||
| POST | `/server/minio/bucket/create/` | 创建 Bucket |
|
||
| GET | `/server/minio/bucket/list/` | 列出 Bucket |
|
||
| DELETE | `/server/minio/bucket/delete/` | 删除 Bucket |
|
||
| POST | `/server/minio/file/upload/` | 单文件上传 |
|
||
| POST | `/server/minio/files/upload/` | 批量文件上传 |
|
||
| POST | `/server/minio/file/upload-zip/` | ZIP 解压后批量入库 |
|
||
| POST | `/server/minio/file/upload-video-frames/` | 视频抽帧后图片入库 |
|
||
| POST | `/server/minio/file/upload-base64/` | Base64 文件上传 |
|
||
| GET | `/server/minio/file/download/` | 流式下载文件 |
|
||
| GET | `/server/minio/file/presign/` | 获取预签名下载 URL |
|
||
| GET | `/server/minio/file/presign-upload/` | 获取预签名上传 URL |
|
||
| GET | `/server/minio/file/preview/` | 流式预览文件 |
|
||
| GET | `/server/minio/file/info/` | 获取文件元信息 |
|
||
| GET | `/server/minio/files/` | 列出对象 |
|
||
| DELETE | `/server/minio/file/delete/` | 删除单个文件 |
|
||
| DELETE | `/server/minio/files/delete/` | 批量删除文件 |
|
||
| POST | `/server/minio/file/copy/` | 复制文件 |
|
||
|
||
---
|
||
|
||
## 三、Bucket 管理
|
||
|
||
### 3.1 创建 Bucket
|
||
|
||
```http
|
||
POST /server/minio/bucket/create/
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `bucket_name` | string | 否 | Bucket 名称,默认 `ai-trainprediction` |
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"bucket_name": "my-bucket",
|
||
"created": true
|
||
},
|
||
"msg": "Bucket 创建成功"
|
||
}
|
||
```
|
||
|
||
说明:
|
||
- 若 Bucket 已存在,返回 `created: false`
|
||
- MinIO 返回 `BucketAlreadyExists` / `BucketAlreadyOwnedByYou` 时会转换为 `409`
|
||
|
||
### 3.2 列出 Bucket
|
||
|
||
```http
|
||
GET /server/minio/bucket/list/
|
||
```
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": ["ai-trainprediction", "my-bucket"],
|
||
"msg": "获取成功"
|
||
}
|
||
```
|
||
|
||
### 3.3 删除 Bucket
|
||
|
||
```http
|
||
DELETE /server/minio/bucket/delete/
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `bucket_name` | string | 是 | Bucket 名称 |
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"bucket_name": "my-bucket"
|
||
},
|
||
"msg": "Bucket 已删除"
|
||
}
|
||
```
|
||
|
||
错误说明:
|
||
- `404`:Bucket 不存在
|
||
- `409`:Bucket 非空,无法删除
|
||
|
||
---
|
||
|
||
## 四、文件上传
|
||
|
||
### 4.1 单文件上传
|
||
|
||
```http
|
||
POST /server/minio/file/upload/
|
||
Content-Type: multipart/form-data
|
||
```
|
||
|
||
请求参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `file` | File | 是 | 上传文件 |
|
||
| `bucket_name` | string | 否 | 目标 Bucket,默认 `ai-trainprediction` |
|
||
| `prefix` | string | 否 | 对象前缀,如 `images/datasets` |
|
||
| `object_name` | string | 否 | 指定对象名;不传则自动生成 |
|
||
|
||
自动命名规则:
|
||
|
||
```text
|
||
{prefix}/YYYY-MM-DD/{uuid12}_{original_filename}
|
||
```
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"bucket_name": "ai-trainprediction",
|
||
"object_name": "images/2026-07-01/a1b2c3d4e5f6_photo.jpg",
|
||
"original_name": "photo.jpg",
|
||
"size": 102400,
|
||
"content_type": "image/jpeg",
|
||
"presigned_url": "http://127.0.0.1:9000/..."
|
||
},
|
||
"msg": "上传成功"
|
||
}
|
||
```
|
||
|
||
说明:
|
||
- 该接口已改为流式上传,不会把整文件一次性读入内存
|
||
- 若 `object_name` 明确指定且对象已存在,则会被覆盖
|
||
|
||
### 4.2 批量文件上传
|
||
|
||
```http
|
||
POST /server/minio/files/upload/
|
||
Content-Type: multipart/form-data
|
||
```
|
||
|
||
请求参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `files` | File[] | 是 | 多个文件 |
|
||
| `bucket_name` | string | 否 | 目标 Bucket |
|
||
| `prefix` | string | 否 | 对象前缀 |
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": [
|
||
{
|
||
"object_name": "images/2026-07-01/a1b2_photo1.jpg",
|
||
"original_name": "photo1.jpg",
|
||
"size": 102400,
|
||
"content_type": "image/jpeg",
|
||
"presigned_url": "http://127.0.0.1:9000/..."
|
||
}
|
||
],
|
||
"msg": "全部上传成功,共 1 个文件"
|
||
}
|
||
```
|
||
|
||
### 4.3 Base64 文件上传
|
||
|
||
```http
|
||
POST /server/minio/file/upload-base64/
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `file_base64` | string | 是 | Base64 编码内容 |
|
||
| `filename` | string | 是 | 原始文件名 |
|
||
| `bucket_name` | string | 否 | 目标 Bucket |
|
||
| `prefix` | string | 否 | 对象前缀 |
|
||
|
||
请求示例:
|
||
|
||
```json
|
||
{
|
||
"file_base64": "/9j/4AAQSkZJRgABAQ...",
|
||
"filename": "avatar.png",
|
||
"bucket_name": "ai-trainprediction",
|
||
"prefix": "avatars"
|
||
}
|
||
```
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"bucket_name": "ai-trainprediction",
|
||
"object_name": "avatars/2026-07-01/a1b2c3d4e5f6_avatar.png",
|
||
"original_name": "avatar.png",
|
||
"size": 51200,
|
||
"content_type": "image/png",
|
||
"presigned_url": "http://127.0.0.1:9000/..."
|
||
},
|
||
"msg": "上传成功"
|
||
}
|
||
```
|
||
|
||
说明:
|
||
- 此接口当前只支持 `application/json`
|
||
- Base64 解码失败时返回 `400`
|
||
|
||
### 4.4 ZIP 解压批量入库
|
||
|
||
```http
|
||
POST /server/minio/file/upload-zip/
|
||
Content-Type: multipart/form-data
|
||
```
|
||
|
||
请求参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `file` | File | 是 | ZIP 压缩包 |
|
||
| `bucket_name` | string | 否 | 目标 Bucket |
|
||
| `prefix` | string | 否 | MinIO 对象前缀 |
|
||
| `images_only` | bool | 否 | 是否仅入库图片文件,默认 `false` |
|
||
|
||
说明:
|
||
- 接口会自动解压 ZIP 内文件并逐个上传到 MinIO
|
||
- 默认保留 ZIP 内相对目录结构
|
||
- 会自动过滤目录项、隐藏文件和非法路径片段(如 `..`)
|
||
- `images_only=true` 时,仅上传图片扩展名文件:`jpg/jpeg/png/bmp/gif/webp/tif/tiff`
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"bucket_name": "ai-trainprediction",
|
||
"zip_name": "dataset.zip",
|
||
"uploaded_count": 2,
|
||
"uploaded": [
|
||
{
|
||
"source_name": "images/cat.jpg",
|
||
"object_name": "datasets/images/cat.jpg",
|
||
"size": 102400,
|
||
"content_type": "image/jpeg"
|
||
}
|
||
],
|
||
"skipped": ["docs/readme.txt"]
|
||
},
|
||
"msg": "ZIP 解压入库成功,共上传 2 个文件"
|
||
}
|
||
```
|
||
|
||
### 4.5 视频抽帧图片入库
|
||
|
||
```http
|
||
POST /server/minio/file/upload-video-frames/
|
||
Content-Type: multipart/form-data
|
||
```
|
||
|
||
请求参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `file` | File | 是 | 视频文件 |
|
||
| `bucket_name` | string | 否 | 目标 Bucket |
|
||
| `prefix` | string | 否 | MinIO 对象前缀 |
|
||
| `interval_seconds` | int | 否 | 抽帧时间间隔,单位秒,默认 `1` |
|
||
| `start_second` | int | 否 | 开始抽帧时间,默认 `0` |
|
||
| `end_second` | int | 否 | 结束抽帧时间,不传则到视频末尾 |
|
||
| `max_frames` | int | 否 | 最多生成图片数,默认 `100` |
|
||
| `image_format` | string | 否 | 输出图片格式,支持 `jpg/jpeg/png`,默认 `jpg` |
|
||
|
||
说明:
|
||
- 当前支持的视频扩展名:`mp4/avi/mov/mkv/wmv/flv/mpeg/mpg/webm`
|
||
- 需要服务端安装 `opencv-python`
|
||
- 抽出的图片会存入 `{prefix}/{视频名}_frames/` 目录
|
||
- 文件名规则:`frame_00001_1200ms.jpg`
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"bucket_name": "ai-trainprediction",
|
||
"video_name": "demo.mp4",
|
||
"fps": 25.0,
|
||
"duration_seconds": 12.4,
|
||
"interval_seconds": 1,
|
||
"start_second": 0,
|
||
"end_second": 10,
|
||
"max_frames": 20,
|
||
"extracted_count": 10,
|
||
"extracted": [
|
||
{
|
||
"object_name": "videos/demo_frames/frame_00001_0ms.jpg",
|
||
"frame_index": 0,
|
||
"time_second": 0.0
|
||
}
|
||
]
|
||
},
|
||
"msg": "视频抽帧入库成功,共生成 10 张图片"
|
||
}
|
||
```
|
||
|
||
### 4.6 获取预签名上传 URL
|
||
|
||
```http
|
||
GET /server/minio/file/presign-upload/?bucket_name={bucket}&object_name={object}&expires={seconds}
|
||
```
|
||
|
||
请求参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `bucket_name` | string | 否 | 目标 Bucket |
|
||
| `object_name` | string | 是 | 目标对象名 |
|
||
| `expires` | int | 否 | 有效期秒数,默认 `3600`,最大 `604800` |
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"presigned_url": "http://127.0.0.1:9000/ai-trainprediction/path/file.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
|
||
"expires_in": 3600
|
||
},
|
||
"msg": "获取成功"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 五、文件下载与预览
|
||
|
||
### 5.1 流式下载文件
|
||
|
||
```http
|
||
GET /server/minio/file/download/?bucket_name={bucket}&object_name={object}
|
||
```
|
||
|
||
请求参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `bucket_name` | string | 否 | Bucket 名称 |
|
||
| `object_name` | string | 是 | 对象名 |
|
||
|
||
说明:
|
||
- 返回流式响应,适合大文件下载
|
||
- `Content-Disposition` 为 `attachment`
|
||
|
||
### 5.2 获取预签名下载 URL
|
||
|
||
```http
|
||
GET /server/minio/file/presign/?bucket_name={bucket}&object_name={object}&expires={seconds}
|
||
```
|
||
|
||
请求参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `bucket_name` | string | 否 | Bucket 名称 |
|
||
| `object_name` | string | 是 | 对象名 |
|
||
| `expires` | int | 否 | 有效期秒数,默认 `3600`,最大 `604800` |
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"presigned_url": "http://127.0.0.1:9000/ai-trainprediction/path/file.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
|
||
"expires_in": 3600
|
||
},
|
||
"msg": "获取成功"
|
||
}
|
||
```
|
||
|
||
说明:
|
||
- `expires <= 0` 时返回 `400`
|
||
- 超过 7 天会被自动截断为 `604800`
|
||
|
||
### 5.3 流式预览文件
|
||
|
||
```http
|
||
GET /server/minio/file/preview/?bucket_name={bucket}&object_name={object}
|
||
```
|
||
|
||
说明:
|
||
- 返回流式响应
|
||
- `Content-Disposition` 为 `inline`
|
||
- 适合图片、PDF、文本等浏览器可直接预览的文件
|
||
|
||
---
|
||
|
||
## 六、文件管理
|
||
|
||
### 6.1 获取文件元信息
|
||
|
||
```http
|
||
GET /server/minio/file/info/?bucket_name={bucket}&object_name={object}
|
||
```
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"name": "images/2026-07-01/a1b2_photo.jpg",
|
||
"size": 102400,
|
||
"etag": "d41d8cd98f00b204e9800998ecf8427e",
|
||
"content_type": "image/jpeg",
|
||
"last_modified": "2026-07-01T12:00:00+00:00",
|
||
"presigned_url": "http://127.0.0.1:9000/..."
|
||
},
|
||
"msg": "获取成功"
|
||
}
|
||
```
|
||
|
||
### 6.2 列出对象
|
||
|
||
```http
|
||
GET /server/minio/files/?bucket_name={bucket}&prefix={prefix}
|
||
```
|
||
|
||
请求参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `bucket_name` | string | 否 | Bucket 名称 |
|
||
| `prefix` | string | 否 | 仅列出指定前缀下的对象 |
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": [
|
||
{
|
||
"name": "images/2026-07-01/a1b2_photo.jpg",
|
||
"size": 102400,
|
||
"last_modified": "2026-07-01T12:00:00+00:00",
|
||
"etag": "d41d8cd98f00b204e9800998ecf8427e",
|
||
"content_type": "image/jpeg",
|
||
"is_dir": false
|
||
}
|
||
],
|
||
"msg": "共 1 个对象"
|
||
}
|
||
```
|
||
|
||
### 6.3 删除单个文件
|
||
|
||
```http
|
||
DELETE /server/minio/file/delete/
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体:
|
||
|
||
```json
|
||
{
|
||
"bucket_name": "ai-trainprediction",
|
||
"object_name": "images/2026-07-01/a1b2_photo.jpg"
|
||
}
|
||
```
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"object_name": "images/2026-07-01/a1b2_photo.jpg"
|
||
},
|
||
"msg": "删除成功"
|
||
}
|
||
```
|
||
|
||
### 6.4 批量删除文件
|
||
|
||
```http
|
||
DELETE /server/minio/files/delete/
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体:
|
||
|
||
```json
|
||
{
|
||
"bucket_name": "ai-trainprediction",
|
||
"object_names": [
|
||
"images/photo1.jpg",
|
||
"images/photo2.png"
|
||
]
|
||
}
|
||
```
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"deleted": 2
|
||
},
|
||
"msg": "全部删除成功"
|
||
}
|
||
```
|
||
|
||
部分成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 207,
|
||
"data": {
|
||
"deleted": 1,
|
||
"errors": ["images/photo2.png"]
|
||
},
|
||
"msg": "部分删除,1 个失败"
|
||
}
|
||
```
|
||
|
||
### 6.5 复制文件
|
||
|
||
```http
|
||
POST /server/minio/file/copy/
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体:
|
||
|
||
```json
|
||
{
|
||
"src_bucket": "ai-trainprediction",
|
||
"src_object": "images/photo.jpg",
|
||
"dst_bucket": "ai-trainprediction",
|
||
"dst_object": "backup/photo.jpg"
|
||
}
|
||
```
|
||
|
||
请求参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `src_bucket` | string | 否 | 源 Bucket,默认 `ai-trainprediction` |
|
||
| `src_object` | string | 是 | 源对象名 |
|
||
| `dst_bucket` | string | 否 | 目标 Bucket,默认 `ai-trainprediction` |
|
||
| `dst_object` | string | 是 | 目标对象名 |
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"object_name": "backup/photo.jpg"
|
||
},
|
||
"msg": "复制成功"
|
||
}
|
||
```
|
||
|
||
说明:
|
||
- 若目标 Bucket 不存在,接口会自动创建
|
||
|
||
---
|
||
|
||
## 七、错误码说明
|
||
|
||
| HTTP 状态码 | 含义 |
|
||
|-------------|------|
|
||
| `200` | 成功 |
|
||
| `207` | 批量删除部分成功 |
|
||
| `400` | 参数错误、`expires` 不合法、Base64 解码失败等 |
|
||
| `401` | 未携带或携带了无效 JWT Token |
|
||
| `403` | MinIO 拒绝访问 |
|
||
| `404` | Bucket 或对象不存在 |
|
||
| `409` | Bucket 已存在、Bucket 非空等冲突 |
|
||
| `500` | 其他 MinIO / 服务端错误 |
|
||
|
||
错误响应格式:
|
||
|
||
```json
|
||
{
|
||
"code": 409,
|
||
"msg": "Bucket 非空,无法删除",
|
||
"detail": "S3 operation failed; code: BucketNotEmpty, ..."
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 八、代码结构
|
||
|
||
```text
|
||
backend/
|
||
├── config/settings/base.py
|
||
├── config/api_urls.py
|
||
└── apps/common/
|
||
├── minio_client.py
|
||
└── views/minio_storage.py
|
||
```
|
||
|
||
### `minio_client.py`
|
||
|
||
封装的核心方法包括:
|
||
|
||
| 方法 | 功能 |
|
||
|------|------|
|
||
| `bucket_exists()` | 检查 Bucket 是否存在 |
|
||
| `create_bucket()` | 创建 Bucket |
|
||
| `list_buckets()` | 列出 Bucket |
|
||
| `remove_bucket()` | 删除空 Bucket |
|
||
| `upload_file()` | 上传本地文件 |
|
||
| `upload_bytes()` | 上传字节数据 |
|
||
| `upload_stream()` | 流式上传文件对象 |
|
||
| `download_file()` | 下载到本地 |
|
||
| `download_bytes()` | 下载为 bytes |
|
||
| `get_object()` | 获取对象流响应 |
|
||
| `get_object_info()` | 获取对象元信息 |
|
||
| `object_exists()` | 判断对象是否存在 |
|
||
| `list_objects()` | 列出对象 |
|
||
| `delete_object()` | 删除单个对象 |
|
||
| `delete_objects()` | 批量删除对象 |
|
||
| `presign_get()` | 获取预签名下载 URL |
|
||
| `presign_upload()` | 获取预签名上传 URL |
|
||
| `generate_object_name()` | 生成唯一对象名 |
|
||
| `copy_object()` | 复制对象 |
|
||
|
||
---
|
||
|
||
## 九、注意事项
|
||
|
||
1. 所有接口均要求 JWT 认证
|
||
2. 下载和预览接口已经改为流式响应,适合较大文件
|
||
3. `upload-base64` 当前只支持 `application/json`
|
||
4. ZIP 入库默认保留压缩包中的相对目录结构,并会过滤非法路径
|
||
5. 视频抽帧依赖 `opencv-python`
|
||
6. `expires` 必须大于 0,最大有效期为 7 天
|
||
7. 指定 `object_name` 时可能覆盖已有对象,请谨慎使用
|
||
8. 删除 Bucket 前必须先清空其中所有对象
|