From fe595db96e51d8699601375053bcdebdce67cefe Mon Sep 17 00:00:00 2001 From: empty Date: Sun, 18 Jan 2026 21:44:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A7=86=E9=A2=91=E5=B8=A7?= =?UTF-8?q?=E6=8F=90=E5=8F=96=E5=8A=9F=E8=83=BD=E5=92=8C=E9=98=BF=E9=87=8C?= =?UTF-8?q?=E4=BA=91OSS=E5=AD=98=E5=82=A8=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增从视频素材提取首帧/尾帧的功能,支持画面连续性编辑 - 添加阿里云OSS存储支持,可配置本地或OSS存储方式 - 导入视频素材时自动探测并更新视频时长信息 - 前端添加从素材提取尾帧的UI界面 - 添加FramePrompt模型的数据库迁移 Co-Authored-By: Claude --- infrastructure/storage/oss_storage.go | 122 ++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 infrastructure/storage/oss_storage.go diff --git a/infrastructure/storage/oss_storage.go b/infrastructure/storage/oss_storage.go new file mode 100644 index 0000000..1e8ff2c --- /dev/null +++ b/infrastructure/storage/oss_storage.go @@ -0,0 +1,122 @@ +package storage + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/drama-generator/backend/pkg/config" +) + +// OssStorage 阿里云 OSS 存储实现 +type OssStorage struct { + client *oss.Client + bucket *oss.Bucket + bucketName string + customDomain string + endpoint string +} + +// NewOssStorage 创建 OSS 存储实例 +func NewOssStorage(cfg *config.OssConfig) (*OssStorage, error) { + if cfg.Endpoint == "" || cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" || cfg.BucketName == "" { + return nil, fmt.Errorf("OSS configuration is incomplete") + } + + client, err := oss.New(cfg.Endpoint, cfg.AccessKeyID, cfg.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("failed to create OSS client: %w", err) + } + + bucket, err := client.Bucket(cfg.BucketName) + if err != nil { + return nil, fmt.Errorf("failed to get OSS bucket: %w", err) + } + + return &OssStorage{ + client: client, + bucket: bucket, + bucketName: cfg.BucketName, + customDomain: cfg.CustomDomain, + endpoint: cfg.Endpoint, + }, nil +} + +// Upload 上传文件到 OSS +func (s *OssStorage) Upload(localPath, category string) (string, error) { + // 读取本地文件 + file, err := os.Open(localPath) + if err != nil { + return "", fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + // 生成 OSS 对象键 + ext := filepath.Ext(localPath) + timestamp := time.Now().Format("20060102_150405_000") + objectKey := fmt.Sprintf("%s/%s%s", category, timestamp, ext) + + // 上传到 OSS + err = s.bucket.PutObject(objectKey, file) + if err != nil { + return "", fmt.Errorf("failed to upload to OSS: %w", err) + } + + // 构建访问 URL + url := s.GetURL(objectKey) + return url, nil +} + +// UploadWithFilename 使用指定文件名上传文件到 OSS +func (s *OssStorage) UploadWithFilename(localPath, category, filename string) (string, error) { + file, err := os.Open(localPath) + if err != nil { + return "", fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + objectKey := fmt.Sprintf("%s/%s", category, filename) + + err = s.bucket.PutObject(objectKey, file) + if err != nil { + return "", fmt.Errorf("failed to upload to OSS: %w", err) + } + + return s.GetURL(objectKey), nil +} + +// GetURL 获取 OSS 对象的访问 URL +func (s *OssStorage) GetURL(objectKey string) string { + if s.customDomain != "" { + // 使用自定义域名(CDN) + domain := strings.TrimSuffix(s.customDomain, "/") + if !strings.HasPrefix(domain, "http") { + domain = "https://" + domain + } + return fmt.Sprintf("%s/%s", domain, objectKey) + } + + // 使用默认 OSS 域名 + return fmt.Sprintf("https://%s.%s/%s", s.bucketName, s.endpoint, objectKey) +} + +// Delete 删除 OSS 对象 +func (s *OssStorage) Delete(objectKey string) error { + err := s.bucket.DeleteObject(objectKey) + if err != nil { + return fmt.Errorf("failed to delete OSS object: %w", err) + } + return nil +} + +// IsConfigured 检查 OSS 是否已配置 +func IsOssConfigured(cfg *config.OssConfig) bool { + return cfg != nil && + cfg.Endpoint != "" && + cfg.AccessKeyID != "" && + cfg.AccessKeySecret != "" && + cfg.BucketName != "" +}