github.com/qiniu/go-sdk/v7@v7.27.0/storage/form_upload.go (about) 1 package storage 2 3 import ( 4 "context" 5 "io" 6 "path" 7 "path/filepath" 8 "strings" 9 "time" 10 11 "github.com/qiniu/go-sdk/v7/client" 12 "github.com/qiniu/go-sdk/v7/internal/clientv2" 13 "github.com/qiniu/go-sdk/v7/storagev2/http_client" 14 "github.com/qiniu/go-sdk/v7/storagev2/region" 15 "github.com/qiniu/go-sdk/v7/storagev2/uploader" 16 "github.com/qiniu/go-sdk/v7/storagev2/uptoken" 17 ) 18 19 // PutExtra 为表单上传的额外可选项 20 type PutExtra struct { 21 // 可选。 22 // 用户自定义参数:key 以"x:"开头,而且 value 不能为空 eg: key为x:qqq 23 // 自定义 meta:key 以"x-qn-meta-"开头,而且 value 不能为空 eg: key为x-qn-meta-aaa 24 Params map[string]string 25 26 UpHost string 27 28 TryTimes int // 可选。尝试次数 29 30 // 主备域名冻结时间(默认:600s),当一个域名请求失败(单个域名会被重试 TryTimes 次),会被冻结一段时间,使用备用域名进行重试,在冻结时间内,域名不能被使用,当一个操作中所有域名竣备冻结操作不在进行重试,返回最后一次操作的错误。 31 HostFreezeDuration time.Duration 32 33 // 可选,当为 "" 时候,服务端自动判断。 34 MimeType string 35 36 // 上传事件:进度通知。这个事件的回调函数应该尽可能快地结束。 37 OnProgress func(fsize, uploaded int64) 38 } 39 40 func (extra *PutExtra) init() { 41 if extra.TryTimes == 0 { 42 extra.TryTimes = settings.TryTimes 43 } 44 if extra.HostFreezeDuration <= 0 { 45 extra.HostFreezeDuration = 10 * 60 * time.Second 46 } 47 } 48 49 // PutRet 为七牛标准的上传回复内容。 50 // 如果使用了上传回调或者自定义了returnBody,那么需要根据实际情况,自己自定义一个返回值结构体 51 type PutRet struct { 52 Hash string `json:"hash"` 53 PersistentID string `json:"persistentId"` 54 Key string `json:"key"` 55 } 56 57 // FormUploader 表示一个表单上传的对象 58 type FormUploader struct { 59 // Deprecated 60 Client *client.Client 61 // Deprecated 62 Cfg *Config 63 uploader uploader.Uploader 64 } 65 66 // NewFormUploader 用来构建一个表单上传的对象 67 func NewFormUploader(cfg *Config) *FormUploader { 68 return NewFormUploaderEx(cfg, nil) 69 } 70 71 // NewFormUploaderEx 用来构建一个表单上传的对象 72 func NewFormUploaderEx(cfg *Config, clt *client.Client) *FormUploader { 73 if cfg == nil { 74 cfg = NewConfig() 75 } 76 77 if clt == nil { 78 clt = &client.DefaultClient 79 } 80 81 bucketQuery, _ := region.NewBucketRegionsQuery(getUcEndpoint(cfg.UseHTTPS, nil), ®ion.BucketRegionsQueryOptions{ 82 UseInsecureProtocol: !cfg.UseHTTPS, 83 Client: clt.Client, 84 }) 85 86 opts := http_client.Options{ 87 BasicHTTPClient: clt.Client, 88 UseInsecureProtocol: !cfg.UseHTTPS, 89 BucketQuery: bucketQuery, 90 AccelerateUploading: cfg.AccelerateUploading, 91 HostRetryConfig: &clientv2.RetryConfig{}, 92 } 93 if region := cfg.GetRegion(); region != nil { 94 opts.Regions = region 95 } 96 97 return &FormUploader{ 98 Client: clt, 99 Cfg: cfg, 100 uploader: uploader.NewFormUploader(&uploader.FormUploaderOptions{Options: opts}), 101 } 102 } 103 104 // PutFile 用来以表单方式上传一个文件,和 Put 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.Reader 来访问。 105 // 106 // ctx 是请求的上下文。 107 // ret 是上传成功后返回的数据。如果 uptoken 中没有设置 callbackUrl 或 returnBody,那么返回的数据结构是 PutRet 结构。 108 // uptoken 是由业务服务器颁发的上传凭证。 109 // key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。 110 // localFile 是要上传的文件的本地路径。 111 // extra 是上传的一些可选项,可以指定为nil。详细见 PutExtra 结构的描述。 112 func (p *FormUploader) PutFile( 113 ctx context.Context, ret interface{}, uptoken, key, localFile string, extra *PutExtra, 114 ) (err error) { 115 return p.putFile(ctx, ret, uptoken, key, true, localFile, extra) 116 } 117 118 // PutFileWithoutKey 用来以表单方式上传一个文件。不指定文件上传后保存的key的情况下,文件命名方式首先看看 119 // uptoken 中是否设置了 saveKey,如果设置了 saveKey,那么按 saveKey 要求的规则生成 key,否则自动以文件的 hash 做 key。 120 // 和 Put 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.Reader 来访问。 121 // 122 // ctx 是请求的上下文。 123 // ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。 124 // uptoken 是由业务服务器颁发的上传凭证。 125 // localFile 是要上传的文件的本地路径。 126 // extra 是上传的一些可选项。可以指定为nil。详细见 PutExtra 结构的描述。 127 func (p *FormUploader) PutFileWithoutKey( 128 ctx context.Context, ret interface{}, uptoken, localFile string, extra *PutExtra, 129 ) (err error) { 130 return p.putFile(ctx, ret, uptoken, "", false, localFile, extra) 131 } 132 133 func (p *FormUploader) putFile( 134 ctx context.Context, ret interface{}, upToken string, 135 key string, hasKey bool, localFile string, extra *PutExtra, 136 ) error { 137 if extra == nil { 138 extra = &PutExtra{} 139 } 140 extra.init() 141 142 objectParams := p.newObjectParams(upToken, key, hasKey, extra) 143 objectParams.FileName = filepath.Base(localFile) 144 return p.uploader.UploadFile(ctx, localFile, objectParams, ret) 145 } 146 147 // Put 用来以表单方式上传一个文件。 148 // 149 // ctx 是请求的上下文。 150 // ret 是上传成功后返回的数据。如果 uptoken 中没有设置 callbackUrl 或 returnBody,那么返回的数据结构是 PutRet 结构。 151 // uptoken 是由业务服务器颁发的上传凭证。 152 // key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。 153 // data 是文件内容的访问接口(io.Reader)。 154 // fsize 是要上传的文件大小。 155 // extra 是上传的一些可选项。可以指定为nil。详细见 PutExtra 结构的描述。 156 func (p *FormUploader) Put( 157 ctx context.Context, ret interface{}, uptoken, key string, data io.Reader, size int64, extra *PutExtra, 158 ) (err error) { 159 err = p.put(ctx, ret, uptoken, key, true, data, size, extra, path.Base(key)) 160 return 161 } 162 163 // PutWithoutKey 用来以表单方式上传一个文件。不指定文件上传后保存的key的情况下,文件命名方式首先看看 uptoken 中是否设置了 saveKey, 164 // 如果设置了 saveKey,那么按 saveKey 要求的规则生成 key,否则自动以文件的 hash 做 key。 165 // 166 // ctx 是请求的上下文。 167 // ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。 168 // uptoken 是由业务服务器颁发的上传凭证。 169 // data 是文件内容的访问接口(io.Reader)。 170 // fsize 是要上传的文件大小。 171 // extra 是上传的一些可选项。详细见 PutExtra 结构的描述。 172 func (p *FormUploader) PutWithoutKey( 173 ctx context.Context, ret interface{}, uptoken string, data io.Reader, size int64, extra *PutExtra, 174 ) (err error) { 175 err = p.put(ctx, ret, uptoken, "", false, data, size, extra, "") 176 return err 177 } 178 179 func (p *FormUploader) put( 180 ctx context.Context, ret interface{}, upToken string, 181 key string, hasKey bool, data io.Reader, size int64, extra *PutExtra, fileName string, 182 ) error { 183 if extra == nil { 184 extra = &PutExtra{} 185 } 186 extra.init() 187 188 objectParams := p.newObjectParams(upToken, key, hasKey, extra) 189 objectParams.FileName = "Untitled" 190 191 return p.uploader.UploadReader(ctx, data, objectParams, ret) 192 } 193 194 func (p *FormUploader) newObjectParams(upToken string, key string, hasKey bool, extra *PutExtra) *uploader.ObjectOptions { 195 objectParams := uploader.ObjectOptions{ 196 ContentType: extra.MimeType, 197 UpToken: uptoken.NewParser(upToken), 198 } 199 if hasKey { 200 objectParams.ObjectName = &key 201 } 202 if extra.UpHost != "" { 203 objectParams.RegionsProvider = ®ion.Region{ 204 Up: region.Endpoints{Preferred: []string{extra.UpHost}}, 205 } 206 } else if p.Cfg.UpHost != "" { 207 objectParams.RegionsProvider = ®ion.Region{ 208 Up: region.Endpoints{Preferred: []string{p.Cfg.UpHost}}, 209 } 210 } else if region := p.Cfg.GetRegion(); region != nil { 211 objectParams.RegionsProvider = region 212 } 213 objectParams.Metadata, objectParams.CustomVars = splitParams(extra.Params) 214 if extra.OnProgress != nil { 215 objectParams.OnUploadingProgress = func(progress *uploader.UploadingProgress) { 216 extra.OnProgress(int64(progress.TotalSize), int64(progress.Uploaded)) 217 } 218 } 219 return &objectParams 220 } 221 222 // Deprecated 223 func (p *FormUploader) UpHost(ak, bucket string) (upHost string, err error) { 224 return getUpHost(p.Cfg, 0, 0, ak, bucket) 225 } 226 227 func makeCustomData(params map[string]string) map[string]string { 228 customData := make(map[string]string, len(params)) 229 for k, v := range params { 230 if (strings.HasPrefix(k, "x:") || strings.HasPrefix(k, "x-qn-meta-")) && v != "" { 231 customData[k] = v 232 } 233 } 234 return customData 235 } 236 237 func splitParams(params map[string]string) (metadata, customVars map[string]string) { 238 metadata = make(map[string]string) 239 customVars = make(map[string]string) 240 for k, v := range params { 241 if v == "" { 242 continue 243 } 244 if strings.HasPrefix(k, "x:") { 245 customVars[k] = v 246 } else if strings.HasPrefix(k, "x-qn-meta-") { 247 metadata[k] = v 248 } 249 } 250 return metadata, customVars 251 }