Go Minio
MinIO 是一个高性能、云原生的对象存储服务器,与 Amazon S3 API 完全兼容。它使用 Go 语言编写,非常适合存储非结构化数据,如图片、视频、日志文件等。
下面是一个示例:
package main
import (
"bytes"
"context"
"crypto/md5"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
// 1. MinIO 客户端管理
type MinIOClient struct {
client *minio.Client
bucket string
}
func NewMinIOClient(endpoint, accessKey, secretKey, bucket string, useSSL bool) (*MinIOClient, error) {
// 初始化 MinIO 客户端
client, err := minio.New(endpoint, &minio.Options{
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
Secure: useSSL,
})
if err != nil {
return nil, fmt.Errorf("创建 MinIO 客户端失败: %v", err)
}
// 检查桶是否存在,不存在则创建
exists, err := client.BucketExists(context.Background(), bucket)
if err != nil {
return nil, fmt.Errorf("检查桶失败: %v", err)
}
if !exists {
err = client.MakeBucket(context.Background(), bucket, minio.MakeBucketOptions{})
if err != nil {
return nil, fmt.Errorf("创建桶失败: %v", err)
}
fmt.Printf("✅ 创建桶: %s\n", bucket)
}
// 设置桶策略(公开读取)
policy := `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": ["*"]},
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::%s/*"]
}
]
}`
err = client.SetBucketPolicy(context.Background(), bucket, fmt.Sprintf(policy, bucket))
if err != nil {
log.Printf("⚠️ 设置桶策略失败: %v", err)
}
return &MinIOClient{
client: client,
bucket: bucket,
}, nil
}
func (m *MinIOClient) Close() {
// MinIO 客户端无需显式关闭
}
// 2. 基本文件操作示例
func demonstrateBasicOperations(client *MinIOClient) {
fmt.Println("\n=== 基本文件操作 ===")
ctx := context.Background()
// 上传字符串内容
fmt.Println("\n1. 上传字符串内容:")
content := "Hello, MinIO! 这是测试内容。"
objectName := "test-files/hello.txt"
reader := strings.NewReader(content)
info, err := client.client.PutObject(ctx, client.bucket, objectName, reader, int64(len(content)), minio.PutObjectOptions{
ContentType: "text/plain",
})
if err != nil {
log.Printf("上传文件失败: %v", err)
return
}
fmt.Printf("✅ 上传成功: %s (大小: %d bytes)\n", objectName, info.Size)
// 下载文件内容
fmt.Println("\n2. 下载文件内容:")
object, err := client.client.GetObject(ctx, client.bucket, objectName, minio.GetObjectOptions{})
if err != nil {
log.Printf("获取文件失败: %v", err)
return
}
defer object.Close()
var buf bytes.Buffer
if _, err := io.Copy(&buf, object); err != nil {
log.Printf("读取文件失败: %v", err)
return
}
fmt.Printf("文件内容: %s\n", buf.String())
// 检查文件是否存在
fmt.Println("\n3. 检查文件是否存在:")
_, err = client.client.StatObject(ctx, client.bucket, objectName, minio.StatObjectOptions{})
if err != nil {
fmt.Printf("❌ 文件不存在: %v\n", err)
} else {
fmt.Printf("✅ 文件存在: %s\n", objectName)
}
// 删除文件
fmt.Println("\n4. 删除文件:")
err = client.client.RemoveObject(ctx, client.bucket, objectName, minio.RemoveObjectOptions{})
if err != nil {
log.Printf("删除文件失败: %v", err)
return
}
fmt.Printf("✅ 删除成功: %s\n", objectName)
}
// 3. 文件上传示例
func demonstrateFileUpload(client *MinIOClient) {
fmt.Println("\n=== 文件上传示例 ===")
ctx := context.Background()
// 创建测试文件
testFiles := map[string]string{
"documents/report.pdf": "这是 PDF 报告内容",
"images/avatar.jpg": "模拟图片数据",
"videos/demo.mp4": "模拟视频数据",
"backups/database.sql": "模拟数据库备份",
}
for filename, content := range testFiles {
// 上传文件
reader := strings.NewReader(content)
info, err := client.client.PutObject(ctx, client.bucket, filename, reader, int64(len(content)), minio.PutObjectOptions{
ContentType: getContentType(filename),
})
if err != nil {
log.Printf("上传文件失败 %s: %v", filename, err)
continue
}
fmt.Printf("✅ 上传成功: %s (大小: %d bytes)\n", filename, info.Size)
}
// 上传本地文件
fmt.Println("\n5. 上传本地文件:")
localFile := "minio_test.txt"
err := os.WriteFile(localFile, []byte("这是本地测试文件内容"), 0644)
if err != nil {
log.Printf("创建本地文件失败: %v", err)
return
}
defer os.Remove(localFile)
info, err := client.client.FPutObject(ctx, client.bucket, "uploads/local-file.txt", localFile, minio.PutObjectOptions{
ContentType: "text/plain",
})
if err != nil {
log.Printf("上传本地文件失败: %v", err)
return
}
fmt.Printf("✅ 上传本地文件成功: %s (大小: %d bytes)\n", "uploads/local-file.txt", info.Size)
}
// 4. 文件下载示例
func demonstrateFileDownload(client *MinIOClient) {
fmt.Println("\n=== 文件下载示例 ===")
ctx := context.Background()
// 下载到内存
fmt.Println("\n1. 下载到内存:")
object, err := client.client.GetObject(ctx, client.bucket, "documents/report.pdf", minio.GetObjectOptions{})
if err != nil {
log.Printf("获取文件失败: %v", err)
return
}
defer object.Close()
var content bytes.Buffer
if _, err := io.Copy(&content, object); err != nil {
log.Printf("读取文件失败: %v", err)
return
}
fmt.Printf("✅ 下载到内存成功,大小: %d bytes\n", content.Len())
// 下载到本地文件
fmt.Println("\n2. 下载到本地文件:")
localPath := "downloaded_report.pdf"
err = client.client.FGetObject(ctx, client.bucket, "documents/report.pdf", localPath, minio.GetObjectOptions{})
if err != nil {
log.Printf("下载文件失败: %v", err)
return
}
defer os.Remove(localPath)
fileInfo, err := os.Stat(localPath)
if err != nil {
log.Printf("获取文件信息失败: %v", err)
return
}
fmt.Printf("✅ 下载到本地成功: %s (大小: %d bytes)\n", localPath, fileInfo.Size())
// 断点续传下载
fmt.Println("\n3. 断点续传下载:")
object, err = client.client.GetObject(ctx, client.bucket, "videos/demo.mp4", minio.GetObjectOptions{})
if err != nil {
log.Printf("获取文件失败: %v", err)
return
}
defer object.Close()
// 模拟分块下载
chunkSize := int64(1024) // 1KB 分块
totalRead := int64(0)
for {
buffer := make([]byte, chunkSize)
n, err := object.Read(buffer)
if err != nil && err != io.EOF {
log.Printf("读取分块失败: %v", err)
break
}
totalRead += int64(n)
if err == io.EOF {
break
}
}
fmt.Printf("✅ 断点续传下载完成,总大小: %d bytes\n", totalRead)
}
// 5. 文件列表和搜索示例
func demonstrateFileListing(client *MinIOClient) {
fmt.Println("\n=== 文件列表和搜索 ===")
ctx := context.Background()
// 列出所有对象
fmt.Println("\n1. 列出所有对象:")
objects := client.client.ListObjects(ctx, client.bucket, minio.ListObjectsOptions{
Recursive: true,
})
count := 0
for object := range objects {
if object.Err != nil {
log.Printf("列出对象错误: %v", object.Err)
continue
}
fmt.Printf("📄 %s (大小: %d bytes, 修改时间: %s)\n",
object.Key, object.Size, object.LastModified.Format("2006-01-02 15:04:05"))
count++
}
fmt.Printf("✅ 共列出 %d 个对象\n", count)
// 按前缀搜索
fmt.Println("\n2. 按前缀搜索 (documents/):")
objects = client.client.ListObjects(ctx, client.bucket, minio.ListObjectsOptions{
Prefix: "documents/",
Recursive: true,
})
for object := range objects {
if object.Err != nil {
continue
}
fmt.Printf("📄 %s\n", object.Key)
}
// 分页列出
fmt.Println("\n3. 分页列出 (每页2个对象):")
objects = client.client.ListObjects(ctx, client.bucket, minio.ListObjectsOptions{
Recursive: true,
MaxKeys: 2,
})
pageCount := 0
for object := range objects {
if object.Err != nil {
continue
}
fmt.Printf("📄 %s\n", object.Key)
pageCount++
}
fmt.Printf("✅ 本页共 %d 个对象\n", pageCount)
}
// 6. 预签名 URL 示例
func demonstratePresignedURLs(client *MinIOClient) {
fmt.Println("\n=== 预签名 URL 示例 ===")
ctx := context.Background()
// 生成预签名上传 URL
fmt.Println("\n1. 生成预签名上传 URL:")
objectName := "shared/upload-file.txt"
uploadURL, err := client.client.PresignedPutObject(ctx, client.bucket, objectName, 15*time.Minute)
if err != nil {
log.Printf("生成上传 URL 失败: %v", err)
return
}
fmt.Printf("✅ 上传 URL (15分钟有效): %s\n", uploadURL.String())
// 使用预签名 URL 上传文件
content := "通过预签名 URL 上传的内容"
req, err := http.NewRequest("PUT", uploadURL.String(), strings.NewReader(content))
if err != nil {
log.Printf("创建上传请求失败: %v", err)
return
}
req.Header.Set("Content-Type", "text/plain")
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Printf("上传文件失败: %v", err)
return
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
fmt.Println("✅ 通过预签名 URL 上传成功")
}
// 生成预签名下载 URL
fmt.Println("\n2. 生成预签名下载 URL:")
downloadURL, err := client.client.PresignedGetObject(ctx, client.bucket, objectName, 30*time.Minute, nil)
if err != nil {
log.Printf("生成下载 URL 失败: %v", err)
return
}
fmt.Printf("✅ 下载 URL (30分钟有效): %s\n", downloadURL.String())
// 测试下载 URL
resp, err = http.Get(downloadURL.String())
if err != nil {
log.Printf("测试下载 URL 失败: %v", err)
return
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
fmt.Println("✅ 预签名下载 URL 测试成功")
}
// 生成带响应头的预签名 URL
fmt.Println("\n3. 带响应头的预签名 URL:")
reqParams := make(url.Values)
reqParams.Set("response-content-disposition", "attachment; filename=\"custom-name.txt\"")
downloadURLWithHeaders, err := client.client.PresignedGetObject(ctx, client.bucket, objectName, 30*time.Minute, reqParams)
if err != nil {
log.Printf("生成带响应头 URL 失败: %v", err)
return
}
fmt.Printf("✅ 带响应头下载 URL: %s\n", downloadURLWithHeaders.String())
}
// 7. 桶操作示例
func demonstrateBucketOperations(client *MinIOClient) {
fmt.Println("\n=== 桶操作示例 ===")
ctx := context.Background()
// 列出所有桶
fmt.Println("\n1. 列出所有桶:")
buckets, err := client.client.ListBuckets(ctx)
if err != nil {
log.Printf("列出桶失败: %v", err)
return
}
for _, bucket := range buckets {
fmt.Printf("📦 %s (创建时间: %s)\n",
bucket.Name, bucket.CreationDate.Format("2006-01-02 15:04:05"))
}
// 创建新桶
fmt.Println("\n2. 创建新桶:")
newBucket := "test-bucket-" + fmt.Sprintf("%d", time.Now().Unix())
err = client.client.MakeBucket(ctx, newBucket, minio.MakeBucketOptions{})
if err != nil {
log.Printf("创建桶失败: %v", err)
} else {
fmt.Printf("✅ 创建桶成功: %s\n", newBucket)
}
// 检查桶是否存在
fmt.Println("\n3. 检查桶是否存在:")
exists, err := client.client.BucketExists(ctx, newBucket)
if err != nil {
log.Printf("检查桶失败: %v", err)
} else {
fmt.Printf("✅ 桶 %s 存在: %t\n", newBucket, exists)
}
// 删除桶(需要先清空桶内对象)
fmt.Println("\n4. 删除桶:")
// 先清空桶
objectsCh := make(chan minio.ObjectInfo)
go func() {
defer close(objectsCh)
objects := client.client.ListObjects(ctx, newBucket, minio.ListObjectsOptions{
Recursive: true,
})
for object := range objects {
if object.Err != nil {
continue
}
objectsCh <- object
}
}()
for rErr := range client.client.RemoveObjects(ctx, newBucket, objectsCh, minio.RemoveObjectsOptions{}) {
log.Printf("删除对象错误: %v", rErr.Err)
}
// 然后删除桶
err = client.client.RemoveBucket(ctx, newBucket)
if err != nil {
log.Printf("删除桶失败: %v", err)
} else {
fmt.Printf("✅ 删除桶成功: %s\n", newBucket)
}
}
// 8. 文件操作示例
func demonstrateFileOperations(client *MinIOClient) {
fmt.Println("\n=== 文件操作示例 ===")
ctx := context.Background()
// 复制对象
fmt.Println("\n1. 复制对象:")
source := minio.CopySrcOptions{
Bucket: client.bucket,
Object: "documents/report.pdf",
}
destination := minio.CopyDestOptions{
Bucket: client.bucket,
Object: "backups/report-backup.pdf",
}
_, err := client.client.CopyObject(ctx, destination, source)
if err != nil {
log.Printf("复制对象失败: %v", err)
} else {
fmt.Printf("✅ 复制成功: %s -> %s\n", source.Object, destination.Object)
}
// 获取对象信息
fmt.Println("\n2. 获取对象信息:")
info, err := client.client.StatObject(ctx, client.bucket, "documents/report.pdf", minio.StatObjectOptions{})
if err != nil {
log.Printf("获取对象信息失败: %v", err)
} else {
fmt.Printf("📊 对象信息:\n")
fmt.Printf(" 名称: %s\n", info.Key)
fmt.Printf(" 大小: %d bytes\n", info.Size)
fmt.Printf(" 修改时间: %s\n", info.LastModified.Format("2006-01-02 15:04:05"))
fmt.Printf(" 类型: %s\n", info.ContentType)
fmt.Printf(" ETag: %s\n", info.ETag)
}
// 批量删除对象
fmt.Println("\n3. 批量删除对象:")
objectsToRemove := []string{
"test-files/hello.txt",
"uploads/local-file.txt",
"shared/upload-file.txt",
}
for _, obj := range objectsToRemove {
err = client.client.RemoveObject(ctx, client.bucket, obj, minio.RemoveObjectOptions{})
if err != nil {
log.Printf("删除对象失败 %s: %v", obj, err)
} else {
fmt.Printf("✅ 删除成功: %s\n", obj)
}
}
}
// 9. 监听桶事件示例
func demonstrateBucketNotifications(client *MinIOClient) {
fmt.Println("\n=== 桶事件监听示例 ===")
ctx := context.Background()
// 设置桶事件通知(需要 MinIO 服务器配置)
fmt.Println("📢 桶事件监听需要 MinIO 服务器配置,这里演示概念")
// 监听对象创建事件
fmt.Println("1. 对象创建事件监听:")
fmt.Println(" 当有新文件上传时会触发事件")
// 监听对象删除事件
fmt.Println("2. 对象删除事件监听:")
fmt.Println(" 当有文件被删除时会触发事件")
// 监听对象访问事件
fmt.Println("3. 对象访问事件监听:")
fmt.Println(" 当文件被访问时会触发事件")
fmt.Println("💡 实际使用时需要配置 MinIO 事件目标(如 Webhook、消息队列等)")
}
// 10. 性能测试示例
func demonstratePerformance(client *MinIOClient) {
fmt.Println("\n=== 性能测试示例 ===")
ctx := context.Background()
// 测试大文件上传性能
fmt.Println("\n1. 大文件上传测试:")
largeContent := strings.Repeat("MinIO Performance Test Data\n", 100000) // ~2.6MB
objectName := "performance/large-file.txt"
start := time.Now()
reader := strings.NewReader(largeContent)
info, err := client.client.PutObject(ctx, client.bucket, objectName, reader, int64(len(largeContent)), minio.PutObjectOptions{})
if err != nil {
log.Printf("大文件上传失败: %v", err)
return
}
duration := time.Since(start)
speed := float64(info.Size) / duration.Seconds() / 1024 / 1024 // MB/s
fmt.Printf("✅ 大文件上传完成:\n")
fmt.Printf(" 文件大小: %.2f MB\n", float64(info.Size)/1024/1024)
fmt.Printf(" 上传时间: %v\n", duration)
fmt.Printf(" 上传速度: %.2f MB/s\n", speed)
// 测试并发上传
fmt.Println("\n2. 并发上传测试:")
const concurrentUploads = 5
var wg sync.WaitGroup
wg.Add(concurrentUploads)
start = time.Now()
for i := 0; i < concurrentUploads; i++ {
go func(index int) {
defer wg.Done()
content := fmt.Sprintf("并发测试文件 %d", index)
objectName := fmt.Sprintf("concurrent/test-%d.txt", index)
reader := strings.NewReader(content)
_, err := client.client.PutObject(ctx, client.bucket, objectName, reader, int64(len(content)), minio.PutObjectOptions{})
if err != nil {
log.Printf("并发上传失败 %d: %v", index, err)
}
}(i)
}
wg.Wait()
concurrentDuration := time.Since(start)
fmt.Printf("✅ 并发上传完成:\n")
fmt.Printf(" 并发数: %d\n", concurrentUploads)
fmt.Printf(" 总时间: %v\n", concurrentDuration)
fmt.Printf(" 平均时间: %v\n", concurrentDuration/time.Duration(concurrentUploads))
// 清理测试文件
client.client.RemoveObject(ctx, client.bucket, objectName, minio.RemoveObjectOptions{})
for i := 0; i < concurrentUploads; i++ {
client.client.RemoveObject(ctx, client.bucket, fmt.Sprintf("concurrent/test-%d.txt", i), minio.RemoveObjectOptions{})
}
}
// 辅助函数
func getContentType(filename string) string {
ext := strings.ToLower(filepath.Ext(filename))
switch ext {
case ".txt":
return "text/plain"
case ".pdf":
return "application/pdf"
case ".jpg", ".jpeg":
return "image/jpeg"
case ".png":
return "image/png"
case ".mp4":
return "video/mp4"
case ".sql":
return "application/sql"
default:
return "application/octet-stream"
}
}
func calculateMD5(content string) string {
hash := md5.Sum([]byte(content))
return fmt.Sprintf("%x", hash)
}
func main() {
// MinIO 连接配置
endpoint := "localhost:9000"
accessKey := "minioadmin"
secretKey := "minioadmin"
bucket := "go-minio-demo"
useSSL := false
// 从环境变量读取配置
if os.Getenv("MINIO_ENDPOINT") != "" {
endpoint = os.Getenv("MINIO_ENDPOINT")
}
if os.Getenv("MINIO_ACCESS_KEY") != "" {
accessKey = os.Getenv("MINIO_ACCESS_KEY")
}
if os.Getenv("MINIO_SECRET_KEY") != "" {
secretKey = os.Getenv("MINIO_SECRET_KEY")
}
if os.Getenv("MINIO_USE_SSL") == "true" {
useSSL = true
}
fmt.Printf("🚀 连接 MinIO: %s (SSL: %t)\n", endpoint, useSSL)
// 创建 MinIO 客户端
client, err := NewMinIOClient(endpoint, accessKey, secretKey, bucket, useSSL)
if err != nil {
log.Fatalf("❌ 初始化 MinIO 客户端失败: %v", err)
}
defer client.Close()
fmt.Printf("✅ 成功连接到 MinIO,使用桶: %s\n", bucket)
// 运行各种示例
demonstrateBasicOperations(client)
demonstrateFileUpload(client)
demonstrateFileDownload(client)
demonstrateFileListing(client)
demonstratePresignedURLs(client)
demonstrateBucketOperations(client)
demonstrateFileOperations(client)
demonstrateBucketNotifications(client)
demonstratePerformance(client)
fmt.Println("\n🎉 所有 MinIO 示例执行完成!")
}
MinIO 关键特性
- 核心概念
桶(Bucket):类似于文件夹的容器
对象(Object):存储的文件和数据
预签名 URL:临时访问链接
策略(Policy):访问控制规则
- 主要操作
上传 :PutObject(), FPutObject()
下载 :GetObject(), FGetObject()
列表 :ListObjects()
删除 :RemoveObject()
复制 :CopyObject()
- 高级功能
预签名 URL:生成临时访问链接
分块上传 :支持大文件分块上传
事件通知 :监听桶事件
生命周期 :自动管理对象生命周期
- 最佳实践
连接池管理 :重用客户端连接
错误处理 :正确处理网络和权限错误
超时设置 :设置合理的上下文超时
安全配置 :使用最小权限原则
性能优化 :使用并发上传大文件
- 适用场景
文件存储 :用户上传的图片、文档
备份归档 :数据库备份、日志归档
静态资源 :网站静态文件、CDN 源站
大数据 :存储分析用的原始数据
云原生应用 :容器化应用的文件存储
MinIO 提供了企业级的对象存储功能,同时保持了简单易用的特点,是自建云存储的理想选择。
通关密语:minio