2023-07-04 12:23:58 +08:00

213 lines
6.0 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 obj
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"time"
"github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/OpenIMSDK/Open-IM-Server/pkg/common/config"
"github.com/tencentyun/cos-go-sdk-v5"
)
var conf = config.Config.Object.Tencent
func create_url(bucket, region, source string) string {
return fmt.Sprintf("https://%s.cos.%s.myqcloud.com/%s", bucket, region, source)
}
func NewCosClient() (Interface, error) {
u, err := url.Parse(create_url(conf.Bucket, conf.Region, ""))
if err != nil {
return nil, fmt.Errorf("tencent cos url parse %w", err)
}
b := &cos.BaseURL{BucketURL: u}
c := cos.NewClient(b, &http.Client{
Transport: &cos.AuthorizationTransport{
SecretID: conf.SecretID, // 用户的 SecretId建议使用子账号密钥授权遵循最小权限指引降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140
SecretKey: conf.SecretKey, // 用户的 SecretKey建议使用子账号密钥授权遵循最小权限指引降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140
},
})
return &cosImpl{
client: c,
tempBucket: conf.Bucket,
}, err
}
type cosImpl struct {
tempBucket string // 上传桶
//dataBucket string // 永久桶
urlstr string // 访问地址
client *cos.Client
}
func (c *cosImpl) Name() string {
return "tx_oss cos"
}
func (c *cosImpl) MinFragmentSize() int64 {
return 1024 * 1024 * 5 // 每个分片最小大小 tx_oss.absMinPartSize
}
func (c *cosImpl) MaxFragmentNum() int {
return 1000 // 最大分片数量 tx_oss.maxPartsCount
}
func (c *cosImpl) MinExpirationTime() time.Duration {
return time.Hour * 24
}
func (c *cosImpl) TempBucket() string {
return c.tempBucket
}
func (c *cosImpl) DataBucket() string {
//return c.dataBucket
return ""
}
func (c *cosImpl) PresignedGetURL(ctx context.Context, bucket string, name string, expires time.Duration, opt *HeaderOption) (string, error) {
// 参考文档https://cloud.tencent.com/document/product/436/14116
// 获取对象访问 URL用于匿名下载和分发
presignedGetURL, err := c.client.Object.GetPresignedURL(ctx, http.MethodGet, name, conf.SecretID, conf.SecretKey, time.Hour, nil)
if err != nil {
return "", err
}
return presignedGetURL.String(), nil
}
func (c *cosImpl) PresignedPutURL(ctx context.Context, args *ApplyPutArgs) (string, error) {
// 参考文档https://cloud.tencent.com/document/product/436/14114
if args.Effective <= 0 {
return "", errors.New("EffectiveTime <= 0")
}
_, err := c.GetObjectInfo(ctx, &BucketObject{
Bucket: c.tempBucket,
Name: args.Name,
})
if err == nil {
return "", fmt.Errorf("minio bucket %s name %s already exists", args.Bucket, args.Name)
} else if !c.IsNotFound(err) {
return "", err
}
// 获取预签名 URL
presignedPutURL, err := c.client.Object.GetPresignedURL(ctx, http.MethodPut, args.Name, conf.SecretID, conf.SecretKey, time.Hour, nil)
if err != nil {
return "", fmt.Errorf("minio apply error: %w", err)
}
return presignedPutURL.String(), nil
}
func (c *cosImpl) GetObjectInfo(ctx context.Context, args *BucketObject) (*ObjectInfo, error) {
// https://cloud.tencent.com/document/product/436/7745
// 新增参数 id 代表指定版本,如果不指定,默认查询对象最新版本
head, err := c.client.Object.Head(ctx, args.Name, nil, "")
if err != nil {
return nil, err
}
size, _ := strconv.ParseInt(head.Header.Get("Content-Length"), 10, 64)
return &ObjectInfo{
Size: size,
Hash: head.Header.Get("ETag"),
}, nil
}
func (c *cosImpl) CopyObject(ctx context.Context, src *BucketObject, dst *BucketObject) error {
srcURL := create_url(src.Bucket, conf.Region, src.Name)
_, _, err := c.client.Object.Copy(ctx, dst.Name, srcURL, nil)
if err == nil {
_, err = c.client.Object.Delete(ctx, srcURL, nil)
if err != nil {
// Error
}
}
return err
}
func (c *cosImpl) DeleteObject(ctx context.Context, info *BucketObject) error {
_, err := c.client.Object.Delete(ctx, info.Name)
return err
}
func (c *cosImpl) ComposeObject(ctx context.Context, src []BucketObject, dst *BucketObject) error {
//TODO implement me
panic("implement me")
}
func (c *cosImpl) IsNotFound(err error) bool {
ok, err := c.client.Object.IsExist(context.Background(), c.tempBucket)
if err == nil && ok {
fmt.Printf("object exists\n")
return true
} else if err != nil {
fmt.Printf("head object failed: %v\n", err)
return false
} else {
fmt.Printf("object does not exist\n")
return false
}
}
func (c *cosImpl) CheckName(name string) error {
return s3utils.CheckValidObjectName(name)
}
func (c *cosImpl) PutObject(ctx context.Context, info *BucketObject, reader io.Reader, size int64) (*ObjectInfo, error) {
/*// 采用高级接口, 无需用户指定 size
update, _, err := c.client.Object.Upload(
ctx, info.Name, info.Bucket, nil,
)
if err != nil {
return nil, err
}
return &ObjectInfo{
Hash: update.ETag,
}, nil*/
// Case1 使用 Put 上传对象
opt := &cos.ObjectPutOptions{
ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
ContentType: "text/html",
},
ACLHeaderOptions: &cos.ACLHeaderOptions{
// 如果不是必要操作,建议上传文件时不要给单个文件设置权限,避免达到限制。若不设置默认继承桶的权限。
XCosACL: "private",
},
}
resp, err := c.client.Object.Put(ctx, info.Name, reader, opt)
if err != nil {
return nil, err
}
return &ObjectInfo{
Hash: resp.Header.Get("ETag"),
}, nil
}
func (c *cosImpl) GetObject(ctx context.Context, info *BucketObject) (SizeReader, error) {
opt := &cos.MultiDownloadOptions{
ThreadPoolSize: 5,
}
update, err := c.client.Object.Download(
ctx, info.Name, info.Bucket, opt,
)
if err != nil {
return nil, err
}
size, _ := strconv.ParseInt(update.Header.Get("Content-Length"), 10, 64)
body := update.Body
if body == nil {
return nil, errors.New("response body is nil")
}
readCloser, ok := body.(io.ReadCloser)
if !ok {
return nil, errors.New("failed to convert response to ReadCloser")
}
return NewSizeReader(readCloser, size), nil
}