github.com/qiniu/go-sdk/v7@v7.27.0/storage/base64_upload.go (about)

     1  package storage
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/base64"
     7  	"fmt"
     8  	"hash/crc32"
     9  	"io"
    10  	"net/http"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/qiniu/go-sdk/v7/conf"
    16  	"github.com/qiniu/go-sdk/v7/internal/hostprovider"
    17  
    18  	"github.com/qiniu/go-sdk/v7/client"
    19  )
    20  
    21  // Base64Uploader 表示一个Base64上传对象
    22  type Base64Uploader struct {
    23  	client *client.Client
    24  	cfg    *Config
    25  }
    26  
    27  // NewBase64Uploader 用来构建一个Base64上传的对象
    28  func NewBase64Uploader(cfg *Config) *Base64Uploader {
    29  	return NewBase64UploaderEx(cfg, nil)
    30  }
    31  
    32  // NewBase64UploaderEx 用来构建一个Base64上传的对象
    33  func NewBase64UploaderEx(cfg *Config, clt *client.Client) *Base64Uploader {
    34  	if cfg == nil {
    35  		cfg = NewConfig()
    36  	}
    37  
    38  	if clt == nil {
    39  		clt = &client.DefaultClient
    40  	}
    41  
    42  	return &Base64Uploader{
    43  		client: clt,
    44  		cfg:    cfg,
    45  	}
    46  }
    47  
    48  // Base64PutExtra 为Base64上传的额外可选项
    49  type Base64PutExtra struct {
    50  	// 可选,用户自定义参数,必须以 "x:" 开头。若不以x:开头,则忽略。
    51  	Params map[string]string
    52  
    53  	// 可选,当为 "" 时候,服务端自动判断。
    54  	MimeType string
    55  
    56  	TryTimes int // 可选。尝试次数
    57  
    58  	// 主备域名冻结时间(默认:600s),当一个域名请求失败(单个域名会被重试 TryTimes 次),会被冻结一段时间,使用备用域名进行重试,在冻结时间内,域名不能被使用,当一个操作中所有域名竣备冻结操作不在进行重试,返回最后一次操作的错误。
    59  	HostFreezeDuration time.Duration
    60  }
    61  
    62  func (r *Base64PutExtra) init() {
    63  	if r.TryTimes == 0 {
    64  		r.TryTimes = settings.TryTimes
    65  	}
    66  	if r.HostFreezeDuration <= 0 {
    67  		r.HostFreezeDuration = 10 * 60 * time.Second
    68  	}
    69  }
    70  
    71  // Put 用来以Base64方式上传一个文件
    72  //
    73  // ctx        是请求的上下文。
    74  // ret        是上传成功后返回的数据。如果 uptoken 中没有设置 callbackUrl 或 returnBody,那么返回的数据结构是 PutRet 结构。
    75  // uptoken    是由业务服务器颁发的上传凭证。
    76  // key        是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
    77  // base64Data 是要上传的Base64数据,一般为图片数据的Base64编码字符串
    78  // extra      是上传的一些可选项,可以指定为nil。详细见 Base64PutExtra 结构的描述。
    79  func (p *Base64Uploader) Put(
    80  	ctx context.Context, ret interface{}, uptoken, key string, base64Data []byte, extra *Base64PutExtra,
    81  ) (err error) {
    82  	return p.put(ctx, ret, uptoken, key, true, base64Data, extra)
    83  }
    84  
    85  // PutWithoutKey 用来以Base64方式上传一个文件,保存的文件名以文件的内容hash作为文件名
    86  func (p *Base64Uploader) PutWithoutKey(
    87  	ctx context.Context, ret interface{}, uptoken string, base64Data []byte, extra *Base64PutExtra,
    88  ) (err error) {
    89  	return p.put(ctx, ret, uptoken, "", false, base64Data, extra)
    90  }
    91  
    92  func (p *Base64Uploader) put(
    93  	ctx context.Context, ret interface{}, uptoken, key string, hasKey bool, base64Data []byte, extra *Base64PutExtra,
    94  ) (err error) {
    95  	// set default extra
    96  	if extra == nil {
    97  		extra = &Base64PutExtra{}
    98  	}
    99  	extra.init()
   100  
   101  	// calc crc32
   102  	h := crc32.NewIEEE()
   103  	rawReader := base64.NewDecoder(base64.StdEncoding, bytes.NewReader(base64Data))
   104  	fsize, decodeErr := io.Copy(h, rawReader)
   105  	if decodeErr != nil {
   106  		err = fmt.Errorf("invalid base64 data, %s", decodeErr.Error())
   107  		return
   108  	}
   109  	fCrc32 := h.Sum32()
   110  
   111  	postPath := bytes.NewBufferString("/putb64")
   112  	// add fsize
   113  	postPath.WriteString("/")
   114  	postPath.WriteString(strconv.Itoa(int(fsize)))
   115  
   116  	// add key
   117  	if hasKey {
   118  		postPath.WriteString("/key/")
   119  		postPath.WriteString(base64.URLEncoding.EncodeToString([]byte(key)))
   120  	}
   121  	// add mimeType
   122  	if extra.MimeType != "" {
   123  		postPath.WriteString("/mimeType/")
   124  		postPath.WriteString(base64.URLEncoding.EncodeToString([]byte(extra.MimeType)))
   125  	}
   126  
   127  	// add crc32
   128  	postPath.WriteString("/crc32/")
   129  	postPath.WriteString(fmt.Sprintf("%d", fCrc32))
   130  
   131  	// add extra params
   132  	if len(extra.Params) > 0 {
   133  		for k, v := range extra.Params {
   134  			if strings.HasPrefix(k, "x:") && v != "" {
   135  				postPath.WriteString("/")
   136  				postPath.WriteString(k)
   137  				postPath.WriteString("/")
   138  				postPath.WriteString(base64.URLEncoding.EncodeToString([]byte(v)))
   139  			}
   140  		}
   141  	}
   142  
   143  	// get up host
   144  	ak, bucket, gErr := getAkBucketFromUploadToken(uptoken)
   145  	if gErr != nil {
   146  		err = gErr
   147  		return
   148  	}
   149  
   150  	var upHostProvider hostprovider.HostProvider
   151  	upHostProvider, err = p.upHostProvider(ak, bucket, extra)
   152  	if err != nil {
   153  		return
   154  	}
   155  
   156  	return doUploadAction(upHostProvider, extra.TryTimes, extra.HostFreezeDuration, func(host string) error {
   157  		postURL := fmt.Sprintf("%s%s", host, postPath.String())
   158  		headers := http.Header{}
   159  		headers.Set("Content-Type", conf.CONTENT_TYPE_OCTET)
   160  		headers.Set("Authorization", "UpToken "+uptoken)
   161  
   162  		return p.client.CallWith(ctx, ret, "POST", postURL, headers, bytes.NewReader(base64Data), len(base64Data))
   163  	})
   164  }
   165  
   166  func (p *Base64Uploader) upHostProvider(ak, bucket string, extra *Base64PutExtra) (hostProvider hostprovider.HostProvider, err error) {
   167  	return getUpHostProvider(p.cfg, extra.TryTimes, extra.HostFreezeDuration, ak, bucket)
   168  }