Files
open-im-server/pkg/common/db/obj/tx_oss.go
T
2023-07-04 12:23:58 +08:00

213 lines
6.0 KiB
Go

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
}