1.环境准备

MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。

2.部署minio

macos推荐使用brew进行安装

1brew install minio

其他操作系统可以去minio官网上进行下载

运行

1minio server ~/Downloads # ~/Downloads为指定的工作目录

运行之后会告诉你endpoint,accesskey,secretkey信息以及相关sdk信息

使用浏览器可以进行对象存储的可视化管理

3.创建go客户端

1.新建配置文件

app.yaml

1endpoint: http://127.0.0.1:9000
2bucket: images
3accessKey: minioadmin
4secretKey: minioadmin
5pathstyle: true

2.创建对应的结构体

1var s3cfg S3cfg
2
3type S3cfg struct {
4  Endpoint  string `yaml:"endpoint"`
5  Bucket    string `yaml:"bucket"`
6  AccessKey string `yaml:"accesskey"`
7  SecretKey string `yaml:"secretkey"`
8  PathStyle bool   `yaml:"pathstyle"`
9}

3.从配置文件读取配置

这里推荐使用viper

1  viper.AddConfigPath("./")
2  viper.SetConfigName("app")
3  viper.SetConfigType("yaml")
4  if err := viper.ReadInConfig(); err != nil {
5    log.Fatalf("read config file failed, %v", err)
6  }
7  if err := viper.Unmarshal(&s3cfg); err != nil {
8    log.Fatalf("unmarshal config file failed, %v", err)
9  }

4.创建s3客户端

建议将client单独拿出来,方便调用

 1func newS3Client() *s3.S3 {
 2  creds := credentials.NewStaticCredentials(s3cfg.AccessKey, s3cfg.SecretKey, "")
 3  sess := session.Must(session.NewSession(&aws.Config{
 4    Region:           aws.String("default"),
 5    Endpoint:         &s3cfg.Endpoint,
 6    S3ForcePathStyle: &s3cfg.PathStyle, // 因为使用的IP:Port/bucket的形式,使用path风格
 7    Credentials:      creds,
 8  }))
 9  svc := s3.New(sess)
10  return svc
11}

5.创建上传接口函数

 1func uploadS3(bucket, fileName string, body []byte) error {
 2  client := newS3Client()
 3  _, err := client.PutObjectWithContext(context.TODO(), &s3.PutObjectInput{
 4    Bucket: aws.String(bucket),
 5    Key:    aws.String(fileName),
 6    Body:   bytes.NewReader(body),
 7  })
 8  if err != nil {
 9    if aerr, ok := err.(awserr.Error); ok && aerr.Code() == request.CanceledErrorCode {
10      // If the SDK can determine the request or retry delay was canceled
11      // by a context the CanceledErrorCode error code will be returned.
12      return fmt.Errorf("upload canceled due to timeout, %v", err)
13    }
14    return fmt.Errorf("failed to upload object, %v", err)
15  }
16  return nil
17}

6.创建gin web并调用上传接口

 1func main() {
 2  r := gin.Default()
 3
 4  r.POST("/upload", upload)
 5
 6  r.Run(":8080")
 7}
 8
 9func upload(ctx *gin.Context) {
10  file, err := ctx.FormFile("file")
11  if err != nil {
12    ctx.JSON(http.StatusOK, gin.H{
13      "code": 500,
14      "msg":  "没有指定上传文件",
15    })
16    return
17  }
18
19  f, _ := file.Open()
20  buf := make([]byte, file.Size)
21  f.Read(buf)
22
23  // bfRd := bufio.NewReader(f)
24  // 上传对象
25  err = uploadS3(s3cfg.Bucket, file.Filename, buf)
26  if err != nil {
27    ctx.JSON(http.StatusOK, gin.H{
28      "code": "500",
29      "msg":  "上传失败" + err.Error(),
30    })
31    return
32  }
33  ctx.JSON(http.StatusOK, gin.H{
34    "code": 200,
35    "msg":  "文件上传成功",
36  })
37}

4.启动测试

1go run .

postman上传图片

查看minio browser

minio-browser

5.附完整代码

