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 关键特性

  1. ​ 核心概念 ​

​ 桶(Bucket)​​:类似于文件夹的容器

​ 对象(Object)​​:存储的文件和数据

​ 预签名 URL​:临时访问链接

​ 策略(Policy)​​:访问控制规则

  1. ​ 主要操作 ​

​ 上传 ​:PutObject(), FPutObject()

​ 下载 ​:GetObject(), FGetObject()

​ 列表 ​:ListObjects()

​ 删除 ​:RemoveObject()

​ 复制 ​:CopyObject()

  1. ​ 高级功能 ​

​ 预签名 URL​:生成临时访问链接

​ 分块上传 ​:支持大文件分块上传

​ 事件通知 ​:监听桶事件

​ 生命周期 ​:自动管理对象生命周期

  1. ​ 最佳实践 ​

​ 连接池管理 ​:重用客户端连接

​ 错误处理 ​:正确处理网络和权限错误

​ 超时设置 ​:设置合理的上下文超时

​ 安全配置 ​:使用最小权限原则

​ 性能优化 ​:使用并发上传大文件

  1. ​ 适用场景 ​

​ 文件存储 ​:用户上传的图片、文档

​ 备份归档 ​:数据库备份、日志归档

​ 静态资源 ​:网站静态文件、CDN 源站

​ 大数据 ​:存储分析用的原始数据

​ 云原生应用 ​:容器化应用的文件存储

MinIO 提供了企业级的对象存储功能,同时保持了简单易用的特点,是自建云存储的理想选择。

通关密语:minio