From 4337dc74e49984c21142cb1772555c892fe0935e Mon Sep 17 00:00:00 2001 From: Yun Date: Mon, 9 Mar 2026 11:44:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aliyun/oss.go | 41 ++++++++++++++++++++++++++ aws/s3.go | 38 ++++++++++++++++++++++++ driver.go | 57 ++++++++++++++++++++++++++++++++++++ example/main.go | 61 ++++++++++++++++++++++++++++++++++++++ factory.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 ++ tencent/cos.go | 15 ++++++++++ types.go | 1 + 8 files changed, 294 insertions(+) create mode 100644 aliyun/oss.go create mode 100644 aws/s3.go create mode 100644 driver.go create mode 100644 example/main.go create mode 100644 factory.go create mode 100644 go.mod create mode 100644 tencent/cos.go create mode 100644 types.go diff --git a/aliyun/oss.go b/aliyun/oss.go new file mode 100644 index 0000000..c84f20a --- /dev/null +++ b/aliyun/oss.go @@ -0,0 +1,41 @@ +package aliyun + +import ( + "context" + "errors" + "io" + "time" + + "code.yun.ink/pkg/storagex" +) + +// 确保实现了 storage.Uploader 接口 +var _ storagex.Uploader = (*AliyunUploader)(nil) + +type AliyunFactory struct{} + +// Create 根据配置创建 AliyunUploader 实例 +func (f *AliyunFactory) Create(cfg storagex.Config) (storagex.Uploader, error) { + return &AliyunUploader{}, nil +} + +type AliyunUploader struct { + storagex.UploaderImpl +} + + +func (o *AliyunUploader) GetObject(ctx context.Context, objectKey string) (io.ReadCloser, error) { + return nil, errors.New("not implemented") +} + +func (o *AliyunUploader) PutObject(ctx context.Context, objectKey string, value io.Reader) (*storagex.PutResult, error) { + return nil, errors.New("not implemented") +} + +func (o *AliyunUploader) GetUrl(ctx context.Context, objectKey string, expire time.Duration) (string, error) { + return "", errors.New("not implemented") +} + +func (o *AliyunUploader) DeleteObject(ctx context.Context, objectKey string) error { + return errors.New("not implemented") +} diff --git a/aws/s3.go b/aws/s3.go new file mode 100644 index 0000000..0863e03 --- /dev/null +++ b/aws/s3.go @@ -0,0 +1,38 @@ +package aws + +import ( + "context" + "errors" + "io" + "time" + + "code.yun.ink/pkg/storagex" +) + +var _ storagex.Uploader = (*S3Uploader)(nil) + +type S3Factory struct{} + +func (f *S3Factory) Create(cfg storagex.Config) (storagex.Uploader, error) { + return &S3Uploader{}, nil +} + +type S3Uploader struct { + storagex.UploaderImpl +} + +func (o *S3Uploader) GetObject(ctx context.Context, objectKey string) (io.ReadCloser, error) { + return nil, errors.New("not implemented") +} + +func (o *S3Uploader) PutObject(ctx context.Context, objectKey string, value io.Reader) (*storagex.PutResult, error) { + return nil, errors.New("not implemented") +} + +func (o *S3Uploader) GetUrl(ctx context.Context, objectKey string, expire time.Duration) (string, error) { + return "", errors.New("not implemented") +} + +func (o *S3Uploader) DeleteObject(ctx context.Context, objectKey string) error { + return errors.New("not implemented") +} diff --git a/driver.go b/driver.go new file mode 100644 index 0000000..858ff53 --- /dev/null +++ b/driver.go @@ -0,0 +1,57 @@ +package storagex + +import ( + "context" + "errors" + "io" + "time" +) + +// Config 通用配置结构 (可根据需要扩展) +type Config struct { + Provider string // 服务商: "aliyun", "aws", "tencent" + AccountID string // 账号标识: "account_01", "finance_team" + Options map[string]string // 具体配置: AK, SK, Bucket, Region, Endpoint 等 +} + +// PutResult 上传成功后的返回信息 +type PutResult struct { + URL string // 文件访问地址 + Size int64 // 文件大小 + ETag string // 文件指纹/哈希 + Provider string // 服务商名称 + AccountID string // 使用的账号ID(用于追踪) +} + +type Uploader interface { + // GetObject 获取对象 + GetObject(ctx context.Context, objectKey string) (io.ReadCloser, error) + // GetUrl 获取对象URL地址 + GetUrl(ctx context.Context, objectKey string, expire time.Duration) (string, error) + // PutObject 写入对象 + PutObject(ctx context.Context, objectKey string, value io.Reader) (*PutResult, error) + // DeleteObject 删除对象 + DeleteObject(ctx context.Context, objectKey string) error +} + +type UploaderImpl struct{} + +func NewUploader() *UploaderImpl { + return &UploaderImpl{} +} + +func (o *UploaderImpl) GetObject(ctx context.Context, objectKey string) (io.ReadCloser, error) { + return nil, errors.New("not implemented") +} + +func (o *UploaderImpl) PutObject(ctx context.Context, objectKey string, value io.Reader) (*PutResult, error) { + return nil, errors.New("not implemented") +} + +func (o *UploaderImpl) GetUrl(ctx context.Context, objectKey string, expire time.Duration) (string, error) { + return "", errors.New("not implemented") +} + +func (o *UploaderImpl) DeleteObject(ctx context.Context, objectKey string) error { + return errors.New("not implemented") +} diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..000738b --- /dev/null +++ b/example/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "context" + "fmt" + "log" + "strings" + + "code.yun.ink/pkg/storagex" + "code.yun.ink/pkg/storagex/aliyun" + "code.yun.ink/pkg/storagex/aws" +) + +func init() { + // 在应用启动时注册所有支持的服务商 + storagex.Register("aliyun", &aliyun.AliyunFactory{}) + storagex.Register("aws", &aws.S3Factory{}) + // storagex.Register("tencent", &tencent.COSFactory{}) +} + +func main() { + // 场景 1: 使用阿里云账号 A 上传 + cfgAli := storagex.Config{ + Provider: "aliyun", + AccountID: "user_123", // 指定账号 + Options: map[string]string{ + "access_key": "LTAIxxxx", + "secret_key": "secretxxxx", + "bucket": "my-app-assets", + "endpoint": "oss-cn-hangzhou.aliyuncs.com", + }, + } + + // 获取上传通道 (自动处理单例复用) + uploader, err := storagex.GetUploader("aliyun", "user_123", cfgAli) + if err != nil { + log.Fatal(err) + } + + // 执行上传 + result, err := uploader.PutObject(context.Background(), "images/test.png", strings.NewReader("file content")) + if err != nil { + log.Fatal(err) + } + fmt.Printf("上传成功: %s\n", result.URL) + + // 场景 2: 使用 AWS 账号 B 上传 + // cfgAws := storagex.Config{ + // Provider: "aws", + // AccountID: "finance_dept", + // Options: map[string]string{ + // "region": "ap-northeast-1", + // "bucket": "finance-bucket", + // // AK/SK... + // }, + // } + + // 注意:这里会创建一个新的 S3 客户端,与阿里云互不干扰 + // awsUploader, _ := storagex.GetUploader("aws", "finance_dept", cfgAws) + // awsUploader.Upload(...) +} diff --git a/factory.go b/factory.go new file mode 100644 index 0000000..d1d6ba0 --- /dev/null +++ b/factory.go @@ -0,0 +1,78 @@ +package storagex + +import ( + "fmt" + "sync" +) + +// DriverFactory 驱动工厂接口 +type DriverFactory interface { + Create(cfg Config) (Uploader, error) +} + +// Registry 注册中心,单例模式管理所有上传通道 +type Registry struct { + mu sync.RWMutex + factories map[string]DriverFactory // 存储服务商工厂 (aliyun -> AliyunFactory) + clients map[string]Uploader // 缓存具体的客户端实例 (key = provider:accountID) +} + +// 全局注册中心实例 +var globalRegistry = &Registry{ + factories: make(map[string]DriverFactory), + clients: make(map[string]Uploader), +} + +// Register 注册服务商工厂 +// 例如: Register("aliyun", &AliyunFactory{}) +func Register(provider string, factory DriverFactory) { + globalRegistry.mu.Lock() + defer globalRegistry.mu.Unlock() + globalRegistry.factories[provider] = factory +} + +// GetUploader 获取指定服务商和账号的上传器 +// 如果该账号的客户端已存在则复用,不存在则创建 +func GetUploader(provider, accountID string, cfg Config) (Uploader, error) { + key := fmt.Sprintf("%s:%s", provider, accountID) + + // 1. 尝试从缓存获取 (读锁) + globalRegistry.mu.RLock() + client, ok := globalRegistry.clients[key] + globalRegistry.mu.RUnlock() + + if ok { + return client, nil + } + + // 2. 缓存未命中,需要创建 (写锁) + globalRegistry.mu.Lock() + defer globalRegistry.mu.Unlock() + + // 双重检查锁,防止并发下重复创建 + if client, ok := globalRegistry.clients[key]; ok { + return client, nil + } + + factory, ok := globalRegistry.factories[provider] + if !ok { + return nil, fmt.Errorf("unknown provider: %s", provider) + } + + // 创建新的 Uploader 实例 + client, err := factory.Create(cfg) + if err != nil { + return nil, fmt.Errorf("failed to create uploader for %s: %w", provider, err) + } + + // 存入缓存 + globalRegistry.clients[key] = client + return client, nil +} + +// Clear 主要用于测试,清空缓存 +func Clear() { + globalRegistry.mu.Lock() + defer globalRegistry.mu.Unlock() + globalRegistry.clients = make(map[string]Uploader) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..766c5c3 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module code.yun.ink/pkg/storagex + +go 1.25.1 diff --git a/tencent/cos.go b/tencent/cos.go new file mode 100644 index 0000000..8236a6b --- /dev/null +++ b/tencent/cos.go @@ -0,0 +1,15 @@ +package tencent + +import ( + "code.yun.ink/pkg/storagex" +) + +type TencentFactory struct{} + +func (f *TencentFactory) Create(cfg storagex.Config) (storagex.Uploader, error) { + return &TencentUploader{}, nil +} + +type TencentUploader struct { + storagex.UploaderImpl +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..d292c27 --- /dev/null +++ b/types.go @@ -0,0 +1 @@ +package storagex \ No newline at end of file