Skip to content

Commit 263a75b

Browse files
authored
feat(infra): add file listing support (#1836)
1 parent 901d025 commit 263a75b

File tree

8 files changed

+513
-142
lines changed

8 files changed

+513
-142
lines changed

backend/infra/contract/storage/storage.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package storage
1919
import (
2020
"context"
2121
"io"
22+
"time"
2223
)
2324

2425
//go:generate mockgen -destination ../../../internal/mock/infra/contract/storage/storage_mock.go -package mock -source storage.go Factory
@@ -28,6 +29,13 @@ type Storage interface {
2829
GetObject(ctx context.Context, objectKey string) ([]byte, error)
2930
DeleteObject(ctx context.Context, objectKey string) error
3031
GetObjectUrl(ctx context.Context, objectKey string, opts ...GetOptFn) (string, error)
32+
// ListObjects returns all objects with the specified prefix.
33+
// It may return a large number of objects, consider using ListObjectsPaginated for better performance.
34+
ListObjects(ctx context.Context, prefix string) ([]*FileInfo, error)
35+
36+
// ListObjectsPaginated returns objects with pagination support.
37+
// Use this method when dealing with large number of objects.
38+
ListObjectsPaginated(ctx context.Context, input *ListObjectsPaginatedInput) (*ListObjectsPaginatedOutput, error)
3139
}
3240

3341
type SecurityToken struct {
@@ -37,3 +45,23 @@ type SecurityToken struct {
3745
ExpiredTime string `thrift:"expired_time,4" frugal:"4,default,string" json:"expired_time"`
3846
CurrentTime string `thrift:"current_time,5" frugal:"5,default,string" json:"current_time"`
3947
}
48+
49+
type ListObjectsPaginatedInput struct {
50+
Prefix string
51+
PageSize int
52+
Cursor string
53+
}
54+
55+
type ListObjectsPaginatedOutput struct {
56+
Files []*FileInfo
57+
Cursor string
58+
// false: All results have been returned
59+
// true: There are more results to return
60+
IsTruncated bool
61+
}
62+
type FileInfo struct {
63+
Key string
64+
LastModified time.Time
65+
ETag string
66+
Size int64
67+
}

backend/infra/impl/storage/minio/minio.go

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,30 @@ import (
2424
"log"
2525
"math/rand"
2626
"net/url"
27-
"os"
28-
"strings"
2927
"time"
3028

3129
"github.com/minio/minio-go/v7"
3230
"github.com/minio/minio-go/v7/pkg/credentials"
3331

34-
"github.com/coze-dev/coze-studio/backend/infra/contract/imagex"
3532
"github.com/coze-dev/coze-studio/backend/infra/contract/storage"
3633
"github.com/coze-dev/coze-studio/backend/infra/impl/storage/proxy"
37-
"github.com/coze-dev/coze-studio/backend/pkg/ctxcache"
38-
"github.com/coze-dev/coze-studio/backend/types/consts"
34+
"github.com/coze-dev/coze-studio/backend/pkg/logs"
3935
)
4036

4137
type minioClient struct {
42-
host string
4338
client *minio.Client
4439
accessKeyID string
4540
secretAccessKey string
4641
bucketName string
4742
endpoint string
4843
}
4944

50-
func NewStorageImagex(ctx context.Context, endpoint, accessKeyID, secretAccessKey, bucketName string, useSSL bool) (imagex.ImageX, error) {
45+
func New(ctx context.Context, endpoint, accessKeyID, secretAccessKey, bucketName string, useSSL bool) (storage.Storage, error) {
5146
m, err := getMinioClient(ctx, endpoint, accessKeyID, secretAccessKey, bucketName, useSSL)
5247
if err != nil {
5348
return nil, err
5449
}
50+
5551
return m, nil
5652
}
5753

@@ -76,14 +72,8 @@ func getMinioClient(_ context.Context, endpoint, accessKeyID, secretAccessKey, b
7672
if err != nil {
7773
return nil, fmt.Errorf("init minio client failed %v", err)
7874
}
79-
return m, nil
80-
}
8175

82-
func New(ctx context.Context, endpoint, accessKeyID, secretAccessKey, bucketName string, useSSL bool) (storage.Storage, error) {
83-
m, err := getMinioClient(ctx, endpoint, accessKeyID, secretAccessKey, bucketName, useSSL)
84-
if err != nil {
85-
return nil, err
86-
}
76+
// m.test()
8777
return m, nil
8878
}
8979

@@ -109,6 +99,8 @@ func (m *minioClient) test() {
10999
ctx := context.Background()
110100
objectName := fmt.Sprintf("test-file-%d.txt", rand.Int())
111101

102+
m.ListObjects(ctx, "")
103+
112104
err := m.PutObject(ctx, objectName, []byte("hello content"), storage.WithContentType("text/plain"))
113105
if err != nil {
114106
log.Fatalf("upload file failed: %v", err)
@@ -223,47 +215,48 @@ func (m *minioClient) GetObjectUrl(ctx context.Context, objectKey string, opts .
223215
return presignedURL.String(), nil
224216
}
225217

226-
func (m *minioClient) GetUploadHost(ctx context.Context) string {
227-
currentHost, ok := ctxcache.Get[string](ctx, consts.HostKeyInCtx)
228-
if !ok {
229-
return ""
218+
func (m *minioClient) ListObjectsPaginated(ctx context.Context, input *storage.ListObjectsPaginatedInput) (*storage.ListObjectsPaginatedOutput, error) {
219+
if input == nil {
220+
return nil, fmt.Errorf("input cannot be nil")
230221
}
231-
return currentHost + consts.ApplyUploadActionURI
232-
}
233-
234-
func (m *minioClient) GetServerID() string {
235-
return ""
236-
}
237-
238-
func (m *minioClient) GetUploadAuth(ctx context.Context, opt ...imagex.UploadAuthOpt) (*imagex.SecurityToken, error) {
239-
scheme := strings.ToLower(os.Getenv(consts.StorageUploadHTTPScheme))
240-
if scheme == "" {
241-
scheme = "http"
222+
if input.PageSize <= 0 {
223+
return nil, fmt.Errorf("page size must be positive")
242224
}
243-
return &imagex.SecurityToken{
244-
AccessKeyID: "",
245-
SecretAccessKey: "",
246-
SessionToken: "",
247-
ExpiredTime: time.Now().Add(time.Hour).Format("2006-01-02 15:04:05"),
248-
CurrentTime: time.Now().Format("2006-01-02 15:04:05"),
249-
HostScheme: scheme,
250-
}, nil
251-
}
252225

253-
func (m *minioClient) GetResourceURL(ctx context.Context, uri string, opts ...imagex.GetResourceOpt) (*imagex.ResourceURL, error) {
254-
url, err := m.GetObjectUrl(ctx, uri)
226+
files, err := m.ListObjects(ctx, input.Prefix)
255227
if err != nil {
256228
return nil, err
257229
}
258-
return &imagex.ResourceURL{
259-
URL: url,
230+
231+
return &storage.ListObjectsPaginatedOutput{
232+
Files: files,
233+
IsTruncated: false,
234+
Cursor: "",
260235
}, nil
261236
}
262237

263-
func (m *minioClient) Upload(ctx context.Context, data []byte, opts ...imagex.UploadAuthOpt) (*imagex.UploadResult, error) {
264-
return nil, nil
265-
}
238+
func (m *minioClient) ListObjects(ctx context.Context, prefix string) ([]*storage.FileInfo, error) {
239+
opts := minio.ListObjectsOptions{
240+
Prefix: prefix,
241+
Recursive: true,
242+
}
243+
244+
objectCh := m.client.ListObjects(ctx, m.bucketName, opts)
245+
246+
var files []*storage.FileInfo
247+
for object := range objectCh {
248+
if object.Err != nil {
249+
return nil, object.Err
250+
}
251+
files = append(files, &storage.FileInfo{
252+
Key: object.Key,
253+
LastModified: object.LastModified,
254+
ETag: object.ETag,
255+
Size: object.Size,
256+
})
257+
258+
logs.CtxDebugf(ctx, "key = %s, lastModified = %s, eTag = %s, size = %d", object.Key, object.LastModified, object.ETag, object.Size)
259+
}
266260

267-
func (m *minioClient) GetUploadAuthWithExpire(ctx context.Context, expire time.Duration, opt ...imagex.UploadAuthOpt) (*imagex.SecurityToken, error) {
268-
return nil, nil
261+
return files, nil
269262
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2025 coze-dev Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package minio
18+
19+
import (
20+
"context"
21+
"os"
22+
"strings"
23+
"time"
24+
25+
"github.com/coze-dev/coze-studio/backend/infra/contract/imagex"
26+
"github.com/coze-dev/coze-studio/backend/pkg/ctxcache"
27+
"github.com/coze-dev/coze-studio/backend/types/consts"
28+
)
29+
30+
func NewStorageImagex(ctx context.Context, endpoint, accessKeyID, secretAccessKey, bucketName string, useSSL bool) (imagex.ImageX, error) {
31+
m, err := getMinioClient(ctx, endpoint, accessKeyID, secretAccessKey, bucketName, useSSL)
32+
if err != nil {
33+
return nil, err
34+
}
35+
return m, nil
36+
}
37+
38+
func (m *minioClient) GetUploadHost(ctx context.Context) string {
39+
currentHost, ok := ctxcache.Get[string](ctx, consts.HostKeyInCtx)
40+
if !ok {
41+
return ""
42+
}
43+
return currentHost + consts.ApplyUploadActionURI
44+
}
45+
46+
func (m *minioClient) GetServerID() string {
47+
return ""
48+
}
49+
50+
func (m *minioClient) GetUploadAuth(ctx context.Context, opt ...imagex.UploadAuthOpt) (*imagex.SecurityToken, error) {
51+
scheme := strings.ToLower(os.Getenv(consts.StorageUploadHTTPScheme))
52+
if scheme == "" {
53+
scheme = "http"
54+
}
55+
return &imagex.SecurityToken{
56+
AccessKeyID: "",
57+
SecretAccessKey: "",
58+
SessionToken: "",
59+
ExpiredTime: time.Now().Add(time.Hour).Format("2006-01-02 15:04:05"),
60+
CurrentTime: time.Now().Format("2006-01-02 15:04:05"),
61+
HostScheme: scheme,
62+
}, nil
63+
}
64+
65+
func (m *minioClient) GetResourceURL(ctx context.Context, uri string, opts ...imagex.GetResourceOpt) (*imagex.ResourceURL, error) {
66+
url, err := m.GetObjectUrl(ctx, uri)
67+
if err != nil {
68+
return nil, err
69+
}
70+
return &imagex.ResourceURL{
71+
URL: url,
72+
}, nil
73+
}
74+
75+
func (m *minioClient) Upload(ctx context.Context, data []byte, opts ...imagex.UploadAuthOpt) (*imagex.UploadResult, error) {
76+
return nil, nil
77+
}
78+
79+
func (m *minioClient) GetUploadAuthWithExpire(ctx context.Context, expire time.Duration, opt ...imagex.UploadAuthOpt) (*imagex.SecurityToken, error) {
80+
return nil, nil
81+
}

0 commit comments

Comments
 (0)