From c1b7a9fbd1bc6588fb1fbfe61826deaeff6cfefc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sat, 24 Jan 2026 22:41:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E5=86=99s3=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 23 +--- go.sum | 42 +----- internal/data/backup.go | 1 + internal/service/backup_storage.go | 1 + pkg/storage/s3.go | 193 ++++++++++++--------------- pkg/types/backup.go | 1 + web/src/views/backup/StorageView.vue | 9 ++ 7 files changed, 103 insertions(+), 167 deletions(-) diff --git a/go.mod b/go.mod index 59963f34..763d6c72 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,9 @@ module github.com/acepanel/panel -go 1.25 +go 1.25.6 require ( github.com/DeRuina/timberjack v1.3.9 - github.com/aws/aws-sdk-go-v2 v1.41.1 - github.com/aws/aws-sdk-go-v2/config v1.32.7 - github.com/aws/aws-sdk-go-v2/credentials v1.19.7 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.21.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.95.1 github.com/bddjr/hlfhr v1.4.0 github.com/beevik/ntp v1.5.0 github.com/coder/websocket v1.8.14 @@ -52,6 +47,7 @@ require ( github.com/orandin/slog-gorm v1.4.0 github.com/pkg/sftp v1.13.10 github.com/pquerna/otp v1.5.0 + github.com/rhnvrm/simples3 v0.11.1 github.com/robfig/cron/v3 v3.0.1 github.com/samber/lo v1.52.0 github.com/sethvargo/go-limiter v1.1.0 @@ -71,21 +67,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/G-Core/gcore-dns-sdk-go v0.3.3 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect - github.com/aws/smithy-go v1.24.0 // indirect github.com/boombuler/barcode v1.1.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/distribution/reference v0.6.0 // indirect diff --git a/go.sum b/go.sum index 776ca93f..6f2e3f13 100644 --- a/go.sum +++ b/go.sum @@ -27,46 +27,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= -github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= -github.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY= -github.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY= -github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8= -github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.21.0 h1:pQZGI0qQXeCHZHMeWzhwPu+4jkWrdrIb2dgpG4OKmco= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.21.0/go.mod h1:XGq5kImVqQT4HUNbbG+0Y8O74URsPNH7CGPg1s1HW5E= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 h1:JqcdRG//czea7Ppjb+g/n4o8i/R50aTBHkA7vu0lK+k= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17/go.mod h1:CO+WeGmIdj/MlPel2KwID9Gt7CNq4M65HUfBW97liM0= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 h1:Z5EiPIzXKewUQK0QTMkutjiaPVeVYXX7KIqhXu/0fXs= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8/go.mod h1:FsTpJtvC4U1fyDXk7c71XoDv3HlRm8V3NiYLeYLh5YE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 h1:bGeHBsGZx0Dvu/eJC0Lh9adJa3M1xREcndxLNZlve2U= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17/go.mod h1:dcW24lbU0CzHusTE8LLHhRLI42ejmINN8Lcr22bwh/g= -github.com/aws/aws-sdk-go-v2/service/s3 v1.95.1 h1:C2dUPSnEpy4voWFIq3JNd8gN0Y5vYGDo44eUE58a/p8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.95.1/go.mod h1:5jggDlZ2CLQhwJBiZJb4vfk4f0GxWdEDruWKEJ1xOdo= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ= -github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= -github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/bddjr/hlfhr v1.4.0 h1:EVryUs0mLzQ455bAIKhASqwxFV9IYrGOFjuz0HE6oYE= github.com/bddjr/hlfhr v1.4.0/go.mod h1:oyIv4Q9JpCgZFdtH3KyTNWp7YYRWl4zl8k4ozrMAB4g= github.com/beevik/ntp v1.5.0 h1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4= @@ -333,6 +293,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rhnvrm/simples3 v0.11.1 h1:/IU3Jz7R3oSJfafPvXemwSZvh0wZ1nFcc2CceMmloU4= +github.com/rhnvrm/simples3 v0.11.1/go.mod h1:c2xW30bukipkBlWNnXG1wDjq3gykQ6ww2AB/9NHMLMY= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= diff --git a/internal/data/backup.go b/internal/data/backup.go index 06fd2aab..ec5c0b06 100644 --- a/internal/data/backup.go +++ b/internal/data/backup.go @@ -405,6 +405,7 @@ func (r *backupRepo) getStorage(backupStorage biz.BackupStorage) (storage.Storag AccessKeyID: backupStorage.Info.AccessKey, SecretAccessKey: backupStorage.Info.SecretKey, Endpoint: backupStorage.Info.Endpoint, + Scheme: backupStorage.Info.Scheme, BasePath: backupStorage.Info.Path, AddressingStyle: storage.S3AddressingStyle(backupStorage.Info.Style), }) diff --git a/internal/service/backup_storage.go b/internal/service/backup_storage.go index da59fbb8..80756fad 100644 --- a/internal/service/backup_storage.go +++ b/internal/service/backup_storage.go @@ -129,6 +129,7 @@ func (s *BackupStorageService) validateStorage(accountType string, info types.Ba AccessKeyID: info.AccessKey, SecretAccessKey: info.SecretKey, Endpoint: info.Endpoint, + Scheme: info.Scheme, BasePath: info.Path, AddressingStyle: storage.S3AddressingStyle(info.Style), }) diff --git a/pkg/storage/s3.go b/pkg/storage/s3.go index bf66d486..623d4e87 100644 --- a/pkg/storage/s3.go +++ b/pkg/storage/s3.go @@ -1,18 +1,13 @@ package storage import ( - "context" "fmt" "io" + "strconv" "strings" "time" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/credentials" - "github.com/aws/aws-sdk-go-v2/feature/s3/manager" - "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/rhnvrm/simples3" ) // S3AddressingStyle S3 地址模式 @@ -31,56 +26,57 @@ type S3Config struct { AccessKeyID string // 访问密钥 ID SecretAccessKey string // 访问密钥 Endpoint string // 自定义端点 + Scheme string // 协议 http 或 https BasePath string // 基础路径前缀 AddressingStyle S3AddressingStyle // 地址模式 } type S3 struct { - client *s3.Client + client *simples3.S3 config S3Config + bucket string // bucket 用于 API 调用 } func NewS3(cfg S3Config) (Storage, error) { if cfg.AddressingStyle == "" { cfg.AddressingStyle = S3AddressingStyleVirtualHosted } + if cfg.Scheme == "" { + cfg.Scheme = "https" + } cfg.BasePath = strings.Trim(cfg.BasePath, "/") - var awsCfg aws.Config - var err error + client := simples3.New(cfg.Region, cfg.AccessKeyID, cfg.SecretAccessKey) - awsCfg, err = config.LoadDefaultConfig(context.TODO(), - config.WithRegion(cfg.Region), - config.WithCredentialsProvider( - credentials.NewStaticCredentialsProvider(cfg.AccessKeyID, cfg.SecretAccessKey, ""), - ), - config.WithRequestChecksumCalculation(aws.RequestChecksumCalculationWhenRequired), - config.WithResponseChecksumValidation(aws.ResponseChecksumValidationWhenRequired), - config.WithRetryMaxAttempts(10), - ) + // bucket 用于 API 调用 + // Virtual Hosted Style 时 bucket 已在 endpoint 中,API 调用时传空 + // Path Style 时 bucket 需要作为路径的一部分 + bucket := cfg.Bucket - if err != nil { - return nil, fmt.Errorf("failed to load AWS config: %w", err) - } - - var client *s3.Client if cfg.Endpoint != "" { - // 自定义端点 - client = s3.NewFromConfig(awsCfg, func(o *s3.Options) { - o.UsePathStyle = cfg.AddressingStyle == S3AddressingStylePath - o.BaseEndpoint = aws.String(cfg.Endpoint) - }) + // 自定义 Endpoint + if cfg.AddressingStyle == S3AddressingStyleVirtualHosted { + // Virtual Hosted Style: https://{bucket}.{endpoint}/{key} + client.SetEndpoint(fmt.Sprintf("%s://%s.%s", cfg.Scheme, cfg.Bucket, cfg.Endpoint)) + bucket = "" + } else { + // Path Style: https://{endpoint}/{bucket}/{key} + client.SetEndpoint(fmt.Sprintf("%s://%s", cfg.Scheme, cfg.Endpoint)) + } } else { - // 标准 AWS S3 - client = s3.NewFromConfig(awsCfg, func(o *s3.Options) { - o.UsePathStyle = cfg.AddressingStyle == S3AddressingStylePath - }) + // AWS S3 + if cfg.AddressingStyle == S3AddressingStyleVirtualHosted { + // Virtual Hosted Style: https://{bucket}.s3.{region}.amazonaws.com/{key} + client.SetEndpoint(fmt.Sprintf("https://%s.s3.%s.amazonaws.com", cfg.Bucket, cfg.Region)) + bucket = "" + } } return &S3{ client: client, config: cfg, + bucket: bucket, }, nil } @@ -91,42 +87,27 @@ func (s *S3) Delete(files ...string) error { } // 批量删除 - var objects []types.ObjectIdentifier + var objects []string for _, file := range files { key := s.getKey(file) - objects = append(objects, types.ObjectIdentifier{ - Key: aws.String(key), - }) + objects = append(objects, key) } - _, err := s.client.DeleteObjects(context.TODO(), &s3.DeleteObjectsInput{ - Bucket: aws.String(s.config.Bucket), - Delete: &types.Delete{ - Objects: objects, - }, + _, err := s.client.DeleteObjects(simples3.DeleteObjectsInput{ + Bucket: s.bucket, + Objects: objects, + Quiet: true, }) - waiter := s3.NewObjectNotExistsWaiter(s.client) - for _, file := range files { - key := s.getKey(file) - err = waiter.Wait(context.TODO(), &s3.HeadObjectInput{ - Bucket: aws.String(s.config.Bucket), - Key: aws.String(key), - }, 30*time.Second) - if err != nil { - return err - } - } - return err } // Exists 检查文件是否存在 func (s *S3) Exists(file string) bool { key := s.getKey(file) - _, err := s.client.HeadObject(context.TODO(), &s3.HeadObjectInput{ - Bucket: aws.String(s.config.Bucket), - Key: aws.String(key), + _, err := s.client.FileDetails(simples3.DetailsInput{ + Bucket: s.bucket, + ObjectKey: key, }) return err == nil } @@ -134,18 +115,29 @@ func (s *S3) Exists(file string) bool { // LastModified 获取文件最后修改时间 func (s *S3) LastModified(file string) (time.Time, error) { key := s.getKey(file) - output, err := s.client.HeadObject(context.TODO(), &s3.HeadObjectInput{ - Bucket: aws.String(s.config.Bucket), - Key: aws.String(key), + output, err := s.client.FileDetails(simples3.DetailsInput{ + Bucket: s.bucket, + ObjectKey: key, }) if err != nil { return time.Time{}, err } - if output.LastModified != nil { - return *output.LastModified, nil + if output.LastModified == "" { + return time.Time{}, nil } - return time.Time{}, nil + + // 解析 HTTP 日期格式 + t, err := time.Parse(time.RFC1123, output.LastModified) + if err != nil { + // 尝试其他格式 + t, err = time.Parse(time.RFC1123Z, output.LastModified) + if err != nil { + return time.Time{}, fmt.Errorf("failed to parse LastModified: %w", err) + } + } + + return t, nil } // List 列出目录下的所有文件 @@ -156,31 +148,29 @@ func (s *S3) List(path string) ([]string, error) { } var files []string - paginator := s3.NewListObjectsV2Paginator(s.client, &s3.ListObjectsV2Input{ - Bucket: aws.String(s.config.Bucket), - Prefix: aws.String(prefix), - Delimiter: aws.String("/"), + seq, finish := s.client.ListAll(simples3.ListInput{ + Bucket: s.bucket, + Prefix: prefix, + Delimiter: "/", }) - for paginator.HasMorePages() { - page, err := paginator.NextPage(context.TODO()) - if err != nil { - return nil, err + for obj := range seq { + key := obj.Key + // 跳过目录本身 + if key == prefix { + continue } - for _, obj := range page.Contents { - key := aws.ToString(obj.Key) - // 跳过目录本身 - if key == prefix { - continue - } - // 提取文件名 - name := strings.TrimPrefix(key, prefix) - if name != "" && !strings.Contains(name, "/") { - files = append(files, name) - } + // 提取文件名 + name := strings.TrimPrefix(key, prefix) + if name != "" && !strings.Contains(name, "/") { + files = append(files, name) } } + if err := finish(); err != nil { + return nil, err + } + return files, nil } @@ -188,27 +178,13 @@ func (s *S3) List(path string) ([]string, error) { func (s *S3) Put(file string, content io.Reader) error { key := s.getKey(file) - // For S3-compatible providers, disable automatic checksum calculation on the Uploader. - // The S3 client's RequestChecksumCalculation setting only affects single-part uploads. - // Multipart uploads via the Uploader require this separate setting (added in s3/manager v1.20.0). - // See: https://github.com/aws/aws-sdk-go-v2/issues/3007 - uploader := manager.NewUploader(s.client, func(u *manager.Uploader) { - u.RequestChecksumCalculation = aws.RequestChecksumCalculationWhenRequired + _, err := s.client.FileUploadMultipart(simples3.MultipartUploadInput{ + Bucket: s.bucket, + ObjectKey: key, + ContentType: "application/octet-stream", + Body: content, + Concurrency: 5, }) - _, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{ - Bucket: aws.String(s.config.Bucket), - Key: aws.String(key), - Body: content, - }) - if err != nil { - return err - } - - waiter := s3.NewObjectExistsWaiter(s.client) - err = waiter.Wait(context.TODO(), &s3.HeadObjectInput{ - Bucket: aws.String(s.config.Bucket), - Key: aws.String(key), - }, 30*time.Second) return err } @@ -216,15 +192,20 @@ func (s *S3) Put(file string, content io.Reader) error { // Size 获取文件大小 func (s *S3) Size(file string) (int64, error) { key := s.getKey(file) - output, err := s.client.HeadObject(context.TODO(), &s3.HeadObjectInput{ - Bucket: aws.String(s.config.Bucket), - Key: aws.String(key), + output, err := s.client.FileDetails(simples3.DetailsInput{ + Bucket: s.bucket, + ObjectKey: key, }) if err != nil { return 0, err } - return aws.ToInt64(output.ContentLength), nil + size, err := strconv.ParseInt(output.ContentLength, 10, 64) + if err != nil { + return 0, fmt.Errorf("failed to parse ContentLength: %w", err) + } + + return size, nil } // getKey 获取完整的对象键 diff --git a/pkg/types/backup.go b/pkg/types/backup.go index 30c0881d..5b3be49e 100644 --- a/pkg/types/backup.go +++ b/pkg/types/backup.go @@ -9,6 +9,7 @@ type BackupStorageInfo struct { Style string `json:"style"` // virtual-hosted, path Region string `json:"region"` // 地区 Endpoint string `json:"endpoint"` // 端点 + Scheme string `json:"scheme"` // http, https Bucket string `json:"bucket"` // 存储桶 // SFTP / WebDAV diff --git a/web/src/views/backup/StorageView.vue b/web/src/views/backup/StorageView.vue index 019a2b77..6cb98c6a 100644 --- a/web/src/views/backup/StorageView.vue +++ b/web/src/views/backup/StorageView.vue @@ -21,6 +21,11 @@ const styleOptions = [ { label: 'Path', value: 'path' } ] +const schemeOptions = [ + { label: 'HTTPS', value: 'https' }, + { label: 'HTTP', value: 'http' } +] + const sftpAuthOptions = [ { label: $gettext('Password'), value: 'password' }, { label: $gettext('Private Key'), value: 'private_key' } @@ -35,6 +40,7 @@ const defaultModel = { style: 'virtual_hosted', region: '', endpoint: '', + scheme: 'https', bucket: '', host: '', port: 22, @@ -253,6 +259,9 @@ onMounted(() => { :placeholder="$gettext('Enter endpoint URL')" /> + + +