112 lines
2.7 KiB
Go
112 lines
2.7 KiB
Go
package oss
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"goalfymax-admin/internal/config"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
)
|
|
|
|
type s3Client struct {
|
|
client *s3.Client
|
|
presign *s3.PresignClient
|
|
cfg config.OssConfig
|
|
}
|
|
|
|
var (
|
|
clientOnce sync.Once
|
|
c *s3Client
|
|
)
|
|
|
|
func initClient() error {
|
|
var initErr error
|
|
clientOnce.Do(func() {
|
|
cfg := config.GetConfig().Oss
|
|
if cfg.Region == "" || cfg.Bucket == "" || cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" {
|
|
initErr = fmt.Errorf("OSS未配置: region/bucket/ak/sk 不能为空")
|
|
return
|
|
}
|
|
|
|
awsCfg := aws.Config{
|
|
Region: cfg.Region,
|
|
Credentials: credentials.NewStaticCredentialsProvider(cfg.AccessKeyID, cfg.AccessKeySecret, ""),
|
|
}
|
|
|
|
// 如果配置了自定义 endpoint (如阿里云 OSS),则设置 endpoint resolver
|
|
if cfg.Endpoint != "" {
|
|
awsCfg.BaseEndpoint = aws.String("https://" + cfg.Endpoint)
|
|
}
|
|
|
|
// 创建 S3 客户端,使用虚拟主机样式(阿里云 OSS 要求)
|
|
s3c := s3.NewFromConfig(awsCfg, func(o *s3.Options) {
|
|
// 阿里云 OSS 使用虚拟主机样式: bucket.endpoint/key
|
|
// AWS S3 默认也是虚拟主机样式,所以保持 UsePathStyle = false (默认值)
|
|
o.UsePathStyle = false
|
|
})
|
|
|
|
c = &s3Client{
|
|
client: s3c,
|
|
presign: s3.NewPresignClient(s3c),
|
|
cfg: cfg,
|
|
}
|
|
})
|
|
return initErr
|
|
}
|
|
|
|
// GetPresignedGetURL 生成S3对象的预签名下载URL
|
|
func GetPresignedGetURL(ctx context.Context, key string) (string, error) {
|
|
if err := initClient(); err != nil {
|
|
return "", err
|
|
}
|
|
expire := c.cfg.PresignUrlExpire
|
|
if expire <= 0 {
|
|
expire = 10 * time.Minute
|
|
}
|
|
req, err := c.presign.PresignGetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: aws.String(c.cfg.Bucket),
|
|
Key: aws.String(key),
|
|
}, func(po *s3.PresignOptions) {
|
|
po.Expires = expire
|
|
})
|
|
if err != nil {
|
|
return "", fmt.Errorf("生成预签名URL失败: %w", err)
|
|
}
|
|
return req.URL, nil
|
|
}
|
|
|
|
// DownloadFileContent 从S3下载文件内容和MIME类型
|
|
func DownloadFileContent(ctx context.Context, key string) ([]byte, string, error) {
|
|
if err := initClient(); err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
resp, err := c.client.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: aws.String(c.cfg.Bucket),
|
|
Key: aws.String(key),
|
|
})
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("从S3下载文件失败: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
buf := new(bytes.Buffer)
|
|
_, err = buf.ReadFrom(resp.Body)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("读取文件内容失败: %w", err)
|
|
}
|
|
|
|
mimeType := "application/octet-stream" // 默认MIME类型
|
|
if resp.ContentType != nil && *resp.ContentType != "" {
|
|
mimeType = *resp.ContentType
|
|
}
|
|
|
|
return buf.Bytes(), mimeType, nil
|
|
}
|