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 }