最近,我刚刚完成了一个异步任务,所以也顺手整理了下 MinIO 对象存储的使用笔记,防止之后忘记。
MinIO 是一个非常流行的开源对象存储服务,兼容 Amazon S3 协议,特别适合存储图片、视频、文档等非结构化数据,可以用来搭配 RabbitMq (消息队列)做一些异步任务。
一、 准备工作
1.1 安装依赖
首先,安装 MinIO 的 Python SDK:
pip install minio
1.2 编写配置文件
在开始编码之前,我们需要配置一下参数。创建一个 config.json 文件:
{
"miniIOConfig": {
"bucketName": "your-bucket",
"endpoint": "https://your-minio-server.com",
"accessKey": "admin",
"secretKey": "your-secret-key"
}
}
| 配置项 | 说明 |
|---|---|
| bucketName | 存储桶名称(类似于文件夹) |
| endpoint | MiniIO 服务地址 |
| accessKey | 访问密钥(用户名) |
| secretKey | 秘密密钥(密码) |
二、 核心类实现
为了方便调用,我们将所有的连接和操作逻辑封装成一个 MiniIOClient 类。新建 miniIOUse.py,填入以下代码:
import json
import os
import io
from datetime import timedelta
from minio import Minio
from minio.error import S3Error
class MiniIOClient:
def __init__(self, config_path="config.json"):
# 读取配置文件
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f).get("miniIOConfig")
self.bucket_name = config.get("bucketName")
endpoint = config.get("endpoint")
access_key = config.get("accessKey")
secret_key = config.get("secretKey")
# 判断是否为 HTTPS
secure = endpoint.startswith("https")
# 去除协议头,Minio 类只需要 host:port
if endpoint.startswith("https://") or endpoint.startswith("http://"):
endpoint = endpoint.split("://")[1]
self.client = Minio(
endpoint,
access_key=access_key,
secret_key=secret_key,
secure=secure
)
def ensure_bucket_exists(self):
"""检查存储桶是否存在,不存在则创建"""
ifnot self.client.bucket_exists(self.bucket_name):
self.client.make_bucket(self.bucket_name)
def upload_file(self, file_path, object_name=None, content_type=None):
"""上传本地文件"""
if object_name isNone:
object_name = os.path.basename(file_path)
self.client.fput_object(
self.bucket_name,
object_name,
file_path,
content_type=content_type
)
def upload_bytes(self, data, object_name, length=None, content_type="application/octet-stream"):
"""直接上传字节数据或文本"""
if isinstance(data, str):
data = data.encode('utf-8')
if length isNone:
length = len(data)
data_stream = io.BytesIO(data)
self.client.put_object(
self.bucket_name,
object_name,
data_stream,
length,
content_type=content_type
)
def download_file(self, object_name, file_path=None):
"""下载文件到本地或读取到内存"""
try:
response = self.client.get_object(self.bucket_name, object_name)
data = response.read()
response.close()
response.release_conn()
if file_path:
with open(file_path, 'wb') as f:
f.write(data)
returnTrue
return data
except S3Error as e:
if e.code == 'NoSuchKey':
returnNone
raise
def delete_file(self, object_name):
"""删除文件"""
try:
self.client.remove_object(self.bucket_name, object_name)
returnTrue
except S3Error:
returnFalse
def list_files(self, prefix=None, recursive=False):
"""列出文件"""
objects = self.client.list_objects(
self.bucket_name,
prefix=prefix,
recursive=recursive
)
result = []
for obj in objects:
if obj.is_dir:
continue
result.append({
'name': obj.object_name,
'size': obj.size,
'last_modified': obj.last_modified,
'etag': obj.etag
})
return result
def file_exists(self, object_name):
"""检查文件是否存在"""
try:
self.client.stat_object(self.bucket_name, object_name)
returnTrue
except S3Error:
returnFalse
def get_file_info(self, object_name):
"""获取文件元数据"""
try:
stat = self.client.stat_object(self.bucket_name, object_name)
return {
'name': stat.object_name,
'size': stat.size,
'last_modified': stat.last_modified,
'etag': stat.etag,
'content_type': stat.content_type
}
except S3Error:
returnNone
def get_presigned_url(self, object_name, expires=3600):
"""生成预签名分享链接"""
return self.client.presigned_get_object(
self.bucket_name,
object_name,
expires=timedelta(seconds=expires)
)
三、 操作指南
3.1 初始化客户端与检查存储桶
from miniIOUse import MiniIOClient
# 使用默认配置文件
client = MiniIOClient()
# 或指定配置文件路径
# client = MiniIOClient("custom_config.json")
# 自动检查存储桶是否存在,不存在则创建
client.ensure_bucket_exists()
3.2 上传本地文件
# 基础用法:使用原文件名
client.upload_file("/your/file_path/file.pdf")
# 高级用法:指定对象名称和MIME类型
client.upload_file(
"example.pdf",
"your-bucket/folder/example.pdf",
"application/pdf"
)
常用 MIME 类型:
| 文件类型 | MIME 类型 |
|---|---|
| 纯文本 | text/plain |
| PDF文档 | application/pdf |
| JPEG图片 | image/jpeg |
| PNG图片 | image/png |
| JSON数据 | application/json |
| 二进制数据 | application/octet-stream |
3.3 直接上传字节数据
无需本地文件,直接上传内存中的数据:
# 上传文本
client.upload_bytes(
"Hello World",
"greeting.txt",
11,
"text/plain"
)
# 上传二进制数据
client.upload_bytes(
b'\x00\x01\x02',
"binary.bin",
3,
"application/octet-stream"
)
参数说明:
-
data:要上传的数据(字符串或字节) -
object_name:存储的对象名称 -
length:数据长度(字节数,若不填会自动计算) -
content_type:MIME类型(可选)
3.4 下载文件
# 仅读取内容到内存
content = client.download_file("example.pdf")
if content:
print(f"下载了 {len(content)} 字节")
# 下载并保存到本地文件
client.download_file("example.pdf", "/your/path/save.pdf")
返回值 :仅读取时返回文件内容的字节数据,失败返回 None;保存到本地时成功返回 True。
3.5 删除文件
client.delete_file("old_example.pdf")
返回值 :成功返回 True,失败返回 False。
3.6 列出文件
# 列出所有文件
all_files = client.list_files()
# 只列出特定目录下的文件
docs = client.list_files(prefix="example/")
# 递归列出所有文件(包括子目录)
all_recursive = client.list_files(recursive=True)
# 遍历结果
for file in all_files:
print(f"文件名: {file['name']}")
print(f"大小: {file['size']} bytes")
print(f"修改时间: {file['last_modified']}")
print(f"ETag: {file['etag']}")
print("-" * 30)
3.7 检查文件是否存在
if client.file_exists("exanple.pdf"):
print("✅ 文件存在")
else:
print("❌ 文件不存在")
3.8 获取文件元数据
info = client.get_file_info("example.pdf")
if info:
print(f"文件名: {info['name']}")
print(f"文件大小: {info['size']} bytes")
print(f"最后修改: {info['last_modified']}")
print(f"ETag: {info['etag']}")
print(f"文件类型: {info['content_type']}")
3.9 生成分享链接(预签名URL)
# 生成1小时有效的分享链接
share_url = client.get_presigned_url("example.pdf")
print(f"分享链接(1小时有效): {share_url}")
# 生成24小时有效的链接
long_url = client.get_presigned_url(
"example.pdf",
expires=86400 # 24小时 = 86400秒
)
print(f"分享链接(24小时有效): {long_url}")
四、 完整示例
把上面的功能串联起来,就是一个完整的示例:
from miniIOUse import MiniIOClient
def main():
# 1. 正在连接 MiniIO
client = MiniIOClient("config.json")
# 2. 检查存储桶
client.ensure_bucket_exists()
# 3. 上传文件
client.upload_file(
"example.pdf",
"your/file_path/example.pdf",
"application/pdf"
)
# 4. 上传文本数据
client.upload_bytes(
"重要通知内容",
"notices/example.txt",
19,
"text/plain"
)
# 5. 检查文件是否存在
if client.file_exists("your/path/example.pdf"):
print("✅ 文件上传成功")
# 6. 获取文件信息
info = client.get_file_info("your/path/example.pdf")
print(f"文件大小: {info['size']} bytes")
# 7. 列出文件
client.list_files(prefix="example/", recursive=True)
# 8. 下载文件
client.download_file(
"your/path/example.pdf",
"downloaded.pdf"
)
# 9. 生成分享链接
share_url = client.get_presigned_url(
"your/path/example.pdf",
expires=7200
)
# 10. 清理测试文件
client.delete_file("example/example.txt")
if __name__ == "__main__":
main()
五、 注意事项
5.1 HTTPS vs HTTP
代码已实现自动判断,依据 config.json 中 endpoint 是否以 https:// 开头来决定。若是 HTTP 协议,配置时写成 http://your-minio-server.com 即可。
5.2 路径分隔符
对象名称中的路径建议统一使用 / 作为分隔符:
"your/path/example.pdf"
5.3 中文文件名
确保使用 UTF-8 编码,避免乱码问题。
5.4 大文件上传
对于大于 100MB 的文件,建议使用分片上传(fput_object 方法会自动处理)。
5.5 权限控制
确保配置文件中的 accessKey 和 secretKey 有足够的权限操作目标存储桶。
5.6 异常处理
所有方法都可能抛出 S3Error 异常,建议在实际生产环境中添加异常处理:
from minio.error import S3Error
try:
client.upload_file("file.pdf")
except S3Error as e:
print(f"操作失败: {e}")