Files
epic-go/internal/util/oss.go
hu xiaotong 7b7f8c31d7 i18n翻译
2025-07-17 15:36:24 +08:00

158 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package util
import (
"context"
"epic/internal/consts"
"fmt"
"io"
"net/http"
"os"
"sync"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
var (
s3Client *s3.Client
s3Once sync.Once
)
// getS3Client 获取S3客户端单例模式
func getS3Client() (*s3.Client, error) {
var err error
s3Once.Do(func() {
accessKey := consts.S3AccessKey
secretKey := consts.S3SecretKey
region := consts.S3Region
endpoint := consts.S3Endpoint
if accessKey == "" || secretKey == "" || endpoint == "" {
err = fmt.Errorf("missing S3 credentials or endpoint info in consts")
return
}
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
if service == s3.ServiceID {
return aws.Endpoint{URL: endpoint}, nil
}
return aws.Endpoint{}, fmt.Errorf("unknown service requested")
})
customProvider := credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")
cfg, cfgErr := config.LoadDefaultConfig(context.TODO(),
config.WithCredentialsProvider(customProvider),
config.WithEndpointResolverWithOptions(customResolver),
)
if cfgErr != nil {
err = fmt.Errorf("failed to load S3 config: %w", cfgErr)
return
}
cfg.Region = region
s3Client = s3.NewFromConfig(cfg)
})
return s3Client, err
}
// 下载网络图片并上传到OSS返回OSS路径
// imageUrl: 网络图片完整URL
// fileName: 上传到OSS的完整objectKey如 epic/image/hero/herocode.jpg
// 返回: OSS上的图片URL或错误
func DownloadAndUploadToOSS(ctx context.Context, imageUrl string, fileName string) (string, error) {
fmt.Printf("开始下载图片: %s\n", imageUrl)
resp, err := http.Get(imageUrl)
if err != nil {
Error(ctx, "下载图片失败", imageUrl, err)
return "", fmt.Errorf("failed to download image: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
Error(ctx, "下载图片状态码异常", imageUrl, resp.StatusCode)
return "", fmt.Errorf("failed to download image: status %d", resp.StatusCode)
}
tmpFile, err := os.CreateTemp("", "ossimg-*.tmp")
if err != nil {
Error(ctx, "创建临时文件失败", err)
return "", fmt.Errorf("failed to create temp file: %w", err)
}
tmpFilePath := tmpFile.Name()
defer func() {
tmpFile.Close()
os.Remove(tmpFilePath)
}()
_, err = io.Copy(tmpFile, resp.Body)
if err != nil {
Error(ctx, "保存图片到临时文件失败", tmpFilePath, err)
return "", fmt.Errorf("failed to save image to temp file: %w", err)
}
s3Client, err := getS3Client()
if err != nil {
Error(ctx, "获取S3客户端失败", err)
return "", fmt.Errorf("failed to get S3 client: %w", err)
}
bucket := consts.S3Bucket
if bucket == "" {
Error(ctx, "S3 bucket未配置")
return "", fmt.Errorf("missing S3 bucket info in consts")
}
tmpFile.Seek(0, io.SeekStart)
fmt.Printf("开始上传到OSS: bucket=%s, key=%s\n", bucket, fileName)
_, err = s3Client.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(fileName),
Body: tmpFile,
})
if err != nil {
Error(ctx, "上传到S3失败", fileName, err)
return "", fmt.Errorf("failed to upload to S3: %w", err)
}
ossUrl := fmt.Sprintf("%s/%s", consts.S3Endpoint, fileName)
fmt.Printf("上传成功OSS图片URL: %s\n", ossUrl)
return ossUrl, nil
}
// RefreshOssPresignedUrlCache 批量刷新OSS图片的预签名URL到Redis缓存
// keys: OSS对象key如 epic/hero/xxx.png
// expire: 预签名URL有效期建议30分钟-2小时
func RefreshOssPresignedUrlCache(ctx context.Context, keys []string, expire time.Duration) error {
s3Client, err := getS3Client()
if err != nil {
return err
}
bucket := consts.S3Bucket
presignClient := s3.NewPresignClient(s3Client)
for _, key := range keys {
presignResult, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: &key,
}, func(opts *s3.PresignOptions) {
opts.Expires = expire
})
if err != nil {
Error(ctx, "生成预签名URL失败", key, err)
continue
}
// 缓存到Rediskey可加前缀区分
cacheKey := "oss:presignurl:" + key
if err := RedisCache.Set(ctx, cacheKey, presignResult.URL, expire); err != nil {
Error(ctx, "写入Redis失败", cacheKey, err)
} else {
// 打印预签名可访问地址
fmt.Printf("预签名可访问地址: %s\n", presignResult.URL)
}
}
return nil
}