diff --git a/backend/application/base/appinfra/app_infra.go b/backend/application/base/appinfra/app_infra.go index 20620766..2b7bbb41 100644 --- a/backend/application/base/appinfra/app_infra.go +++ b/backend/application/base/appinfra/app_infra.go @@ -105,6 +105,10 @@ type AppDependencies struct { func Init(ctx context.Context) (*AppDependencies, error) { deps := &AppDependencies{} var err error + deps.TOSClient, err = initTOS(ctx) + if err != nil { + return nil, fmt.Errorf("init tos client failed, err=%w", err) + } deps.DB, err = mysql.New() if err != nil { @@ -128,11 +132,6 @@ func Init(ctx context.Context) (*AppDependencies, error) { return nil, fmt.Errorf("init imagex client failed, err=%w", err) } - deps.TOSClient, err = initTOS(ctx) - if err != nil { - return nil, fmt.Errorf("init tos client failed, err=%w", err) - } - deps.ResourceEventProducer, err = initResourceEventBusProducer() if err != nil { return nil, fmt.Errorf("init resource event bus producer failed, err=%w", err) diff --git a/backend/crossdomain/contract/agent/single_agent.go b/backend/crossdomain/contract/agent/single_agent.go index d9d4266b..6ce8d116 100644 --- a/backend/crossdomain/contract/agent/single_agent.go +++ b/backend/crossdomain/contract/agent/single_agent.go @@ -61,7 +61,6 @@ func SetDefaultSVC(svc SingleAgent) { defaultSVC = svc } - type ShortcutCommandComponentType string const ( @@ -70,7 +69,6 @@ const ( ShortcutCommandComponentTypeFile ShortcutCommandComponentType = "file" ) - var ShortcutCommandComponentTypeMapping = map[playground.InputType]ShortcutCommandComponentType{ playground.InputType_TextInput: ShortcutCommandComponentTypeText, playground.InputType_Select: ShortcutCommandComponentTypeSelect, @@ -85,6 +83,7 @@ var ShortcutCommandComponentTypeMapping = map[playground.InputType]ShortcutComma playground.InputType_TXT: ShortcutCommandComponentTypeFile, playground.InputType_PPT: ShortcutCommandComponentTypeFile, } + type ShortcutCommandComponentFileType string const ( diff --git a/backend/domain/plugin/service/plugin_oauth_test.go b/backend/domain/plugin/service/plugin_oauth_test.go index 2e0c535a..84e0eb53 100644 --- a/backend/domain/plugin/service/plugin_oauth_test.go +++ b/backend/domain/plugin/service/plugin_oauth_test.go @@ -21,10 +21,11 @@ import ( "fmt" "testing" - "github.com/coze-dev/coze-studio/backend/domain/plugin/repository/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "go.uber.org/mock/gomock" + + mock_plugin_oauth "github.com/coze-dev/coze-studio/backend/domain/plugin/repository/mock" ) type pluginOAuthSuite struct { diff --git a/backend/infra/contract/storage/option.go b/backend/infra/contract/storage/option.go index 4d1af121..23d2044e 100644 --- a/backend/infra/contract/storage/option.go +++ b/backend/infra/contract/storage/option.go @@ -23,7 +23,9 @@ import ( type GetOptFn func(option *GetOption) type GetOption struct { - Expire int64 // seconds + Expire int64 // seconds + WithURL bool + WithTagging bool } func WithExpire(expire int64) GetOptFn { @@ -32,6 +34,18 @@ func WithExpire(expire int64) GetOptFn { } } +func WithURL(withURL bool) GetOptFn { + return func(o *GetOption) { + o.WithURL = withURL + } +} + +func WithGetTagging(withTagging bool) GetOptFn { + return func(o *GetOption) { + o.WithTagging = withTagging + } +} + type PutOption struct { ContentType *string ContentEncoding *string diff --git a/backend/infra/contract/storage/storage.go b/backend/infra/contract/storage/storage.go index 517e0078..27a3712c 100644 --- a/backend/infra/contract/storage/storage.go +++ b/backend/infra/contract/storage/storage.go @@ -36,13 +36,13 @@ type Storage interface { // The URL is valid for the specified duration. GetObjectUrl(ctx context.Context, objectKey string, opts ...GetOptFn) (string, error) // HeadObject returns the object metadata with the specified key. - HeadObject(ctx context.Context, objectKey string, withTagging bool) (*FileInfo, error) + HeadObject(ctx context.Context, objectKey string, opts ...GetOptFn) (*FileInfo, error) // ListAllObjects returns all objects with the specified prefix. // It may return a large number of objects, consider using ListObjectsPaginated for better performance. - ListAllObjects(ctx context.Context, prefix string, withTagging bool) ([]*FileInfo, error) + ListAllObjects(ctx context.Context, prefix string, opts ...GetOptFn) ([]*FileInfo, error) // ListObjectsPaginated returns objects with pagination support. // Use this method when dealing with large number of objects. - ListObjectsPaginated(ctx context.Context, input *ListObjectsPaginatedInput) (*ListObjectsPaginatedOutput, error) + ListObjectsPaginated(ctx context.Context, input *ListObjectsPaginatedInput, opts ...GetOptFn) (*ListObjectsPaginatedOutput, error) } type SecurityToken struct { @@ -57,8 +57,6 @@ type ListObjectsPaginatedInput struct { Prefix string PageSize int Cursor string - // Include objects tagging in the listing - WithTagging bool } type ListObjectsPaginatedOutput struct { @@ -74,5 +72,6 @@ type FileInfo struct { LastModified time.Time ETag string Size int64 + URL string Tagging map[string]string } diff --git a/backend/infra/impl/storage/internal/fileutil/file_util.go b/backend/infra/impl/storage/internal/fileutil/file_util.go new file mode 100644 index 00000000..e6e72b2e --- /dev/null +++ b/backend/infra/impl/storage/internal/fileutil/file_util.go @@ -0,0 +1,55 @@ +/* + * Copyright 2025 coze-dev Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package fileutil + +import ( + "context" + + "github.com/coze-dev/coze-studio/backend/infra/contract/storage" + "github.com/coze-dev/coze-studio/backend/pkg/taskgroup" +) + +func AssembleFileUrl(ctx context.Context, urlExpire *int64, files []*storage.FileInfo, s storage.Storage) ([]*storage.FileInfo, error) { + if files == nil || s == nil { + return files, nil + } + + taskGroup := taskgroup.NewTaskGroup(ctx, 5) + for idx := range files { + f := files[idx] + expire := int64(60 * 60 * 24) + if urlExpire != nil && *urlExpire > 0 { + expire = *urlExpire + } + + taskGroup.Go(func() error { + url, err := s.GetObjectUrl(ctx, f.Key, storage.WithExpire(expire)) + if err != nil { + return err + } + + f.URL = url + + return nil + }) + } + + if err := taskGroup.Wait(); err != nil { + return nil, err + } + + return files, nil +} diff --git a/backend/infra/impl/storage/proxy/proxy.go b/backend/infra/impl/storage/internal/proxy/proxy.go similarity index 100% rename from backend/infra/impl/storage/proxy/proxy.go rename to backend/infra/impl/storage/internal/proxy/proxy.go diff --git a/backend/infra/impl/storage/minio/minio.go b/backend/infra/impl/storage/minio/minio.go index af67d33a..2784de0a 100644 --- a/backend/infra/impl/storage/minio/minio.go +++ b/backend/infra/impl/storage/minio/minio.go @@ -29,7 +29,8 @@ import ( "github.com/minio/minio-go/v7/pkg/credentials" "github.com/coze-dev/coze-studio/backend/infra/contract/storage" - "github.com/coze-dev/coze-studio/backend/infra/impl/storage/proxy" + "github.com/coze-dev/coze-studio/backend/infra/impl/storage/internal/fileutil" + "github.com/coze-dev/coze-studio/backend/infra/impl/storage/internal/proxy" "github.com/coze-dev/coze-studio/backend/pkg/logs" ) @@ -108,7 +109,7 @@ func (m *minioClient) test() { logs.CtxErrorf(ctx, "upload file failed: %v", err) } - f, err := m.HeadObject(ctx, objectName, true) + f, err := m.HeadObject(ctx, objectName, storage.WithGetTagging(true), storage.WithURL(true)) if err != nil { logs.CtxErrorf(ctx, "head object failed: %v", err) } @@ -116,12 +117,12 @@ func (m *minioClient) test() { logs.CtxInfof(ctx, "head object success, f: %v, tagging: %v", *f, f.Tagging) } - f, err = m.HeadObject(ctx, "not_exit.txt", true) + f, err = m.HeadObject(ctx, "not_exit.txt", storage.WithGetTagging(true)) logs.CtxInfof(context.Background(), "HeadObject not exit success, f: %v, err: %v", f, err) logs.CtxInfof(ctx, "upload file success") - files, err := m.ListAllObjects(ctx, "test-file-", true) + files, err := m.ListAllObjects(ctx, "test-file-", storage.WithGetTagging(true), storage.WithURL(true)) if err != nil { logs.CtxErrorf(ctx, "list objects failed: %v", err) } @@ -240,7 +241,7 @@ func (m *minioClient) GetObjectUrl(ctx context.Context, objectKey string, opts . return presignedURL.String(), nil } -func (m *minioClient) ListObjectsPaginated(ctx context.Context, input *storage.ListObjectsPaginatedInput) (*storage.ListObjectsPaginatedOutput, error) { +func (m *minioClient) ListObjectsPaginated(ctx context.Context, input *storage.ListObjectsPaginatedInput, opts ...storage.GetOptFn) (*storage.ListObjectsPaginatedOutput, error) { if input == nil { return nil, fmt.Errorf("input cannot be nil") } @@ -248,11 +249,23 @@ func (m *minioClient) ListObjectsPaginated(ctx context.Context, input *storage.L return nil, fmt.Errorf("page size must be positive") } - files, err := m.ListAllObjects(ctx, input.Prefix, input.WithTagging) + files, err := m.ListAllObjects(ctx, input.Prefix, opts...) if err != nil { return nil, err } + option := storage.GetOption{} + for _, opt := range opts { + opt(&option) + } + + if option.WithURL { + files, err = fileutil.AssembleFileUrl(ctx, &option.Expire, files, m) + if err != nil { + return nil, err + } + } + return &storage.ListObjectsPaginatedOutput{ Files: files, IsTruncated: false, @@ -260,14 +273,19 @@ func (m *minioClient) ListObjectsPaginated(ctx context.Context, input *storage.L }, nil } -func (m *minioClient) ListAllObjects(ctx context.Context, prefix string, withTagging bool) ([]*storage.FileInfo, error) { - opts := minio.ListObjectsOptions{ +func (m *minioClient) ListAllObjects(ctx context.Context, prefix string, opts ...storage.GetOptFn) ([]*storage.FileInfo, error) { + option := storage.GetOption{} + for _, opt := range opts { + opt(&option) + } + + minioOpts := minio.ListObjectsOptions{ Prefix: prefix, Recursive: true, - WithMetadata: withTagging, + WithMetadata: option.WithTagging, } - objectCh := m.client.ListObjects(ctx, m.bucketName, opts) + objectCh := m.client.ListObjects(ctx, m.bucketName, minioOpts) var files []*storage.FileInfo for object := range objectCh { @@ -290,7 +308,12 @@ func (m *minioClient) ListAllObjects(ctx context.Context, prefix string, withTag return files, nil } -func (m *minioClient) HeadObject(ctx context.Context, objectKey string, withTagging bool) (*storage.FileInfo, error) { +func (m *minioClient) HeadObject(ctx context.Context, objectKey string, opts ...storage.GetOptFn) (*storage.FileInfo, error) { + option := storage.GetOption{} + for _, opt := range opts { + opt(&option) + } + stat, err := m.client.StatObject(ctx, m.bucketName, objectKey, minio.StatObjectOptions{}) if err != nil { if minio.ToErrorResponse(err).Code == "NoSuchKey" { @@ -307,7 +330,7 @@ func (m *minioClient) HeadObject(ctx context.Context, objectKey string, withTagg Size: stat.Size, } - if withTagging { + if option.WithTagging { tags, err := m.client.GetObjectTagging(ctx, m.bucketName, objectKey, minio.GetObjectTaggingOptions{}) if err != nil { return nil, err @@ -316,5 +339,12 @@ func (m *minioClient) HeadObject(ctx context.Context, objectKey string, withTagg f.Tagging = tags.ToMap() } + if option.WithURL { + f.URL, err = m.GetObjectUrl(ctx, objectKey, opts...) + if err != nil { + return nil, err + } + } + return f, nil } diff --git a/backend/infra/impl/storage/s3/s3.go b/backend/infra/impl/storage/s3/s3.go index e7d9d166..6670f401 100644 --- a/backend/infra/impl/storage/s3/s3.go +++ b/backend/infra/impl/storage/s3/s3.go @@ -31,7 +31,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/coze-dev/coze-studio/backend/infra/contract/storage" - "github.com/coze-dev/coze-studio/backend/infra/impl/storage/proxy" + "github.com/coze-dev/coze-studio/backend/infra/impl/storage/internal/fileutil" + "github.com/coze-dev/coze-studio/backend/infra/impl/storage/internal/proxy" "github.com/coze-dev/coze-studio/backend/pkg/goutil" "github.com/coze-dev/coze-studio/backend/pkg/logs" "github.com/coze-dev/coze-studio/backend/pkg/taskgroup" @@ -247,7 +248,7 @@ func (t *s3Client) GetObjectUrl(ctx context.Context, objectKey string, opts ...s return req.URL, nil } -func (t *s3Client) ListAllObjects(ctx context.Context, prefix string, withTagging bool) ([]*storage.FileInfo, error) { +func (t *s3Client) ListAllObjects(ctx context.Context, prefix string, opts ...storage.GetOptFn) ([]*storage.FileInfo, error) { const ( DefaultPageSize = 100 MaxListObjects = 10000 @@ -257,11 +258,10 @@ func (t *s3Client) ListAllObjects(ctx context.Context, prefix string, withTaggin var cursor string for { output, err := t.ListObjectsPaginated(ctx, &storage.ListObjectsPaginatedInput{ - Prefix: prefix, - PageSize: DefaultPageSize, - WithTagging: withTagging, - Cursor: cursor, - }) + Prefix: prefix, + PageSize: DefaultPageSize, + Cursor: cursor, + }, opts...) if err != nil { return nil, err @@ -284,7 +284,7 @@ func (t *s3Client) ListAllObjects(ctx context.Context, prefix string, withTaggin return files, nil } -func (t *s3Client) ListObjectsPaginated(ctx context.Context, input *storage.ListObjectsPaginatedInput) (*storage.ListObjectsPaginatedOutput, error) { +func (t *s3Client) ListObjectsPaginated(ctx context.Context, input *storage.ListObjectsPaginatedInput, opts ...storage.GetOptFn) (*storage.ListObjectsPaginatedOutput, error) { if input == nil { return nil, fmt.Errorf("input cannot be nil") } @@ -335,7 +335,12 @@ func (t *s3Client) ListObjectsPaginated(ctx context.Context, input *storage.List output.Cursor = *p.NextContinuationToken } - if input.WithTagging { + opt := storage.GetOption{} + for _, optFn := range opts { + optFn(&opt) + } + + if opt.WithTagging { taskGroup := taskgroup.NewTaskGroup(ctx, 5) for idx := range files { f := files[idx] @@ -358,10 +363,17 @@ func (t *s3Client) ListObjectsPaginated(ctx context.Context, input *storage.List } } + if opt.WithURL { + files, err = fileutil.AssembleFileUrl(ctx, &opt.Expire, files, t) + if err != nil { + return nil, err + } + } + return output, nil } -func (t *s3Client) HeadObject(ctx context.Context, objectKey string, withTagging bool) (*storage.FileInfo, error) { +func (t *s3Client) HeadObject(ctx context.Context, objectKey string, opts ...storage.GetOptFn) (*storage.FileInfo, error) { obj, err := t.client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: aws.String(t.bucketName), Key: aws.String(objectKey), @@ -389,7 +401,12 @@ func (t *s3Client) HeadObject(ctx context.Context, objectKey string, withTagging f.Size = *obj.ContentLength } - if withTagging { + opt := storage.GetOption{} + for _, optFn := range opts { + optFn(&opt) + } + + if opt.WithTagging { tagging, err := t.client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: aws.String(t.bucketName), Key: aws.String(objectKey), @@ -401,6 +418,13 @@ func (t *s3Client) HeadObject(ctx context.Context, objectKey string, withTagging f.Tagging = tagsToMap(tagging.TagSet) } + if opt.WithURL { + f.URL, err = t.GetObjectUrl(ctx, objectKey, opts...) + if err != nil { + return nil, err + } + } + return f, nil } diff --git a/backend/infra/impl/storage/tos/tos.go b/backend/infra/impl/storage/tos/tos.go index 45607d6b..991facb5 100644 --- a/backend/infra/impl/storage/tos/tos.go +++ b/backend/infra/impl/storage/tos/tos.go @@ -29,7 +29,8 @@ import ( "github.com/volcengine/ve-tos-golang-sdk/v2/tos/enum" "github.com/coze-dev/coze-studio/backend/infra/contract/storage" - "github.com/coze-dev/coze-studio/backend/infra/impl/storage/proxy" + "github.com/coze-dev/coze-studio/backend/infra/impl/storage/internal/fileutil" + "github.com/coze-dev/coze-studio/backend/infra/impl/storage/internal/proxy" "github.com/coze-dev/coze-studio/backend/pkg/goutil" "github.com/coze-dev/coze-studio/backend/pkg/lang/conv" "github.com/coze-dev/coze-studio/backend/pkg/logs" @@ -78,48 +79,50 @@ func (t *tosClient) test() { // test upload objectKey := fmt.Sprintf("test-%s.txt", time.Now().Format("20060102150405")) - err := t.PutObject(context.Background(), objectKey, []byte("hello world"), storage.WithTagging(map[string]string{ + err := t.PutObject(ctx, objectKey, []byte("hello world"), storage.WithTagging(map[string]string{ "uid": "7543149965070155780", "conversation_id": "7543149965070155781", "type": "user", })) if err != nil { - logs.CtxErrorf(context.Background(), "PutObject failed, objectKey: %s, err: %v", objectKey, err) + logs.CtxErrorf(ctx, "PutObject failed, objectKey: %s, err: %v", objectKey, err) } - f, err := t.HeadObject(ctx, objectKey, true) + f, err := t.HeadObject(ctx, objectKey, storage.WithGetTagging(true), storage.WithURL(true)) if err != nil { - logs.CtxErrorf(context.Background(), "HeadObject failed, objectKey: %s, err: %v", objectKey, err) + logs.CtxErrorf(ctx, "HeadObject failed, objectKey: %s, err: %v", objectKey, err) } + logs.CtxInfof(ctx, "HeadObject file success, f: %v, err: %v", conv.DebugJsonToStr(f), err) + if f != nil { - logs.CtxInfof(context.Background(), "HeadObject success, f: %v, tagging: %v", *f, f.Tagging) + logs.CtxInfof(ctx, "HeadObject success, f: %v, tagging: %v", *f, f.Tagging) } - f, err = t.HeadObject(ctx, "not_exit.txt", true) - logs.CtxInfof(context.Background(), "HeadObject not exit success, f: %v, err: %v", f, err) + f, err = t.HeadObject(ctx, "not_exit.txt", storage.WithGetTagging(true), storage.WithURL(true)) + logs.CtxInfof(ctx, "HeadObject not exit success, f: %v, err: %v", f, err) - t.ListAllObjects(ctx, "", true) + t.ListAllObjects(ctx, "", storage.WithGetTagging(true)) // test download - content, err := t.GetObject(context.Background(), objectKey) + content, err := t.GetObject(ctx, objectKey) if err != nil { - logs.CtxErrorf(context.Background(), "GetObject failed, objectKey: %s, err: %v", objectKey, err) + logs.CtxErrorf(ctx, "GetObject failed, objectKey: %s, err: %v", objectKey, err) } - logs.CtxInfof(context.Background(), "GetObject content: %s", string(content)) + logs.CtxInfof(ctx, "GetObject content: %s", string(content)) // Test Get URL - url, err := t.GetObjectUrl(context.Background(), objectKey) + url, err := t.GetObjectUrl(ctx, objectKey) if err != nil { - logs.CtxErrorf(context.Background(), "GetObjectUrl failed, objectKey: %s, err: %v", objectKey, err) + logs.CtxErrorf(ctx, "GetObjectUrl failed, objectKey: %s, err: %v", objectKey, err) } - logs.CtxInfof(context.Background(), "GetObjectUrl url: %s", url) + logs.CtxInfof(ctx, "GetObjectUrl url: %s", url) // test delete - err = t.DeleteObject(context.Background(), objectKey) + err = t.DeleteObject(ctx, objectKey) if err != nil { - logs.CtxErrorf(context.Background(), "DeleteObject failed, objectKey: %s, err: %v", objectKey, err) + logs.CtxErrorf(ctx, "DeleteObject failed, objectKey: %s, err: %v", objectKey, err) } } @@ -262,7 +265,7 @@ func (t *tosClient) GetObjectUrl(ctx context.Context, objectKey string, opts ... return output.SignedUrl, nil } -func (t *tosClient) ListObjectsPaginated(ctx context.Context, input *storage.ListObjectsPaginatedInput) (*storage.ListObjectsPaginatedOutput, error) { +func (t *tosClient) ListObjectsPaginated(ctx context.Context, input *storage.ListObjectsPaginatedInput, opts ...storage.GetOptFn) (*storage.ListObjectsPaginatedOutput, error) { if input == nil { return nil, fmt.Errorf("input cannot be nil") } @@ -273,10 +276,9 @@ func (t *tosClient) ListObjectsPaginated(ctx context.Context, input *storage.Lis output, err := t.client.ListObjectsV2(ctx, &tos.ListObjectsV2Input{ Bucket: t.bucketName, ListObjectsInput: tos.ListObjectsInput{ - MaxKeys: int(input.PageSize), - Marker: input.Cursor, - Prefix: input.Prefix, - FetchMeta: input.WithTagging, + MaxKeys: int(input.PageSize), + Marker: input.Cursor, + Prefix: input.Prefix, }, }) if err != nil { @@ -298,7 +300,12 @@ func (t *tosClient) ListObjectsPaginated(ctx context.Context, input *storage.Lis }) } - if input.WithTagging { + opt := storage.GetOption{} + for _, optFn := range opts { + optFn(&opt) + } + + if opt.WithTagging { client := t.client taskGroup := taskgroup.NewTaskGroup(ctx, 5) for idx := range files { @@ -322,6 +329,13 @@ func (t *tosClient) ListObjectsPaginated(ctx context.Context, input *storage.Lis } } + if opt.WithURL { + files, err = fileutil.AssembleFileUrl(ctx, &opt.Expire, files, t) + if err != nil { + return nil, err + } + } + return &storage.ListObjectsPaginatedOutput{ Files: files, Cursor: output.NextMarker, @@ -329,7 +343,7 @@ func (t *tosClient) ListObjectsPaginated(ctx context.Context, input *storage.Lis }, nil } -func (t *tosClient) ListAllObjects(ctx context.Context, prefix string, withTagging bool) ([]*storage.FileInfo, error) { +func (t *tosClient) ListAllObjects(ctx context.Context, prefix string, opts ...storage.GetOptFn) ([]*storage.FileInfo, error) { const ( DefaultPageSize = 100 MaxListObjects = 10000 @@ -340,18 +354,17 @@ func (t *tosClient) ListAllObjects(ctx context.Context, prefix string, withTaggi for { output, err := t.ListObjectsPaginated(ctx, &storage.ListObjectsPaginatedInput{ - Prefix: prefix, - PageSize: DefaultPageSize, - Cursor: cursor, - WithTagging: withTagging, - }) + Prefix: prefix, + PageSize: DefaultPageSize, + Cursor: cursor, + }, opts...) if err != nil { return nil, fmt.Errorf("list objects failed, prefix = %v, err: %v", prefix, err) } for _, object := range output.Files { - logs.CtxDebugf(ctx, "key = %s, lastModified = %s, eTag = %s, size = %d, tagging = %v", - object.Key, object.LastModified, object.ETag, object.Size, object.Tagging) + logs.CtxDebugf(ctx, "key = %s, lastModified = %s, eTag = %s, size = %d, tagging = %v, url = %s", + object.Key, object.LastModified, object.ETag, object.Size, object.Tagging, object.URL) files = append(files, object) } @@ -371,7 +384,7 @@ func (t *tosClient) ListAllObjects(ctx context.Context, prefix string, withTaggi return files, nil } -func (t *tosClient) HeadObject(ctx context.Context, objectKey string, withTagging bool) (*storage.FileInfo, error) { +func (t *tosClient) HeadObject(ctx context.Context, objectKey string, opts ...storage.GetOptFn) (*storage.FileInfo, error) { output, err := t.client.HeadObjectV2(ctx, &tos.HeadObjectV2Input{Bucket: t.bucketName, Key: objectKey}) if err != nil { if serverErr, ok := err.(*tos.TosServerError); ok { @@ -389,7 +402,12 @@ func (t *tosClient) HeadObject(ctx context.Context, objectKey string, withTaggin Size: output.ContentLength, } - if withTagging { + opt := storage.GetOption{} + for _, optFn := range opts { + optFn(&opt) + } + + if opt.WithTagging { tagging, err := t.client.GetObjectTagging(ctx, &tos.GetObjectTaggingInput{ Bucket: t.bucketName, Key: objectKey, @@ -401,6 +419,13 @@ func (t *tosClient) HeadObject(ctx context.Context, objectKey string, withTaggin fileInfo.Tagging = tagsToMap(tagging.TagSet.Tags) } + if opt.WithURL { + fileInfo.URL, err = t.GetObjectUrl(ctx, objectKey, opts...) + if err != nil { + return nil, err + } + } + return fileInfo, nil } diff --git a/backend/internal/mock/infra/contract/storage/storage_mock.go b/backend/internal/mock/infra/contract/storage/storage_mock.go index be3bf8fa..ee8fb6b5 100644 --- a/backend/internal/mock/infra/contract/storage/storage_mock.go +++ b/backend/internal/mock/infra/contract/storage/storage_mock.go @@ -1,19 +1,3 @@ -/* - * Copyright 2025 coze-dev Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - // Code generated by MockGen. DO NOT EDIT. // Source: storage.go // @@ -108,48 +92,63 @@ func (mr *MockStorageMockRecorder) GetObjectUrl(ctx, objectKey any, opts ...any) } // HeadObject mocks base method. -func (m *MockStorage) HeadObject(ctx context.Context, objectKey string, withTagging bool) (*storage.FileInfo, error) { +func (m *MockStorage) HeadObject(ctx context.Context, objectKey string, opts ...storage.GetOptFn) (*storage.FileInfo, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HeadObject", ctx, objectKey, withTagging) + varargs := []any{ctx, objectKey} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HeadObject", varargs...) ret0, _ := ret[0].(*storage.FileInfo) ret1, _ := ret[1].(error) return ret0, ret1 } // HeadObject indicates an expected call of HeadObject. -func (mr *MockStorageMockRecorder) HeadObject(ctx, objectKey, withTagging any) *gomock.Call { +func (mr *MockStorageMockRecorder) HeadObject(ctx, objectKey any, opts ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadObject", reflect.TypeOf((*MockStorage)(nil).HeadObject), ctx, objectKey, withTagging) + varargs := append([]any{ctx, objectKey}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadObject", reflect.TypeOf((*MockStorage)(nil).HeadObject), varargs...) } // ListAllObjects mocks base method. -func (m *MockStorage) ListAllObjects(ctx context.Context, prefix string, withTagging bool) ([]*storage.FileInfo, error) { +func (m *MockStorage) ListAllObjects(ctx context.Context, prefix string, opts ...storage.GetOptFn) ([]*storage.FileInfo, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListAllObjects", ctx, prefix, withTagging) + varargs := []any{ctx, prefix} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ListAllObjects", varargs...) ret0, _ := ret[0].([]*storage.FileInfo) ret1, _ := ret[1].(error) return ret0, ret1 } // ListAllObjects indicates an expected call of ListAllObjects. -func (mr *MockStorageMockRecorder) ListAllObjects(ctx, prefix, withTagging any) *gomock.Call { +func (mr *MockStorageMockRecorder) ListAllObjects(ctx, prefix any, opts ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllObjects", reflect.TypeOf((*MockStorage)(nil).ListAllObjects), ctx, prefix, withTagging) + varargs := append([]any{ctx, prefix}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllObjects", reflect.TypeOf((*MockStorage)(nil).ListAllObjects), varargs...) } // ListObjectsPaginated mocks base method. -func (m *MockStorage) ListObjectsPaginated(ctx context.Context, input *storage.ListObjectsPaginatedInput) (*storage.ListObjectsPaginatedOutput, error) { +func (m *MockStorage) ListObjectsPaginated(ctx context.Context, input *storage.ListObjectsPaginatedInput, opts ...storage.GetOptFn) (*storage.ListObjectsPaginatedOutput, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListObjectsPaginated", ctx, input) + varargs := []any{ctx, input} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ListObjectsPaginated", varargs...) ret0, _ := ret[0].(*storage.ListObjectsPaginatedOutput) ret1, _ := ret[1].(error) return ret0, ret1 } // ListObjectsPaginated indicates an expected call of ListObjectsPaginated. -func (mr *MockStorageMockRecorder) ListObjectsPaginated(ctx, input any) *gomock.Call { +func (mr *MockStorageMockRecorder) ListObjectsPaginated(ctx, input any, opts ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListObjectsPaginated", reflect.TypeOf((*MockStorage)(nil).ListObjectsPaginated), ctx, input) + varargs := append([]any{ctx, input}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListObjectsPaginated", reflect.TypeOf((*MockStorage)(nil).ListObjectsPaginated), varargs...) } // PutObject mocks base method.