当上传的文件比较大时可以采取分段上传,具体可以参考官方的sdkaws-sdk-go

  1package main
  2
  3/**
  4* s3上传图片
  5 */
  6
  7import (
  8  "bytes"
  9  "context"
 10  "fmt"
 11  "log"
 12  "net/http"
 13
 14  "github.com/aws/aws-sdk-go/aws"
 15  "github.com/aws/aws-sdk-go/aws/awserr"
 16  "github.com/aws/aws-sdk-go/aws/credentials"
 17  "github.com/aws/aws-sdk-go/aws/request"
 18  "github.com/aws/aws-sdk-go/aws/session"
 19  "github.com/aws/aws-sdk-go/service/s3"
 20  "github.com/gin-gonic/gin"
 21  "github.com/spf13/viper"
 22)
 23
 24var s3cfg S3cfg
 25
 26type S3cfg struct {
 27  Endpoint  string `yaml:"endpoint"`
 28  Bucket    string `yaml:"bucket"`
 29  AccessKey string `yaml:"accesskey"`
 30  SecretKey string `yaml:"secretkey"`
 31  PathStyle bool   `yaml:"pathstyle"`
 32}
 33
 34func init() {
 35  viper.AddConfigPath("./")
 36  viper.SetConfigName("app")
 37  viper.SetConfigType("yaml")
 38  if err := viper.ReadInConfig(); err != nil {
 39    log.Fatalf("read config file failed, %v", err)
 40  }
 41  if err := viper.Unmarshal(&s3cfg); err != nil {
 42    log.Fatalf("unmarshal config file failed, %v", err)
 43  }
 44}
 45
 46func main() {
 47  r := gin.Default()
 48
 49  r.POST("/upload", upload)
 50
 51  r.Run(":8080")
 52}
 53
 54func upload(ctx *gin.Context) {
 55  file, err := ctx.FormFile("file")
 56  if err != nil {
 57    ctx.JSON(http.StatusOK, gin.H{
 58      "code": 500,
 59      "msg":  "没有指定上传文件",
 60    })
 61    return
 62  }
 63
 64  f, _ := file.Open()
 65  buf := make([]byte, file.Size)
 66  f.Read(buf)
 67
 68  // bfRd := bufio.NewReader(f)
 69  // 上传对象
 70  err = uploadS3(s3cfg.Bucket, file.Filename, buf)
 71  if err != nil {
 72    ctx.JSON(http.StatusOK, gin.H{
 73      "code": "500",
 74      "msg":  "上传失败" + err.Error(),
 75    })
 76    return
 77  }
 78  ctx.JSON(http.StatusOK, gin.H{
 79    "code": 200,
 80    "msg":  "文件上传成功",
 81  })
 82}
 83
 84func newS3Client() *s3.S3 {
 85  creds := credentials.NewStaticCredentials(s3cfg.AccessKey, s3cfg.SecretKey, "")
 86  sess := session.Must(session.NewSession(&aws.Config{
 87    Region:           aws.String("default"),
 88    Endpoint:         &s3cfg.Endpoint,
 89    S3ForcePathStyle: &s3cfg.PathStyle,
 90    Credentials:      creds,
 91  }))
 92  svc := s3.New(sess)
 93  return svc
 94}
 95
 96func uploadS3(bucket, fileName string, body []byte) error {
 97  client := newS3Client()
 98  _, err := client.PutObjectWithContext(context.TODO(), &s3.PutObjectInput{
 99    Bucket: aws.String(bucket),
100    Key:    aws.String(fileName),
101    Body:   bytes.NewReader(body),
102  })
103  if err != nil {
104    if aerr, ok := err.(awserr.Error); ok && aerr.Code() == request.CanceledErrorCode {
105      // If the SDK can determine the request or retry delay was canceled
106      // by a context the CanceledErrorCode error code will be returned.
107      return fmt.Errorf("upload canceled due to timeout, %v", err)
108    }
109    return fmt.Errorf("failed to upload object, %v", err)
110  }
111  return nil
112}