i18n翻译
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
@@ -16,22 +16,68 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
)
|
||||
|
||||
// DownloadAndUploadToOSS 下载网络图片并上传到OSS,返回OSS路径
|
||||
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) (string, error) {
|
||||
// 1. 下载 imageUrl 到本地临时文件
|
||||
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()
|
||||
@@ -42,57 +88,70 @@ func DownloadAndUploadToOSS(ctx context.Context, imageUrl string) (string, error
|
||||
|
||||
_, 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)
|
||||
}
|
||||
|
||||
// 2. 上传临时文件到OSS,获取OSS路径
|
||||
accessKey := consts.S3AccessKey
|
||||
secretKey := consts.S3SecretKey
|
||||
bucket := consts.S3Bucket
|
||||
region := consts.S3Region
|
||||
endpoint := consts.S3Endpoint
|
||||
if accessKey == "" || secretKey == "" || bucket == "" || endpoint == "" {
|
||||
return "", fmt.Errorf("missing S3 credentials or bucket info in consts")
|
||||
}
|
||||
|
||||
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, err := config.LoadDefaultConfig(ctx,
|
||||
config.WithCredentialsProvider(customProvider),
|
||||
config.WithEndpointResolverWithOptions(customResolver),
|
||||
)
|
||||
s3Client, err := getS3Client()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to load S3 config: %w", err)
|
||||
Error(ctx, "获取S3客户端失败", err)
|
||||
return "", fmt.Errorf("failed to get S3 client: %w", err)
|
||||
}
|
||||
cfg.Region = region
|
||||
s3Client := s3.NewFromConfig(cfg)
|
||||
|
||||
// 生成唯一的 object key
|
||||
fileName := filepath.Base(tmpFilePath)
|
||||
objectKey := fmt.Sprintf("images/%d_%s", time.Now().UnixNano(), fileName)
|
||||
bucket := consts.S3Bucket
|
||||
if bucket == "" {
|
||||
Error(ctx, "S3 bucket未配置")
|
||||
return "", fmt.Errorf("missing S3 bucket info in consts")
|
||||
}
|
||||
|
||||
tmpFile.Seek(0, io.SeekStart)
|
||||
_, err = tmpFile.Stat()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to stat temp file: %w", err)
|
||||
}
|
||||
|
||||
// 上传
|
||||
fmt.Printf("开始上传到OSS: bucket=%s, key=%s\n", bucket, fileName)
|
||||
_, err = s3Client.PutObject(ctx, &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(objectKey),
|
||||
Key: aws.String(fileName),
|
||||
Body: tmpFile,
|
||||
})
|
||||
if err != nil {
|
||||
Error(ctx, "上传到S3失败", fileName, err)
|
||||
return "", fmt.Errorf("failed to upload to S3: %w", err)
|
||||
}
|
||||
|
||||
// 4. 返回OSS路径(拼接URL)
|
||||
ossUrl := fmt.Sprintf("%s/%s/%s", endpoint, bucket, objectKey)
|
||||
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
|
||||
}
|
||||
// 缓存到Redis,key可加前缀区分
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user