package store import ( "bytes" "context" "errors" "github.com/minio/minio-go" "io" "io/ioutil" "net/http" "net/url" "strings" "sync" ) var once sync.Once // ErrorInvalidName 无效的文件名 var ErrorInvalidName = errors.New("invalid file name") // MinioStore minio 对象存储 type MinioStore struct { cli *minio.Client } // ComposeObject 通过使用服务端拷贝实现钭多个源对象合并创建成一个新的对象。 func (m *MinioStore) ComposeObject(ctx context.Context,pathS []string,filePath string) error { bucketName,fileName,err := m.parseFilename(filePath) if err != nil{ return err } dst, err := minio.NewDestinationInfo(bucketName, fileName, nil, nil) if err != nil { return err } srcs := make([]minio.SourceInfo,0,len(pathS)) for i:=range pathS{ bucketName,fileName,err := m.parseFilename(pathS[i]) if err != nil{ return err } srcs = append(srcs,minio.NewSourceInfo(bucketName, fileName, nil)) } return m.cli.ComposeObject(dst, srcs) } //RemoveObject 删除minio中的文件 func (m *MinioStore) RemoveObject(ctx context.Context,filePath string) error { bucketName,fileName,err := m.parseFilename(filePath) if err != nil{ return err } return m.cli.RemoveObject(bucketName, fileName) } func (m *MinioStore) Delete(ctx context.Context, fileName string) error { b, o, err := m.parseFilename(fileName) if err != nil { return err } n, err := url.PathUnescape(o) if err != nil { return err } return m.cli.RemoveObject(b, n) } // Stat 文件状态信息 func (m *MinioStore) Stat(filename string) (minio.ObjectInfo, error) { bucketName, objectName, err := m.parseFilename(filename) if err != nil { return minio.ObjectInfo{}, err } return m.cli.StatObject(bucketName, objectName, minio.StatObjectOptions{}) } // Get get file buffers func (m *MinioStore) Get(ctx context.Context, fileName string) ([]byte, string, error) { if ctx == nil { ctx = context.Background() } stat, err := m.Stat(fileName) if err != nil { return nil, "", errors.New("文件不存在:" + fileName) } bucketName, objectName, err := m.parseFilename(fileName) if err != nil { return nil, "", err } obj, err := m.cli.GetObjectWithContext(ctx, bucketName, objectName, minio.GetObjectOptions{}) if err != nil { return nil, "", err } buf, err := ioutil.ReadAll(obj) if err != nil { return nil, "", err } return buf, stat.ContentType, nil } // Store save file func (m *MinioStore) Store(ctx context.Context, filename string, data io.Reader, size int64) (fileHash string, err error) { if ctx == nil { ctx = context.Background() } bucket, objName, err := m.parseFilename(filename) if err != nil { return } exists, err := m.cli.BucketExists(bucket) if err != nil { return } else if !exists { err = m.cli.MakeBucket(bucket, "local") if err != nil { return } } buf, err := ioutil.ReadAll(data) if err != nil { return } rd := bytes.NewBuffer(buf) if size == 0 { size = int64(rd.Len()) } _, err = m.cli.PutObjectWithContext(ctx, bucket, objName, rd, size, minio.PutObjectOptions{ ContentType: http.DetectContentType(buf), NumThreads: 2, }) stat, err := m.Stat(filename) if err != nil { return } fileHash = stat.ETag return } // 解析文件名 `prefix/bucket/uuid/filename` func (m *MinioStore) parseFilename(filename string) (string, string, error) { if len(filename) > 0 && filename[0] == '/' { filename = filename[1:] } names := strings.Split(filename, "/") if len(names) < 3 { return "", "", ErrorInvalidName } return strings.ToLower(names[1]), strings.Join(names[2:], "/"), nil } // MiniStoreInit minio init func MiniStoreInit(addr, accessKey, secretKey string) *MinioStore { ms := new(MinioStore) once.Do(func() { cli, err := minio.New(addr, accessKey, secretKey, false) if err != nil { panic(err) } ms.cli = cli }) return ms }