140 lines
4.2 KiB
Go
140 lines
4.2 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"kra/internal/biz/system"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"kra/internal/conf"
|
|
|
|
"github.com/qiniu/go-sdk/v7/auth/qbox"
|
|
qstorage "github.com/qiniu/go-sdk/v7/storage"
|
|
)
|
|
|
|
type qiniuStorage struct {
|
|
config *conf.AdminBackend_Qiniu
|
|
upload *qstorage.FormUploader
|
|
manager *qstorage.BucketManager
|
|
mac *qbox.Mac
|
|
token string
|
|
}
|
|
|
|
func newQiniuStorage(config *conf.AdminBackend_Qiniu) (system.FileStorage, error) {
|
|
if config == nil || config.Bucket == "" || config.AccessKey == "" || config.SecretKey == "" {
|
|
return nil, fmt.Errorf("qiniu storage configuration is incomplete")
|
|
}
|
|
cfg := qstorage.Config{UseHTTPS: config.UseHttps, UseCdnDomains: config.UseCdnDomains}
|
|
switch config.Zone {
|
|
case "ZoneHuadong":
|
|
cfg.Zone = &qstorage.ZoneHuadong
|
|
case "ZoneHuabei":
|
|
cfg.Zone = &qstorage.ZoneHuabei
|
|
case "ZoneHuanan":
|
|
cfg.Zone = &qstorage.ZoneHuanan
|
|
case "ZoneBeimei":
|
|
cfg.Zone = &qstorage.ZoneBeimei
|
|
case "ZoneXinjiapo":
|
|
cfg.Zone = &qstorage.ZoneXinjiapo
|
|
}
|
|
mac := qbox.NewMac(config.AccessKey, config.SecretKey)
|
|
policy := qstorage.PutPolicy{Scope: config.Bucket}
|
|
token := policy.UploadToken(mac)
|
|
return &qiniuStorage{config: config, upload: qstorage.NewFormUploader(&cfg), manager: qstorage.NewBucketManager(mac, &cfg), mac: mac, token: token}, nil
|
|
}
|
|
func (s *qiniuStorage) file(key string, size int64) *system.StoredFile {
|
|
return &system.StoredFile{Name: path.Base(key), Path: key, URL: strings.TrimSuffix(s.config.BaseUrl, "/") + "/" + key, Size: size}
|
|
}
|
|
func (s *qiniuStorage) Put(ctx context.Context, name string, reader io.Reader) (*system.StoredFile, error) {
|
|
temporary, err := os.CreateTemp("", "kra-qiniu-upload-*")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
temporaryName := temporary.Name()
|
|
defer os.Remove(temporaryName)
|
|
size, err := io.Copy(temporary, reader)
|
|
if err != nil {
|
|
_ = temporary.Close()
|
|
return nil, err
|
|
}
|
|
if _, err = temporary.Seek(0, io.SeekStart); err != nil {
|
|
_ = temporary.Close()
|
|
return nil, err
|
|
}
|
|
defer temporary.Close()
|
|
ret := qstorage.PutRet{}
|
|
if err = s.upload.Put(ctx, &ret, s.token, strings.TrimPrefix(name, "/"), temporary, size, &qstorage.PutExtra{}); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.file(ret.Key, size), nil
|
|
}
|
|
func (s *qiniuStorage) Open(ctx context.Context, name string) (io.ReadCloser, error) {
|
|
downloadURL := qstorage.MakePrivateURL(s.mac, strings.TrimSuffix(s.config.BaseUrl, "/"), strings.TrimPrefix(name, "/"), time.Now().Add(time.Hour).Unix())
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode >= 300 {
|
|
_ = resp.Body.Close()
|
|
return nil, fmt.Errorf("qiniu download failed: %s", resp.Status)
|
|
}
|
|
return resp.Body, nil
|
|
}
|
|
func (s *qiniuStorage) Delete(ctx context.Context, name string) error {
|
|
return s.manager.Delete(s.config.Bucket, strings.TrimPrefix(name, "/"))
|
|
}
|
|
func (s *qiniuStorage) Compose(ctx context.Context, names []string, destination string) (*system.StoredFile, string, error) {
|
|
return composeFiles(ctx, s, names, destination)
|
|
}
|
|
func (s *qiniuStorage) DeletePrefix(ctx context.Context, prefix string) error {
|
|
prefix, err := normalizeDeletePrefix(prefix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cursor := ""
|
|
for {
|
|
items, next, more, err := s.List(ctx, prefix+"/", cursor, 1000)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, item := range items {
|
|
if err = s.Delete(ctx, item.Path); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if !more {
|
|
return nil
|
|
}
|
|
cursor, err = advanceDeletePrefixCursor(cursor, next, more)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
func (s *qiniuStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*system.StoredFile, string, bool, error) {
|
|
if limit <= 0 {
|
|
limit = 100
|
|
}
|
|
entries, _, marker, more, err := s.manager.ListFiles(s.config.Bucket, boundedPrefix(strings.TrimPrefix(prefix, "/"), prefix), "", cursor, limit)
|
|
if err != nil {
|
|
return nil, "", false, err
|
|
}
|
|
out := make([]*system.StoredFile, 0, len(entries))
|
|
for _, entry := range entries {
|
|
item := s.file(entry.Key, entry.Fsize)
|
|
item.LastModified = time.Unix(0, entry.PutTime*100)
|
|
item.ContentType = entry.MimeType
|
|
out = append(out, item)
|
|
}
|
|
return out, marker, more, nil
|
|
}
|