package storage import ( "context" "fmt" "io" "net/http" "os" "path" "strings" "time" "github.com/qiniu/go-sdk/v7/auth/qbox" qstorage "github.com/qiniu/go-sdk/v7/storage" "kra/internal/conf" "kra/internal/modules/system/biz" ) type qiniuStorage struct { config *conf.AdminBackend_Qiniu upload *qstorage.FormUploader manager *qstorage.BucketManager mac *qbox.Mac token string } func newQiniuStorage(config *conf.AdminBackend_Qiniu) (biz.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) *biz.StoredFile { return &biz.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) (*biz.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) (*biz.StoredFile, string, error) { return composeFiles(ctx, s, names, destination) } func (s *qiniuStorage) DeletePrefix(ctx context.Context, prefix string) error { for { items, _, more, err := s.List(ctx, prefix, "", 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 } } } func (s *qiniuStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { if limit <= 0 { limit = 100 } entries, _, marker, more, err := s.manager.ListFiles(s.config.Bucket, prefix, "", cursor, limit) if err != nil { return nil, "", false, err } out := make([]*biz.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 }