- 新增从视频素材提取首帧/尾帧的功能,支持画面连续性编辑 - 添加阿里云OSS存储支持,可配置本地或OSS存储方式 - 导入视频素材时自动探测并更新视频时长信息 - 前端添加从素材提取尾帧的UI界面 - 添加FramePrompt模型的数据库迁移 Co-Authored-By: Claude <noreply@anthropic.com>
123 lines
3.1 KiB
Go
123 lines
3.1 KiB
Go
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 != ""
|
||
}
|