From bec09d5c1aac5dad063c1d747441e7943c180095 Mon Sep 17 00:00:00 2001 From: 521141 Date: Sat, 6 Jun 2026 06:48:38 +0000 Subject: [PATCH] feat(drivers): add Tencent COS (Cloud Object Storage) driver Add a new storage driver for Tencent Cloud Object Storage (COS) using the official cos-go-sdk-v5 SDK. Features: - List objects with pagination via Bucket.Get - Generate presigned download URLs via GetPresignedURL2 - Upload files with progress tracking and context cancellation - Copy/Move/Rename objects with server-side copy - Recursive directory operations (copy, delete) - Direct upload support with presigned PUT URLs - Custom domain support for download links - Configurable presigned URL expiration The driver follows the same patterns as the existing S3 driver, implementing driver.Driver and driver.Getter interfaces. --- drivers/all.go | 1 + drivers/tencent_cos/driver.go | 243 ++++++++++++++++++++++++++++++++++ drivers/tencent_cos/meta.go | 31 +++++ drivers/tencent_cos/types.go | 1 + drivers/tencent_cos/util.go | 162 +++++++++++++++++++++++ go.mod | 4 + go.sum | 13 ++ 7 files changed, 455 insertions(+) create mode 100644 drivers/tencent_cos/driver.go create mode 100644 drivers/tencent_cos/meta.go create mode 100644 drivers/tencent_cos/types.go create mode 100644 drivers/tencent_cos/util.go diff --git a/drivers/all.go b/drivers/all.go index 4af88dc00..02494bd74 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -71,6 +71,7 @@ import ( _ "github.com/OpenListTeam/OpenList/v4/drivers/strm" _ "github.com/OpenListTeam/OpenList/v4/drivers/teambition" _ "github.com/OpenListTeam/OpenList/v4/drivers/teldrive" + _ "github.com/OpenListTeam/OpenList/v4/drivers/tencent_cos" _ "github.com/OpenListTeam/OpenList/v4/drivers/terabox" _ "github.com/OpenListTeam/OpenList/v4/drivers/thunder" _ "github.com/OpenListTeam/OpenList/v4/drivers/thunder_browser" diff --git a/drivers/tencent_cos/driver.go b/drivers/tencent_cos/driver.go new file mode 100644 index 000000000..04f2f8ba6 --- /dev/null +++ b/drivers/tencent_cos/driver.go @@ -0,0 +1,243 @@ +package tencent_cos + +import ( + "bytes" + "context" + "fmt" + "net/http" + "net/url" + stdpath "path" + "time" + + "github.com/OpenListTeam/OpenList/v4/internal/driver" + "github.com/OpenListTeam/OpenList/v4/internal/errs" + "github.com/OpenListTeam/OpenList/v4/internal/model" + "github.com/OpenListTeam/OpenList/v4/internal/stream" + "github.com/OpenListTeam/OpenList/v4/server/common" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "github.com/tencentyun/cos-go-sdk-v5" +) + +type TencentCOS struct { + model.Storage + Addition + client *cos.Client + config driver.Config +} + +func (d *TencentCOS) Config() driver.Config { + return d.config +} + +func (d *TencentCOS) GetAddition() driver.Additional { + return &d.Addition +} + +func (d *TencentCOS) Init(ctx context.Context) error { + bucketURL, err := cos.NewBucketURL(d.Bucket, d.Region, true) + if err != nil { + return errors.Wrap(err, "failed to create bucket URL") + } + baseURL := &cos.BaseURL{ + BucketURL: bucketURL, + } + transport := &cos.AuthorizationTransport{ + SecretID: d.SecretID, + SecretKey: d.SecretKey, + } + httpClient := &http.Client{ + Transport: transport, + } + d.client = cos.NewClient(baseURL, httpClient) + return nil +} + +func (d *TencentCOS) Drop(ctx context.Context) error { + return nil +} + +func (d *TencentCOS) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { + return d.listObjects(ctx, dir.GetPath(), args) +} + +func (d *TencentCOS) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { + key := getKey(file.GetPath(), false) + fileName := stdpath.Base(key) + + var link model.Link + + if d.CustomHost != "" { + // Use custom host for generating link + u, err := d.client.Object.GetPresignedURL2(ctx, http.MethodGet, key, time.Hour*time.Duration(d.SignURLExpire), nil) + if err != nil { + return nil, errors.Wrap(err, "failed to generate presigned URL") + } + // Replace host with custom host + parsedURL, err := url.Parse(d.CustomHost) + if err == nil { + u.Scheme = parsedURL.Scheme + u.Host = parsedURL.Host + } + link.URL = u.String() + } else { + if common.ShouldProxy(d, fileName) { + // For proxied files, we need to sign the request but return it through proxy + u, err := d.client.Object.GetPresignedURL2(ctx, http.MethodGet, key, time.Hour*time.Duration(d.SignURLExpire), nil) + if err != nil { + return nil, errors.Wrap(err, "failed to generate presigned URL") + } + link.URL = u.String() + link.Header = http.Header{} + } else { + u, err := d.client.Object.GetPresignedURL2(ctx, http.MethodGet, key, time.Hour*time.Duration(d.SignURLExpire), nil) + if err != nil { + return nil, errors.Wrap(err, "failed to generate presigned URL") + } + link.URL = u.String() + } + } + return &link, nil +} + +func (d *TencentCOS) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { + return d.Put(ctx, &model.Object{ + Path: stdpath.Join(parentDir.GetPath(), dirName), + }, &stream.FileStream{ + Obj: &model.Object{ + Name: getPlaceholderName(d.Placeholder), + Modified: time.Now(), + }, + Reader: bytes.NewReader([]byte{}), + Mimetype: "application/octet-stream", + }, func(float64) {}) +} + +func (d *TencentCOS) Move(ctx context.Context, srcObj, dstDir model.Obj) error { + err := d.Copy(ctx, srcObj, dstDir) + if err != nil { + return err + } + return d.Remove(ctx, srcObj) +} + +func (d *TencentCOS) Rename(ctx context.Context, srcObj model.Obj, newName string) error { + err := d.copyObject(ctx, srcObj.GetPath(), stdpath.Join(stdpath.Dir(srcObj.GetPath()), newName), srcObj.IsDir()) + if err != nil { + return err + } + return d.Remove(ctx, srcObj) +} + +func (d *TencentCOS) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { + return d.copyObject(ctx, srcObj.GetPath(), stdpath.Join(dstDir.GetPath(), srcObj.GetName()), srcObj.IsDir()) +} + +func (d *TencentCOS) Remove(ctx context.Context, obj model.Obj) error { + if obj.IsDir() { + return d.removeDir(ctx, obj.GetPath()) + } + return d.removeFile(obj.GetPath()) +} + +func (d *TencentCOS) Put(ctx context.Context, dstDir model.Obj, s model.FileStreamer, up driver.UpdateProgress) error { + key := getKey(stdpath.Join(dstDir.GetPath(), s.GetName()), false) + contentType := s.GetMimetype() + log.Debugln("key:", key) + + opt := &cos.ObjectPutOptions{ + ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ + ContentType: contentType, + }, + } + + body := driver.NewLimitedUploadStream(ctx, &driver.ReaderUpdatingProgress{ + Reader: s, + UpdateProgress: up, + }) + + _, err := d.client.Object.Put(ctx, key, body, opt) + return err +} + +func (d *TencentCOS) GetDirectUploadTools() []string { + if !d.EnableDirectUpload { + return nil + } + return []string{"HttpDirect"} +} + +func (d *TencentCOS) GetDirectUploadInfo(ctx context.Context, _ string, dstDir model.Obj, fileName string, _ int64) (any, error) { + if !d.EnableDirectUpload { + return nil, errs.NotImplement + } + key := getKey(stdpath.Join(dstDir.GetPath(), fileName), false) + u, err := d.client.Object.GetPresignedURL2(ctx, http.MethodPut, key, time.Hour*time.Duration(d.SignURLExpire), nil) + if err != nil { + return nil, err + } + return &model.HttpDirectUploadInfo{ + UploadURL: u.String(), + Method: "PUT", + }, nil +} + +// Get implements driver.Getter interface +func (d *TencentCOS) Get(ctx context.Context, path string) (model.Obj, error) { + path = stdpath.Join(d.GetRootPath(), path) + key := getKey(path, false) + + // Try to get object as a file using HeadObject + resp, err := d.client.Object.Head(ctx, key, nil) + if err == nil { + fileName := stdpath.Base(path) + obj := &model.Object{ + Name: fileName, + Path: path, + } + // Parse Content-Length + if contentLength := resp.Header.Get("Content-Length"); contentLength != "" { + var size int64 + fmt.Sscanf(contentLength, "%d", &size) + obj.Size = size + } + // Parse Last-Modified + if lastModified := resp.Header.Get("Last-Modified"); lastModified != "" { + if t, err := time.Parse(time.RFC1123, lastModified); err == nil { + obj.Modified = t + } else if t, err := time.Parse(time.RFC1123Z, lastModified); err == nil { + obj.Modified = t + } + } + return obj, nil + } + + // If HeadObject fails, check if it's a directory by listing with prefix + if cos.IsNotFoundError(err) { + prefix := getKey(path, true) + opt := &cos.BucketGetOptions{ + Prefix: prefix, + Delimiter: "/", + MaxKeys: 1, + } + result, _, listErr := d.client.Bucket.Get(ctx, opt) + if listErr != nil { + return nil, errors.WithMessage(listErr, "failed to list objects with prefix") + } + if len(result.Contents) > 0 || len(result.CommonPrefixes) > 0 { + dirName := stdpath.Base(path) + return &model.Object{ + Name: dirName, + Modified: d.Modified, + IsFolder: true, + Path: path, + }, nil + } + return nil, errs.ObjectNotFound + } + + return nil, errors.WithMessage(err, "failed to head object") +} + +var _ driver.Driver = (*TencentCOS)(nil) +var _ driver.Getter = (*TencentCOS)(nil) diff --git a/drivers/tencent_cos/meta.go b/drivers/tencent_cos/meta.go new file mode 100644 index 000000000..7b15743e1 --- /dev/null +++ b/drivers/tencent_cos/meta.go @@ -0,0 +1,31 @@ +package tencent_cos + +import ( + "github.com/OpenListTeam/OpenList/v4/internal/driver" + "github.com/OpenListTeam/OpenList/v4/internal/op" +) + +type Addition struct { + driver.RootPath + Bucket string `json:"bucket" required:"true" help:"Bucket name in format: BucketName-APPID"` + Region string `json:"region" required:"true" help:"COS region, e.g. ap-beijing, ap-shanghai"` + SecretID string `json:"secret_id" required:"true"` + SecretKey string `json:"secret_key" required:"true"` + CustomHost string `json:"custom_host" help:"Custom domain for generating download links"` + SignURLExpire int `json:"sign_url_expire" type:"number" default:"4" help:"Presigned URL expiration time in hours"` + Placeholder string `json:"placeholder" help:"Placeholder file name for marking directories"` + EnableDirectUpload bool `json:"enable_direct_upload" default:"false"` +} + +func init() { + op.RegisterDriver(func() driver.Driver { + return &TencentCOS{ + config: driver.Config{ + Name: "TencnetCOS", + DefaultRoot: "/", + LocalSort: true, + CheckStatus: true, + }, + } + }) +} diff --git a/drivers/tencent_cos/types.go b/drivers/tencent_cos/types.go new file mode 100644 index 000000000..d22ad5134 --- /dev/null +++ b/drivers/tencent_cos/types.go @@ -0,0 +1 @@ +package tencent_cos diff --git a/drivers/tencent_cos/util.go b/drivers/tencent_cos/util.go new file mode 100644 index 000000000..68854c780 --- /dev/null +++ b/drivers/tencent_cos/util.go @@ -0,0 +1,162 @@ +package tencent_cos + +import ( + "context" + "errors" + "path" + "strings" + "time" + + "github.com/OpenListTeam/OpenList/v4/internal/model" + "github.com/OpenListTeam/OpenList/v4/internal/op" + log "github.com/sirupsen/logrus" + "github.com/tencentyun/cos-go-sdk-v5" +) + +// do others that not defined in Driver interface + +func getKey(filePath string, dir bool) string { + filePath = strings.TrimPrefix(filePath, "/") + if filePath != "" && dir { + filePath += "/" + } + return filePath +} + +var defaultPlaceholderName = ".openlist" + +func getPlaceholderName(placeholder string) string { + if placeholder == "" { + return defaultPlaceholderName + } + return placeholder +} + +func (d *TencentCOS) listObjects(ctx context.Context, dirPath string, args model.ListArgs) ([]model.Obj, error) { + prefix := getKey(dirPath, true) + log.Debugf("list: %s", prefix) + files := make([]model.Obj, 0) + marker := "" + for { + opt := &cos.BucketGetOptions{ + Prefix: prefix, + Delimiter: "/", + MaxKeys: 1000, + } + if marker != "" { + opt.Marker = marker + } + result, _, err := d.client.Bucket.Get(ctx, opt) + if err != nil { + return nil, err + } + for _, object := range result.CommonPrefixes { + name := path.Base(strings.TrimSuffix(object, "/")) + file := model.Object{ + Path: path.Join(dirPath, name), + Name: name, + Modified: d.Modified, + IsFolder: true, + } + files = append(files, &file) + } + for _, object := range result.Contents { + name := path.Base(object.Key) + if !args.S3ShowPlaceholder && (name == getPlaceholderName(d.Placeholder) || name == d.Placeholder) { + continue + } + file := model.Object{ + Path: path.Join(dirPath, name), + Name: name, + Size: object.Size, + Modified: d.Modified, + } + // Parse LastModified if available + if object.LastModified != "" { + if t, err := parseTime(object.LastModified); err == nil { + file.Modified = t + } + } + files = append(files, &file) + } + if !result.IsTruncated { + break + } + marker = result.NextMarker + } + return files, nil +} + +func parseTime(s string) (time.Time, error) { + // COS returns time in format: 2019-04-23T02:21:05.000Z + if t, err := time.Parse(time.RFC3339, s); err == nil { + return t, nil + } + if t, err := time.Parse("2006-01-02T15:04:05.000Z", s); err == nil { + return t, nil + } + return time.Time{}, errors.New("unable to parse time: " + s) +} + +func (d *TencentCOS) copyObject(ctx context.Context, src string, dst string, isDir bool) error { + if isDir { + return d.copyDir(ctx, src, dst) + } + return d.copyFile(ctx, src, dst) +} + +func (d *TencentCOS) copyFile(ctx context.Context, src string, dst string) error { + srcKey := getKey(src, false) + dstKey := getKey(dst, false) + // sourceURL format: / + sourceURL := d.Bucket + "/" + srcKey + _, _, err := d.client.Object.Copy(ctx, dstKey, sourceURL, nil) + return err +} + +func (d *TencentCOS) copyDir(ctx context.Context, src string, dst string) error { + objs, err := op.List(ctx, d, src, model.ListArgs{S3ShowPlaceholder: true}) + if err != nil { + return err + } + for _, obj := range objs { + cSrc := path.Join(src, obj.GetName()) + cDst := path.Join(dst, obj.GetName()) + if obj.IsDir() { + err = d.copyDir(ctx, cSrc, cDst) + } else { + err = d.copyFile(ctx, cSrc, cDst) + } + if err != nil { + return err + } + } + return nil +} + +func (d *TencentCOS) removeDir(ctx context.Context, src string) error { + objs, err := op.List(ctx, d, src, model.ListArgs{}) + if err != nil { + return err + } + for _, obj := range objs { + cSrc := path.Join(src, obj.GetName()) + if obj.IsDir() { + err = d.removeDir(ctx, cSrc) + } else { + err = d.removeFile(cSrc) + } + if err != nil { + return err + } + } + _ = d.removeFile(path.Join(src, getPlaceholderName(d.Placeholder))) + _ = d.removeFile(path.Join(src, d.Placeholder)) + return nil +} + +func (d *TencentCOS) removeFile(src string) error { + key := getKey(src, false) + _, err := d.client.Object.Delete(context.Background(), key) + return err +} diff --git a/go.mod b/go.mod index 098a75591..adb207c8f 100644 --- a/go.mod +++ b/go.mod @@ -102,6 +102,7 @@ require ( github.com/bradenaw/juniper v0.15.3 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/clbanning/mxj v1.8.4 // indirect github.com/cloudsoda/sddl v0.0.0-20250224235906-926454e91efc // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cronokirby/saferith v0.33.0 // indirect @@ -112,6 +113,7 @@ require ( github.com/geoffgarside/ber v1.2.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/go-querystring v1.0.0 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect @@ -123,6 +125,7 @@ require ( github.com/mikelolasagasti/xz v1.0.1 // indirect github.com/minio/minlz v1.0.0 // indirect github.com/minio/xxml v0.0.3 // indirect + github.com/mozillazg/go-httpheader v0.2.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/relvacode/iso8601 v1.6.0 // indirect @@ -285,6 +288,7 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/tencentyun/cos-go-sdk-v5 v0.7.73 github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect diff --git a/go.sum b/go.sum index 8ff7c863d..30fb71f89 100644 --- a/go.sum +++ b/go.sum @@ -208,6 +208,8 @@ github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyV github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= github.com/city404/v6-public-rpc-proto/go v0.0.0-20240817070657-90f8e24b653e h1:GLC8iDDcbt1H8+RkNao2nRGjyNTIo81e1rAJT9/uWYA= github.com/city404/v6-public-rpc-proto/go v0.0.0-20240817070657-90f8e24b653e/go.mod h1:ln9Whp+wVY/FTbn2SK0ag+SKD2fC0yQCF/Lqowc1LmU= +github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= +github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= @@ -338,6 +340,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -500,6 +504,7 @@ github.com/minio/xxml v0.0.3 h1:ZIpPQpfyG5uZQnqqC0LZuWtPk/WT8G/qkxvO6jb7zMU= github.com/minio/xxml v0.0.3/go.mod h1:wcXErosl6IezQIMEWSK/LYC2VS7LJ1dAkgvuyIN3aH4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -509,6 +514,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ= +github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= @@ -592,6 +599,7 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= @@ -641,6 +649,11 @@ github.com/taruti/bytepool v0.0.0-20160310082835-5e3a9ea56543 h1:6Y51mutOvRGRx6K github.com/taruti/bytepool v0.0.0-20160310082835-5e3a9ea56543/go.mod h1:jpwqYA8KUVEvSUJHkCXsnBRJCSKP1BMa81QZ6kvRpow= github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc= github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0= +github.com/tencentyun/cos-go-sdk-v5 v0.7.73 h1:uFfgp1A7cQaAGR6QP9DsIkoEQ67b8ewj5r1RV6XB540= +github.com/tencentyun/cos-go-sdk-v5 v0.7.73/go.mod h1:STbTNaNKq03u+gscPEGOahKzLcGSYOj6Dzc5zNay7Pg= +github.com/tencentyun/qcloud-cos-sts-sdk v0.0.0-20250515025012-e0eec8a5d123/go.mod h1:b18KQa4IxHbxeseW1GcZox53d7J0z39VNONTxvvlkXw= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=