上一篇我们聊的是 MinIO 的防盗链策略,偏运维侧。这篇我们把视角切回开发侧:如何用 Python 和 Go 快速接入 MinIO 完成日常的文件操作。
MinIO 的优势之一就是几乎全语言覆盖的 SDK——只要你的语言能发 HTTP 请求,就能操作 S3 兼容的对象存储。但"能用"和"用得顺手"之间,往往差着一份靠谱的示例代码和几页避坑指南。
本文的目标很简单:给 Python 和 Go 开发者各一份"复制粘贴就能跑"的代码模板,覆盖上传、下载、列举、删除、预签名 URL 五个最高频的操作。
一、环境准备:运行一个本地 MinIO
如果你已经有 MinIO 实例,可以跳过这一步。否则用 Docker 一键启动:
docker run -d \ -p 9000:9000 \ -p 9001:9001 \ --name minio \ -e MINIO_ROOT_USER=minioadmin \ -e MINIO_ROOT_PASSWORD=minioadmin \ minio/minio server /data --console-address ":9001"
- 9000:S3 API 端口(SDK 连这个)
- 9001:Web Console 端口(浏览器访问
http://localhost:9001)
启动后手动登录 Console,创建一个 bucket,比如叫 demo。
二、Python 接入:minio 官方 SDK
Python 的 minio 包是官方维护的,API 设计非常贴近 S3 原生语义,pip 直接装:
2.1 初始化客户端
from minio import Miniofrom minio.error import S3Errorclient = Minio( "localhost:9000", access_key="minioadmin", secret_key="minioadmin", secure=False, # 本地测试关闭 HTTPS)# 检查 bucket 是否存在,不存在则创建bucket_name = "demo"found = client.bucket_exists(bucket_name)if not found: client.make_bucket(bucket_name) print(f"Bucket '{bucket_name}' created")else: print(f"Bucket '{bucket_name}' already exists")
注意 secure=False:本地开发时 MinIO 默认是 HTTP,如果这里写 True 会报 SSL 握手错误。生产环境必须配证书并改为 True。
2.2 上传文件
from minio.commonconfig import Tagsobject_name = "reports/2026/04/sales.csv"file_path = "./sales.csv"# 方式一:上传本地文件(推荐)client.fput_object( bucket_name=bucket_name, object_name=object_name, file_path=file_path, content_type="text/csv",)print(f"Uploaded: {object_name}")# 方式二:上传内存中的字节流# data = b"id,name,amount\n1,Alice,100\n2,Bob,200"# client.put_object(# bucket_name=bucket_name,# object_name="reports/in_memory.csv",# data=BytesIO(data),# length=len(data),# content_type="text/csv",# )
fput_object 和 put_object 的区别在于:前者接受本地文件路径,SDK 内部帮你处理分块;后者接受文件对象,适合内存流或网络流场景。
2.3 下载文件
# 方式一:下载到本地路径client.fget_object( bucket_name=bucket_name, object_name=object_name, file_path="./sales_downloaded.csv",)print("Downloaded to ./sales_downloaded.csv")# 方式二:读取到内存response = client.get_object(bucket_name, object_name)withopen("./sales_from_stream.csv", "wb") as f: for chunk in response.stream(4096): f.write(chunk)response.close()response.release_conn()
必须调用 close() 和 release_conn(),否则连接池会被占满。更安全的写法是用 with 上下文管理器,但 minio 包的 get_object 返回的是 urllib3 的 HTTPResponse,标准 with 支持一般没问题。
2.4 列举对象
from minio import Minioobjects = client.list_objects( bucket_name, prefix="reports/2026/", recursive=True, # 递归列举子目录)for obj in objects: print(f"{obj.object_name} | {obj.size} bytes | {obj.last_modified}")
list_objects 默认是扁平化的——MinIO 本身没有"目录"概念,reports/2026/ 这种斜杠只是对象名的前缀。设置 recursive=True 会列出所有以该前缀开头的对象;设为 False 则只在当前"层级"列举。
2.5 删除对象
# 单条删除client.remove_object(bucket_name, object_name)print(f"Deleted: {object_name}")# 批量删除(更省 API 调用)from minio.deleteobjects import DeleteObjectdelete_object_list = [DeleteObject("reports/2026/04/a.csv"), DeleteObject("reports/2026/04/b.csv")]errors = client.remove_objects(bucket_name, delete_object_list)for error in errors: print(f"Deletion error: {error}")
2.6 生成预签名 URL
from datetime import timedeltaurl = client.presigned_get_object( bucket_name=bucket_name, object_name=object_name, expires=timedelta(hours=1),)print(f"Presigned URL (valid 1h): {url}")# 生成 PUT 预签名 URL,允许前端直传upload_url = client.presigned_put_object( bucket_name=bucket_name, object_name="uploads/user123/avatar.jpg", expires=timedelta(minutes=15),)print(f"Presigned PUT URL: {upload_url}")
三、Go 接入:minio-go 官方 SDK
Go 的 SDK 是 MinIO 团队用自家语言写的,性能和完整性都是第一梯队。安装:
go get github.com/minio/minio-go/v7go get github.com/minio/minio-go/v7/pkg/credentials
3.1 初始化客户端
package mainimport ("context""fmt""log""github.com/minio/minio-go/v7""github.com/minio/minio-go/v7/pkg/credentials")funcmain() { ctx := context.Background() client, err := minio.New("localhost:9000", &minio.Options{ Creds: credentials.NewStaticV4("minioadmin", "minioadmin", ""), Secure: false, })if err != nil { log.Fatalf("Failed to create client: %v", err) } bucketName := "demo" exists, err := client.BucketExists(ctx, bucketName)if err != nil { log.Fatalf("Check bucket failed: %v", err) }if !exists { err = client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})if err != nil { log.Fatalf("Create bucket failed: %v", err) } fmt.Printf("Bucket '%s' created\n", bucketName) } else { fmt.Printf("Bucket '%s' already exists\n", bucketName) }}
Go SDK 的几乎每个操作都需要传 context.Context,这是为了支持超时和取消。生产代码建议从 http.Request 里取 r.Context() 透传下去,而不是用裸的 context.Background()。
3.2 上传文件
package mainimport ("context""fmt""log""mime""path/filepath""github.com/minio/minio-go/v7")funcuploadFile(client *minio.Client, bucketName, objectName, filePath string) { ctx := context.Background() contentType := mime.TypeByExtension(filepath.Ext(filePath))if contentType == "" { contentType = "application/octet-stream" } uploadInfo, err := client.FPutObject( ctx, bucketName, objectName, filePath, minio.PutObjectOptions{ContentType: contentType}, )if err != nil { log.Fatalf("Upload failed: %v", err) } fmt.Printf("Uploaded: %s (size: %d bytes, etag: %s)\n", uploadInfo.Key, uploadInfo.Size, uploadInfo.ETag)}
Go SDK 的 FPutObject 内部会自动做多分片并行上传(默认分片大小 16MB),对大文件非常友好。如果你需要更细的控制(比如自定义分片大小),可以用 PutObject 并传入 minio.PutObjectOptions{PartSize: 64 * 1024 * 1024}。
3.3 下载文件
funcdownloadFile(client *minio.Client, bucketName, objectName, destPath string) { ctx := context.Background() err := client.FGetObject(ctx, bucketName, objectName, destPath, minio.GetObjectOptions{})if err != nil { log.Fatalf("Download failed: %v", err) } fmt.Printf("Downloaded to: %s\n", destPath)// 方式二:读取到内存// obj, err := client.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})// if err != nil { ... }// defer obj.Close()// buf := new(bytes.Buffer)// buf.ReadFrom(obj)}
3.4 列举对象
func listObjects(client *minio.Client, bucketName, prefix string) { ctx := context.Background() objectCh := client.ListObjects(ctx, bucketName, minio.ListObjectsOptions{Prefix: prefix,Recursive: true, })for object := range objectCh {if object.Err != nil { log.Printf("List error: %v", object.Err)continue } fmt.Printf("%s | %d bytes | %s\n", object.Key, object.Size, object.LastModified) }}
Go SDK 的 ListObjects 返回的是一个 channel,内部已经帮你处理了分页(S3 的 ListObjectsV2 一次最多返回 1000 条),你只需 for range 遍历即可。这种设计在处理百万级对象时内存占用极低。
3.5 删除对象
funcremoveObject(client *minio.Client, bucketName, objectName string) { ctx := context.Background() err := client.RemoveObject(ctx, bucketName, objectName, minio.RemoveObjectOptions{})if err != nil { log.Fatalf("Remove failed: %v", err) } fmt.Printf("Deleted: %s\n", objectName)}// 批量删除funcremoveObjects(client *minio.Client, bucketName string, objectNames []string) { ctx := context.Background() objectsCh := make(chan minio.ObjectInfo, len(objectNames))for _, name := range objectNames { objectsCh <- minio.ObjectInfo{Key: name} }close(objectsCh)for removeErr := range client.RemoveObjects(ctx, bucketName, objectsCh, minio.RemoveObjectsOptions{}) {if removeErr.Err != nil { log.Printf("Remove error on '%s': %v", removeErr.ObjectName, removeErr.Err) } }}
3.6 生成预签名 URL
import "time"funcpresignedURL(client *minio.Client, bucketName, objectName string) { ctx := context.Background()// GET 预签名 url, err := client.PresignedGetObject(ctx, bucketName, objectName, time.Hour, nil)if err != nil { log.Fatalf("Presign failed: %v", err) } fmt.Printf("Presigned GET: %s\n", url)// PUT 预签名(前端直传) putURL, err := client.PresignedPutObject(ctx, bucketName, "uploads/test.jpg", 15*time.Minute)if err != nil { log.Fatalf("Presign PUT failed: %v", err) } fmt.Printf("Presigned PUT: %s\n", putURL)}
四、Python vs Go:SDK 差异速查
| | |
|---|
| 安装 | pip install minio | go get github.com/minio/minio-go/v7 |
| 上下文管理 | | |
| 流式上传 | put_object(data=BytesIO(...)) | PutObject(reader, ...) |
| 流式下载 | get_object() | GetObject() 返回 *Object(实现了 io.Reader) |
| 自动分片 | fput_object | FPutObject |
| 列举方式 | | |
| 类型安全 | | |
两个 SDK 的核心 API 命名高度一致(fput_object / FPutObject、presigned_get_object / PresignedGetObject),所以从 Python 切到 Go,或者反过来,学习成本主要是语言本身,而不是 SDK 语义。
五、三个通用踩坑点(语言无关)
1. secure=True/False 的迷惑性
很多新手本地测试时忘了关 secure,或者上线时忘了开。一个稳妥的做法是把 endpoint 和 secure 做成配置项,根据环境自动判断:
# Python 示例import osendpoint = os.getenv("MINIO_ENDPOINT", "localhost:9000")secure = os.getenv("MINIO_SECURE", "false").lower() == "true"client = Minio(endpoint, access_key=..., secret_key=..., secure=secure)
2. 对象名前面的斜杠
MinIO 的对象名不应该以 / 开头。如果你写 object_name="/reports/1.csv",实际存储的名字就是带斜杠的,后续列举和访问时如果不带这个斜杠就会 404。建议统一规范:对象名永远不以 / 开头,目录分隔只用 /。
3. 大文件的内存控制
无论 Python 还是 Go,上传/下载大文件时都应该用流式 API(put_object / PutObject、get_object / GetObject),而不是一次性读到内存。特别是 Python,如果直接用 open(file, "rb").read() 去传一个 2GB 的视频文件,进程会直接 OOM。
六、写在最后
Python 和 Go 是目前接入 MinIO 最活跃的两门语言:Python 适合数据分析、AI 训练 pipeline、后端脚本等场景;Go 适合高并发微服务、网关、存储中间件。两者的官方 SDK 都非常成熟,文档和报错信息也很友好。
本文给的代码都是可直接运行的最小示例,你可以把初始化部分抽成工厂函数/单例,把 bucket 名和 endpoint 放到配置文件里,再包一层业务语义(如 upload_avatar(user_id, file)),就是一套生产可用的文件存储层了。