89 lines
2.6 KiB
Go
89 lines
2.6 KiB
Go
// Package tencentcos 封装腾讯云 COS 对象上传能力。
|
||
package tencentcos
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
"time"
|
||
|
||
cos "github.com/tencentyun/cos-go-sdk-v5"
|
||
)
|
||
|
||
// Config 是 gateway 上传文件所需的 COS 最小配置。
|
||
type Config struct {
|
||
SecretID string
|
||
SecretKey string
|
||
Bucket string
|
||
Region string
|
||
AccessURL string
|
||
}
|
||
|
||
// Uploader 负责把 gateway 收到的文件流写入 COS,并返回 CDN/自定义域名访问地址。
|
||
type Uploader struct {
|
||
client *cos.Client
|
||
accessURL string
|
||
}
|
||
|
||
// NewUploader 按 bucket + region 装配 COS SDK client。
|
||
func NewUploader(cfg Config, transport http.RoundTripper) (*Uploader, error) {
|
||
cfg.SecretID = strings.TrimSpace(cfg.SecretID)
|
||
cfg.SecretKey = strings.TrimSpace(cfg.SecretKey)
|
||
cfg.Bucket = strings.TrimSpace(cfg.Bucket)
|
||
cfg.Region = strings.TrimSpace(cfg.Region)
|
||
cfg.AccessURL = strings.TrimRight(strings.TrimSpace(cfg.AccessURL), "/")
|
||
if cfg.SecretID == "" || cfg.SecretKey == "" || cfg.Bucket == "" || cfg.Region == "" || cfg.AccessURL == "" {
|
||
return nil, fmt.Errorf("tencent cos config is incomplete")
|
||
}
|
||
if _, err := url.ParseRequestURI(cfg.AccessURL); err != nil {
|
||
return nil, fmt.Errorf("tencent cos access_url is invalid: %w", err)
|
||
}
|
||
|
||
bucketURL, err := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", cfg.Bucket, cfg.Region))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("tencent cos bucket url is invalid: %w", err)
|
||
}
|
||
if transport == nil {
|
||
transport = http.DefaultTransport
|
||
}
|
||
|
||
client := cos.NewClient(&cos.BaseURL{BucketURL: bucketURL}, &http.Client{
|
||
Timeout: 30 * time.Second,
|
||
Transport: &cos.AuthorizationTransport{
|
||
SecretID: cfg.SecretID,
|
||
SecretKey: cfg.SecretKey,
|
||
Transport: transport,
|
||
},
|
||
})
|
||
|
||
return &Uploader{client: client, accessURL: cfg.AccessURL}, nil
|
||
}
|
||
|
||
// PutObject 上传对象并返回对外访问 URL。
|
||
func (u *Uploader) PutObject(ctx context.Context, key string, reader io.Reader, sizeBytes int64, contentType string) (string, error) {
|
||
key = strings.TrimLeft(strings.TrimSpace(key), "/")
|
||
if key == "" || reader == nil || sizeBytes <= 0 {
|
||
return "", fmt.Errorf("object upload input is invalid")
|
||
}
|
||
|
||
options := &cos.ObjectPutOptions{
|
||
ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
|
||
ContentLength: sizeBytes,
|
||
ContentType: strings.TrimSpace(contentType),
|
||
},
|
||
}
|
||
if _, err := u.client.Object.Put(ctx, key, reader, options); err != nil {
|
||
return "", err
|
||
}
|
||
|
||
publicURL, err := url.JoinPath(u.accessURL, key)
|
||
if err != nil {
|
||
return "", fmt.Errorf("build public object url: %w", err)
|
||
}
|
||
|
||
return publicURL, nil
|
||
}
|