// Package oss adapts the caller-owned official Alibaba Cloud OSS v2 client. package oss import ( "context" "errors" "io" "net/url" "strings" "time" "git.yuntuo.hk/k2safe/go-omni/storage" sdk "github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss" ) type Store struct { client *sdk.Client bucket string maxBytes int64 } // New borrows a configured SDK client. This adapter uses simple uploads; large // multipart uploads are a separate provider capability, not buffered in memory. func New(client *sdk.Client, bucket string, maxBytes int64) (*Store, error) { if client == nil || bucket == "" || maxBytes < 0 || maxBytes > 5<<30 { return nil, errors.New("storage/oss: invalid client, bucket or size limit") } if maxBytes == 0 { maxBytes = 32 << 20 } return &Store{client: client, bucket: bucket, maxBytes: maxBytes}, nil } func (s *Store) Put(ctx context.Context, key string, reader io.Reader, size int64, contentType string) (storage.Info, error) { if err := storage.Check(ctx, key); err != nil { return storage.Info{}, err } if reader == nil || size < 0 || len(contentType) > 256 || strings.ContainsAny(contentType, "\r\n") { return storage.Info{}, errors.New("storage/oss: invalid upload") } if size > s.maxBytes { return storage.Info{}, storage.ErrTooLarge } _, err := s.client.PutObject(ctx, &sdk.PutObjectRequest{Bucket: sdk.Ptr(s.bucket), Key: sdk.Ptr(key), ContentLength: sdk.Ptr(size), ContentType: sdk.Ptr(contentType), Body: storage.ContextReader{Context: ctx, Reader: reader}}) if err != nil { return storage.Info{}, mapError(err) } return storage.Info{Key: key, Size: size, ContentType: contentType, ModifiedAt: time.Now().UTC()}, nil } func (s *Store) Get(ctx context.Context, key string) (storage.Object, error) { if err := storage.Check(ctx, key); err != nil { return storage.Object{}, err } r, err := s.client.GetObject(ctx, &sdk.GetObjectRequest{Bucket: sdk.Ptr(s.bucket), Key: sdk.Ptr(key)}) if err != nil { return storage.Object{}, mapError(err) } if r.ContentLength < 0 || r.ContentLength > s.maxBytes { r.Body.Close() return storage.Object{}, storage.ErrTooLarge } info := storage.Info{Key: key, Size: r.ContentLength, ContentType: sdk.ToString(r.ContentType)} if r.LastModified != nil { info.ModifiedAt = *r.LastModified } return storage.Object{Info: info, Body: r.Body}, nil } func (s *Store) Delete(ctx context.Context, key string) error { if err := storage.Check(ctx, key); err != nil { return err } _, err := s.client.DeleteObject(ctx, &sdk.DeleteObjectRequest{Bucket: sdk.Ptr(s.bucket), Key: sdk.Ptr(key)}) err = mapError(err) if errors.Is(err, storage.ErrNotFound) { return nil } return err } func (s *Store) PresignGet(ctx context.Context, key string, ttl time.Duration) (*url.URL, error) { if err := storage.Check(ctx, key); err != nil { return nil, err } if ttl < time.Second || ttl > time.Hour { return nil, errors.New("storage/oss: TTL must be 1s..1h") } r, err := s.client.Presign(ctx, &sdk.GetObjectRequest{Bucket: sdk.Ptr(s.bucket), Key: sdk.Ptr(key), ResponseContentDisposition: sdk.Ptr("attachment")}, sdk.PresignExpires(ttl)) if err != nil { return nil, err } return url.Parse(r.URL) } func mapError(err error) error { var serviceError *sdk.ServiceError if errors.As(err, &serviceError) && serviceError.Code == "NoSuchKey" { return storage.ErrNotFound } return err } var _ storage.Store = (*Store)(nil) var _ storage.Presigner = (*Store)(nil)