feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
283
backend/infra/impl/storage/minio/minio.go
Normal file
283
backend/infra/impl/storage/minio/minio.go
Normal file
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* 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 minio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/imagex"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/storage"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/ctxcache"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/types/consts"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type minioClient struct {
|
||||
host string
|
||||
client *minio.Client
|
||||
accessKeyID string
|
||||
secretAccessKey string
|
||||
bucketName string
|
||||
endpoint string
|
||||
}
|
||||
|
||||
func NewStorageImagex(ctx context.Context, endpoint, accessKeyID, secretAccessKey, bucketName string, useSSL bool) (imagex.ImageX, error) {
|
||||
m, err := minoClient(ctx, endpoint, accessKeyID, secretAccessKey, bucketName, useSSL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func minoClient(_ context.Context, endpoint, accessKeyID, secretAccessKey, bucketName string, useSSL bool) (*minioClient, error) {
|
||||
client, err := minio.New(endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
|
||||
Secure: useSSL,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init minio client failed %v", err)
|
||||
}
|
||||
|
||||
m := &minioClient{
|
||||
client: client,
|
||||
accessKeyID: accessKeyID,
|
||||
secretAccessKey: secretAccessKey,
|
||||
bucketName: bucketName,
|
||||
endpoint: endpoint,
|
||||
}
|
||||
|
||||
err = m.createBucketIfNeed(context.Background(), client, bucketName, "cn-north-1")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init minio client failed %v", err)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func New(ctx context.Context, endpoint, accessKeyID, secretAccessKey, bucketName string, useSSL bool) (storage.Storage, error) {
|
||||
m, err := minoClient(ctx, endpoint, accessKeyID, secretAccessKey, bucketName, useSSL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *minioClient) createBucketIfNeed(ctx context.Context, client *minio.Client, bucketName, region string) error {
|
||||
exists, err := client.BucketExists(ctx, bucketName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check bucket %s exist failed %v", bucketName, err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: region})
|
||||
if err != nil {
|
||||
return fmt.Errorf("create bucket %s failed %v", bucketName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *minioClient) test() {
|
||||
ctx := context.Background()
|
||||
objectName := fmt.Sprintf("test-file-%d.txt", rand.Int())
|
||||
|
||||
err := m.PutObject(ctx, objectName, []byte("hello content"), storage.WithContentType("text/plain"))
|
||||
if err != nil {
|
||||
log.Fatalf("upload file failed: %v", err)
|
||||
}
|
||||
log.Printf("upload file success")
|
||||
|
||||
url, err := m.GetObjectUrl(ctx, objectName)
|
||||
if err != nil {
|
||||
log.Fatalf("get file url failed: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("get file url success, url: %s", url)
|
||||
|
||||
content, err := m.GetObject(ctx, objectName)
|
||||
if err != nil {
|
||||
log.Fatalf("download file failed: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("download file success, content: %s", string(content))
|
||||
|
||||
err = m.DeleteObject(ctx, objectName)
|
||||
if err != nil {
|
||||
log.Fatalf("delete object failed: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("delete object success")
|
||||
}
|
||||
|
||||
func (m *minioClient) PutObject(ctx context.Context, objectKey string, content []byte, opts ...storage.PutOptFn) error {
|
||||
option := storage.PutOption{}
|
||||
for _, opt := range opts {
|
||||
opt(&option)
|
||||
}
|
||||
|
||||
minioOpts := minio.PutObjectOptions{}
|
||||
if option.ContentType != nil {
|
||||
minioOpts.ContentType = *option.ContentType
|
||||
}
|
||||
|
||||
if option.ContentEncoding != nil {
|
||||
minioOpts.ContentEncoding = *option.ContentEncoding
|
||||
}
|
||||
|
||||
if option.ContentDisposition != nil {
|
||||
minioOpts.ContentDisposition = *option.ContentDisposition
|
||||
}
|
||||
|
||||
if option.ContentLanguage != nil {
|
||||
minioOpts.ContentLanguage = *option.ContentLanguage
|
||||
}
|
||||
|
||||
if option.Expires != nil {
|
||||
minioOpts.Expires = *option.Expires
|
||||
}
|
||||
|
||||
_, err := m.client.PutObject(ctx, m.bucketName, objectKey,
|
||||
bytes.NewReader(content), int64(len(content)), minioOpts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("PutObject failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *minioClient) GetObject(ctx context.Context, objectKey string) ([]byte, error) {
|
||||
obj, err := m.client.GetObject(ctx, m.bucketName, objectKey, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetObject failed: %v", err)
|
||||
}
|
||||
defer obj.Close()
|
||||
data, err := io.ReadAll(obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ReadObject failed: %v", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (m *minioClient) DeleteObject(ctx context.Context, objectKey string) error {
|
||||
err := m.client.RemoveObject(ctx, m.bucketName, objectKey, minio.RemoveObjectOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("DeleteObject failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *minioClient) GetObjectUrl(ctx context.Context, objectKey string, opts ...storage.GetOptFn) (string, error) {
|
||||
option := storage.GetOption{}
|
||||
for _, opt := range opts {
|
||||
opt(&option)
|
||||
}
|
||||
|
||||
if option.Expire == 0 {
|
||||
option.Expire = 3600 * 24
|
||||
}
|
||||
|
||||
reqParams := make(url.Values)
|
||||
presignedURL, err := m.client.PresignedGetObject(ctx, m.bucketName, objectKey, time.Duration(option.Expire)*time.Second, reqParams)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("GetObjectUrl failed: %v", err)
|
||||
}
|
||||
|
||||
// logs.CtxDebugf(ctx, "[GetObjectUrl] origin presignedURL.String = %s", presignedURL.String())
|
||||
|
||||
proxyPort := os.Getenv(consts.MinIOProxyEndpoint) // :8889
|
||||
if len(proxyPort) > 0 {
|
||||
currentHost, ok := ctxcache.Get[string](ctx, consts.HostKeyInCtx)
|
||||
if !ok {
|
||||
return presignedURL.String(), nil
|
||||
}
|
||||
|
||||
currentScheme, ok := ctxcache.Get[string](ctx, consts.RequestSchemeKeyInCtx)
|
||||
if !ok {
|
||||
return presignedURL.String(), nil
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(currentHost)
|
||||
if err != nil {
|
||||
host = currentHost
|
||||
}
|
||||
minioProxyHost := host + proxyPort
|
||||
presignedURL.Host = minioProxyHost
|
||||
presignedURL.Scheme = currentScheme
|
||||
// logs.CtxDebugf(ctx, "[GetObjectUrl] reset presignedURL.String = %s", presignedURL.String())
|
||||
}
|
||||
|
||||
return presignedURL.String(), nil
|
||||
}
|
||||
|
||||
func (m *minioClient) GetUploadHost(ctx context.Context) string {
|
||||
currentHost, ok := ctxcache.Get[string](ctx, consts.HostKeyInCtx)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return currentHost + consts.ApplyUploadActionURI
|
||||
}
|
||||
|
||||
func (m *minioClient) GetServerID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *minioClient) GetUploadAuth(ctx context.Context, opt ...imagex.UploadAuthOpt) (*imagex.SecurityToken, error) {
|
||||
scheme, ok := ctxcache.Get[string](ctx, consts.RequestSchemeKeyInCtx)
|
||||
if !ok {
|
||||
return nil, errorx.New(errno.ErrUploadHostSchemaNotExistCode)
|
||||
}
|
||||
return &imagex.SecurityToken{
|
||||
AccessKeyID: "",
|
||||
SecretAccessKey: "",
|
||||
SessionToken: "",
|
||||
ExpiredTime: time.Now().Add(time.Hour).Format("2006-01-02 15:04:05"),
|
||||
CurrentTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
HostScheme: scheme,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *minioClient) GetResourceURL(ctx context.Context, uri string, opts ...imagex.GetResourceOpt) (*imagex.ResourceURL, error) {
|
||||
|
||||
url, err := m.GetObjectUrl(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &imagex.ResourceURL{
|
||||
URL: url,
|
||||
}, nil
|
||||
|
||||
}
|
||||
func (m *minioClient) Upload(ctx context.Context, data []byte, opts ...imagex.UploadAuthOpt) (*imagex.UploadResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *minioClient) GetUploadAuthWithExpire(ctx context.Context, expire time.Duration, opt ...imagex.UploadAuthOpt) (*imagex.SecurityToken, error) {
|
||||
return nil, nil
|
||||
}
|
||||
Reference in New Issue
Block a user