package qiniu import ( "context" "errors" "fmt" "git.bvbej.com/bvbej/base-golang/pkg/md5" "git.bvbej.com/bvbej/base-golang/tool" "github.com/qiniu/go-sdk/v7/auth/qbox" "github.com/qiniu/go-sdk/v7/storage" "github.com/tidwall/gjson" "io" "net/http" "net/url" "path" "strings" "time" ) var _ QiNiu = (*qiNiu)(nil) type QiNiu interface { i() SetDefaultUploadTokenTTL(ttl uint64) GetCallbackUploadToken(ttl uint64, callbackURL string) string GetUploadToken(ttl uint64) string GetPrivateURL(key string, ttl uint64) string VerifyCallback(req *http.Request) (bool, error) UploadFile(key, localFile string) (*PutRet, error) ResumeUploadFile(key, localFile string) (*PutRet, error) DelFile(key string) error TimestampSecuritySign(path string, ttl time.Duration) string GetFileInfo(key string) *storage.FileInfo ListFiles(prefix, delimiter, marker string, limit int) (entries []storage.ListItem, commonPrefixes []string, nextMarker string, hasNext bool, err error) GetFileHash(path, qhash string) (hash string, err error) } type qiNiu struct { mac *qbox.Mac bucketManager *storage.BucketManager conf *storage.Config bucket string domain string securityKey string md5 md5.MD5 uploadTokenTTL uint64 } type PutRet struct { Key string `json:"key"` Hash string `json:"hash"` Fsize string `json:"fsize"` Fname string `json:"fname"` Ext string `json:"ext"` Unique string `json:"unique"` User string `json:"user"` } func New(accessKey, secretKey, bucket, domain, securityKey string) QiNiu { region, _ := storage.GetRegion(accessKey, bucket) mac := qbox.NewMac(accessKey, secretKey) conf := &storage.Config{ Region: region, //空间所在的存储区域 UseHTTPS: true, //是否使用https域名 UseCdnDomains: true, //上传是否使用CDN上传加速 } return &qiNiu{ mac: mac, bucketManager: storage.NewBucketManager(mac, conf), bucket: bucket, domain: domain, securityKey: securityKey, conf: conf, md5: md5.New(), uploadTokenTTL: 3600, } } func (q *qiNiu) i() {} func (q *qiNiu) SetDefaultUploadTokenTTL(ttl uint64) { q.uploadTokenTTL = ttl } func (q *qiNiu) GetUploadToken(ttl uint64) string { putPolicy := storage.PutPolicy{ Scope: q.bucket, Expires: ttl, } return putPolicy.UploadToken(q.mac) } func (q *qiNiu) GetCallbackUploadToken(ttl uint64, callbackURL string) string { putPolicy := storage.PutPolicy{ Scope: q.bucket, CallbackURL: callbackURL, CallbackBody: `{"key":"$(key)","hash":"$(etag)","fname":"$(fname)","fsize":"$(fsize)","ext":"$(ext)","unique":"$(x:unique)","user":"$(x:user)"}`, CallbackBodyType: "application/json", Expires: ttl, } return putPolicy.UploadToken(q.mac) } func (q *qiNiu) GetPrivateURL(key string, ttl uint64) string { deadline := time.Now().Add(time.Second * time.Duration(ttl)).Unix() return storage.MakePrivateURL(q.mac, q.domain, key, deadline) } func (q *qiNiu) VerifyCallback(req *http.Request) (bool, error) { return q.mac.VerifyCallback(req) } func (q *qiNiu) UploadFile(key, localFile string) (*PutRet, error) { upToken := q.GetUploadToken(q.uploadTokenTTL) //构建表单上传的对象 formUploader := storage.NewFormUploader(q.conf) //请求参数 filename := path.Base(key) fileSuffix := path.Ext(key) filePrefix := filename[0 : len(filename)-len(fileSuffix)] putExtra := &storage.PutExtra{ Params: map[string]string{ "x:unique": filePrefix, "x:user": "-", }, } //自定义返回body ret := new(PutRet) err := formUploader.PutFile(context.Background(), ret, upToken, key, localFile, putExtra) if err != nil { return nil, err } return ret, nil } func (q *qiNiu) ResumeUploadFile(key, localFile string) (*PutRet, error) { upToken := q.GetUploadToken(q.uploadTokenTTL) //构建分片上传的对象 resumeUploader := storage.NewResumeUploaderV2(q.conf) //请求参数 filename := path.Base(key) fileSuffix := path.Ext(key) filePrefix := filename[0 : len(filename)-len(fileSuffix)] putExtra := &storage.RputV2Extra{ CustomVars: map[string]string{ "x:unique": filePrefix, "x:user": "-", }, } //自定义返回body ret := new(PutRet) err := resumeUploader.PutFile(context.Background(), ret, upToken, key, localFile, putExtra) if err != nil { return nil, err } return ret, nil } func (q *qiNiu) DelFile(key string) error { err := q.bucketManager.Delete(q.bucket, key) if err != nil { return err } return nil } func (q *qiNiu) TimestampSecuritySign(path string, ttl time.Duration) string { sep := "/" path = strings.Trim(path, sep) splits := strings.Split(path, sep) for i, split := range splits { splits[i] = url.QueryEscape(split) } path = sep + strings.Join(splits, sep) unix := time.Now().Add(ttl).Unix() hex := fmt.Sprintf("%x", unix) encrypt := q.md5.Encrypt(q.securityKey + path + hex) param := make(url.Values) param.Set("sign", encrypt) param.Set("t", hex) return param.Encode() } func (q *qiNiu) GetFileInfo(key string) *storage.FileInfo { fileInfo, sErr := q.bucketManager.Stat(q.bucket, key) if sErr != nil { return nil } return &fileInfo } func (q *qiNiu) ListFiles(prefix, delimiter, marker string, limit int) (entries []storage.ListItem, commonPrefixes []string, nextMarker string, hasNext bool, err error) { return q.bucketManager.ListFiles(q.bucket, prefix, delimiter, marker, limit) } func (q *qiNiu) GetFileHash(path, qhash string) (hash string, err error) { if !tool.InArray(qhash, []string{"sha1", "md5", "sha256"}) { return "", errors.New("qhash invalid") } sign := q.TimestampSecuritySign(path, time.Second*5) addr := fmt.Sprintf("https://cdn.mogume.com/%s?%s&qhash/%s", path, sign, qhash) resp, err := http.Get(addr) if err != nil { return "", err } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return "", errors.New(resp.Status) } body, err := io.ReadAll(resp.Body) if err != nil { return "", err } return gjson.GetBytes(body, "hash").String(), nil }