github.com/GuanceCloud/cliutils@v1.1.21/oss.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  package cliutils
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"time"
    13  
    14  	"github.com/aliyun/aliyun-oss-go-sdk/oss"
    15  )
    16  
    17  var (
    18  	ErrOssFileTooLarge = errors.New(`oss file too large`)
    19  
    20  	DefaultPartSize = int64(32 * 1024 * 1024) //nolint:gomnd // 32MB
    21  	DefaultTimeout  = uint(30)                //nolint:gomnd // seconds
    22  	DefaultWorkers  = 8
    23  )
    24  
    25  const (
    26  	ossMaxParts = 10000
    27  )
    28  
    29  type OssCli struct {
    30  	Host, AccessKey, SecretKey, BucketName, WorkDir string
    31  	Timeout                                         uint
    32  	PartSize                                        int64
    33  	Workers                                         int
    34  
    35  	bkt *oss.Bucket
    36  
    37  	ReconnectCnt  int
    38  	FailedCnt     int
    39  	UploadedFiles int
    40  	UploadedBytes int64
    41  }
    42  
    43  func (oc *OssCli) getBucket() error {
    44  	cli, err := oss.New(oc.Host, oc.AccessKey, oc.SecretKey)
    45  	if err != nil {
    46  		return err
    47  	}
    48  
    49  	cli.Config.Timeout = oc.Timeout
    50  
    51  	cli.Config.HTTPTimeout.ConnectTimeout = time.Second * 3
    52  	cli.Config.HTTPTimeout.HeaderTimeout = time.Duration(oc.Timeout) * time.Second
    53  	cli.Config.HTTPTimeout.ReadWriteTimeout = time.Duration(oc.Timeout) * time.Second
    54  	cli.Config.HTTPTimeout.LongTimeout = time.Duration(oc.Timeout) * time.Second
    55  
    56  	bkt, err := cli.Bucket(oc.BucketName)
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	oc.bkt = bkt
    62  	return nil
    63  }
    64  
    65  func (oc *OssCli) Init() error {
    66  	if oc.PartSize == 0 {
    67  		oc.PartSize = DefaultPartSize
    68  	}
    69  
    70  	if oc.Timeout == 0 {
    71  		oc.Timeout = DefaultTimeout
    72  	}
    73  
    74  	if oc.Workers == 0 {
    75  		oc.Workers = DefaultWorkers
    76  	}
    77  
    78  	if err := oc.getBucket(); err != nil {
    79  		return err
    80  	}
    81  
    82  	oc.ReconnectCnt = 0
    83  	oc.FailedCnt = 0
    84  	oc.UploadedFiles = 0
    85  	oc.UploadedBytes = int64(0)
    86  
    87  	return nil
    88  }
    89  
    90  func (oc *OssCli) Reconnect() error {
    91  	if err := oc.getBucket(); err != nil {
    92  		return err
    93  	}
    94  
    95  	oc.ReconnectCnt++
    96  	return nil
    97  }
    98  
    99  func (oc *OssCli) Stat() string {
   100  	return fmt.Sprintf("uploaded %d files, total %s, reconnect: %d, FailedCnt: %d",
   101  		oc.UploadedFiles, SizeFmt(oc.UploadedBytes), oc.ReconnectCnt, oc.FailedCnt)
   102  }
   103  
   104  func (oc *OssCli) Upload(from, to string) error {
   105  	var err error
   106  
   107  	st, err := os.Stat(from)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	if size := st.Size(); size <= oc.PartSize { // 小文件直接上传
   113  		if err := oc.bkt.PutObjectFromFile(to, from); err != nil {
   114  			oc.FailedCnt++
   115  			return err
   116  		}
   117  
   118  		oc.UploadedFiles++
   119  		oc.UploadedBytes += size
   120  
   121  		return nil
   122  	}
   123  
   124  	return oc.multipartUpload(from, to)
   125  }
   126  
   127  func (oc *OssCli) SetMeta(obj string, meta map[string]string) error {
   128  	options := []oss.Option{}
   129  	for k, v := range meta {
   130  		options = append(options, oss.Meta(k, v))
   131  	}
   132  
   133  	return oc.bkt.SetObjectMeta(obj, options...)
   134  }
   135  
   136  func (oc *OssCli) GetMeta(obj string) (map[string][]string, error) {
   137  	return oc.bkt.GetObjectDetailedMeta(obj)
   138  }
   139  
   140  func (oc *OssCli) ListObjects(prefix, marker string, maxKeys int) (oss.ListObjectsResult, error) {
   141  	res, err := oc.bkt.ListObjects(oss.Prefix(prefix), oss.Marker(marker), oss.MaxKeys(maxKeys))
   142  	return res, err
   143  }
   144  
   145  func (oc *OssCli) Move(from, to string) error {
   146  	if _, err := oc.bkt.CopyObject(from, to); err != nil {
   147  		return err
   148  	}
   149  
   150  	return oc.bkt.DeleteObject(from)
   151  }
   152  
   153  func (oc *OssCli) mpworker(imur *oss.InitiateMultipartUploadResult,
   154  	c *oss.FileChunk, from string, exit chan interface{},
   155  ) (p oss.UploadPart, err error) {
   156  	select {
   157  	case <-exit:
   158  		return
   159  
   160  	default:
   161  
   162  		for i := 0; i < 3; i++ {
   163  			p, err = oc.bkt.UploadPartFromFile(*imur, from, c.Offset, c.Size, c.Number)
   164  			if err == nil {
   165  				return p, nil
   166  			}
   167  			time.Sleep(time.Second)
   168  		}
   169  		return
   170  	}
   171  }
   172  
   173  func (oc *OssCli) multipartUpload(from, to string) error {
   174  	st, err := os.Stat(from)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	size := st.Size()
   180  
   181  	if size > oc.PartSize*ossMaxParts { // 最大只支持 10k 个分片
   182  		return ErrOssFileTooLarge
   183  	}
   184  
   185  	partCnt := size / oc.PartSize
   186  
   187  	chunks, err := oss.SplitFileByPartNum(from, int(partCnt))
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	// 新建分片上传
   193  	imur, err := oc.bkt.InitiateMultipartUpload(to, nil)
   194  	if err != nil {
   195  		return err
   196  	}
   197  
   198  	resCh := make(chan *oss.UploadPart, len(chunks)) // 接受返回结果
   199  	failedCh := make(chan error)
   200  	exit := make(chan interface{}) // 勒令退出
   201  
   202  	defer close(exit)
   203  
   204  	// 启动上传任务
   205  	parts := []oss.UploadPart{}
   206  	nrWorkers := 0
   207  	idx := 0
   208  	for {
   209  		select {
   210  		case p := <-resCh:
   211  			parts = append(parts, *p)
   212  			nrWorkers--
   213  
   214  		case err = <-failedCh:
   215  			// ignore abort error
   216  			_ = oc.bkt.AbortMultipartUpload(imur)
   217  			return err
   218  
   219  		default:
   220  			time.Sleep(time.Second)
   221  		}
   222  
   223  		if len(parts) == len(chunks) { // 所有分片全部完成
   224  			break
   225  		}
   226  		if nrWorkers >= oc.Workers || idx >= len(chunks) { // 控制并发个数
   227  			continue
   228  		}
   229  
   230  		// 每个分片都用一个新的 goroutine 上传
   231  		go func() {
   232  			if p, err := oc.mpworker(&imur, &chunks[idx], from, exit); err != nil { //nolint:govet
   233  				failedCh <- err
   234  			} else {
   235  				resCh <- &p
   236  			}
   237  		}()
   238  
   239  		idx++
   240  		nrWorkers++
   241  	}
   242  
   243  	_, err = oc.bkt.CompleteMultipartUpload(imur, parts)
   244  	if err != nil {
   245  		_ = oc.bkt.AbortMultipartUpload(imur)
   246  
   247  		oc.FailedCnt++
   248  		return err
   249  	}
   250  
   251  	oc.UploadedFiles++
   252  	oc.UploadedBytes += size
   253  	return nil
   254  }
   255  
   256  func (oc *OssCli) Download(obj, to string) error {
   257  	return oc.bkt.GetObjectToFile(obj, to)
   258  }