# 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 前必须先清空其中所有对象