package qiniu import ( "context" "errors" "fmt" "gitea.bvbej.com/bvbej/base-golang/pkg/md5" "github.com/qiniu/go-sdk/v7/auth" "github.com/qiniu/go-sdk/v7/cdn" "github.com/qiniu/go-sdk/v7/storagev2/credentials" "github.com/qiniu/go-sdk/v7/storagev2/downloader" "github.com/qiniu/go-sdk/v7/storagev2/http_client" "github.com/qiniu/go-sdk/v7/storagev2/objects" "github.com/qiniu/go-sdk/v7/storagev2/uploader" "github.com/qiniu/go-sdk/v7/storagev2/uptoken" "github.com/tidwall/gjson" "io" "net/http" "net/url" "path" "strings" "time" ) type Qhash string const ( QSha1 Qhash = "sha1" QSha256 Qhash = "sha256" ) var _ QiNiu = (*qiNiu)(nil) type QiNiu interface { SetDefaultUploadTokenTTL(duration time.Duration) GetUploadToken() (string, error) GetCallbackUploadToken(callbackURL string) (string, error) TimestampSecuritySign(urlStr string, ttl time.Duration) (string, error) UploadFile(key, localFile, callbackURL string) (*PutRet, error) GetPrivateURL(key string, ttl time.Duration) (string, error) VerifyCallback(req *http.Request) (bool, error) GetFileInfo(key string) (*objects.ObjectDetails, error) DelFile(key string) error ListFiles(keyPrefix, marker string, limit uint64) ([]objects.ObjectDetails, error) GetFileHash(path string, qhash Qhash) (hash string, err error) } type qiNiu struct { bucket string domain string securityKey string credentials *auth.Credentials bucketManager *objects.Bucket md5 md5.MD5 uploadTokenTTL time.Duration } 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 { cred := credentials.NewCredentials(accessKey, secretKey) objectsManager := objects.NewObjectsManager(&objects.ObjectsManagerOptions{ Options: http_client.Options{Credentials: cred}, }) return &qiNiu{ bucket: bucket, domain: domain, securityKey: securityKey, credentials: cred, bucketManager: objectsManager.Bucket(bucket), md5: md5.New(), uploadTokenTTL: time.Second * 3600, } } func (q *qiNiu) SetDefaultUploadTokenTTL(duration time.Duration) { q.uploadTokenTTL = duration } func (q *qiNiu) GetUploadToken() (string, error) { putPolicy, err := uptoken.NewPutPolicy(q.bucket, time.Now().Add(q.uploadTokenTTL)) if err != nil { return "", err } return uptoken.NewSigner(putPolicy, q.credentials).GetUpToken(context.Background()) } func (q *qiNiu) GetCallbackUploadToken(callbackURL string) (string, error) { putPolicy, err := uptoken.NewPutPolicy(q.bucket, time.Now().Add(q.uploadTokenTTL)) if err != nil { return "", err } putPolicy.SetCallbackUrl(callbackURL). SetCallbackBody(`{"key":"$(key)","hash":"$(etag)","fname":"$(fname)","fsize":"$(fsize)","ext":"$(ext)","unique":"$(x:unique)","user":"$(x:user)"}`). SetCallbackBodyType("application/json") return uptoken.NewSigner(putPolicy, q.credentials).GetUpToken(context.Background()) } func (q *qiNiu) TimestampSecuritySign(urlStr string, ttl time.Duration) (string, error) { deadline := time.Now().Add(ttl).Unix() tUrl, err := cdn.CreateTimestampAntileechURL(urlStr, q.securityKey, deadline) if err != nil { return "", err } return tUrl, nil } func (q *qiNiu) UploadFile(key, localFile, callbackURL string) (*PutRet, error) { putPolicy, err := uptoken.NewPutPolicy(q.bucket, time.Now().Add(q.uploadTokenTTL)) if err != nil { return nil, err } putPolicy.SetCallbackUrl(callbackURL). SetCallbackBody(`{"key":"$(key)","hash":"$(etag)","fname":"$(fname)","fsize":"$(fsize)","ext":"$(ext)","unique":"$(x:unique)","user":"$(x:user)"}`). SetCallbackBodyType("application/json") ret := &PutRet{} filename := path.Base(key) fileSuffix := path.Ext(key) filePrefix := filename[0 : len(filename)-len(fileSuffix)] uploadManager := uploader.NewUploadManager(&uploader.UploadManagerOptions{}) err = uploadManager.UploadFile(context.Background(), localFile, &uploader.ObjectOptions{ UpToken: uptoken.NewSigner(putPolicy, q.credentials), ObjectName: &key, CustomVars: map[string]string{ "unique": filePrefix, "user": "api", }, }, ret) if err != nil { return nil, err } return ret, nil } func (q *qiNiu) GetPrivateURL(key string, ttl time.Duration) (string, error) { urlsProvider := downloader.SignURLsProvider( downloader.NewStaticDomainBasedURLsProvider([]string{q.domain}), downloader.NewCredentialsSigner(q.credentials), &downloader.SignOptions{ TTL: ttl, }) iter, err := urlsProvider.GetURLsIter(context.Background(), key, &downloader.GenerateOptions{BucketName: q.bucket}) if err != nil { return "", err } res := url.URL{} peek, err := iter.Peek(&res) if err != nil { return "", err } if peek { return res.String(), nil } return "", errors.New("get private url false") } func (q *qiNiu) VerifyCallback(req *http.Request) (bool, error) { return q.credentials.VerifyCallback(req) } func (q *qiNiu) GetFileInfo(key string) (*objects.ObjectDetails, error) { objectInfo, err := q.bucketManager.Object(key).Stat().Call(context.Background()) if err != nil { return nil, err } return objectInfo, nil } func (q *qiNiu) DelFile(key string) error { err := q.bucketManager.Object(key).Delete().Call(context.Background()) if err != nil { return err } return nil } func (q *qiNiu) ListFiles(keyPrefix, marker string, limit uint64) ([]objects.ObjectDetails, error) { iter := q.bucketManager.List(context.Background(), &objects.ListObjectsOptions{ Limit: &limit, Prefix: keyPrefix, Marker: marker, }) defer func() { _ = iter.Close() }() objectInfos := make([]objects.ObjectDetails, 0) var objectInfo objects.ObjectDetails for iter.Next(&objectInfo) { objectInfos = append(objectInfos, objectInfo) } if err := iter.Error(); err != nil { return nil, err } return objectInfos, nil } func (q *qiNiu) GetFileHash(path string, qhash Qhash) (hash string, err error) { sign, err := q.TimestampSecuritySign(path, time.Second*5) if err != nil { return "", err } addr := fmt.Sprintf("%s/%s?%s&qhash/%s", strings.TrimRight(q.domain, "/"), 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 }