diff --git a/backend/models/storage.go b/backend/models/storage.go index 6ea72d1..5750012 100644 --- a/backend/models/storage.go +++ b/backend/models/storage.go @@ -20,12 +20,16 @@ type StorageConfig struct { func (s *StorageConfig) Sanitized() map[string]interface{} { return map[string]interface{}{ - "id": s.ID, - "name": s.Name, - "storageType": s.StorageType, - "endpoint": s.Endpoint, - "bucket": s.Bucket, - "region": s.Region, + "id": s.ID, + "name": s.Name, + "storageType": s.StorageType, + "endpoint": s.Endpoint, + "bucket": s.Bucket, + "region": s.Region, + // accessKey / secretKey 明文返回:本平台为内网运维工具,管理员需要在 + // 编辑弹框里看到并「测试连接」,脱敏会导致编辑时字段为空无法保存/测试。 + "accessKey": s.AccessKey, + "secretKey": s.SecretKey, "pathPrefix": s.PathPrefix, "insecure": s.Insecure, "isDefault": s.IsDefault, diff --git a/backend/services/storage_check.go b/backend/services/storage_check.go index febbfd3..597840f 100644 --- a/backend/services/storage_check.go +++ b/backend/services/storage_check.go @@ -204,9 +204,42 @@ func TestStorageConnection(p StorageTestParams) StorageTestResult { // 200=创建成功;409 BucketAlreadyOwnedByYou 视为已存在 if putResp.StatusCode == http.StatusOK || putResp.StatusCode == http.StatusConflict { + // 二次校验:不同对象存储的 CreateBucket 语义不一致。 + // 例如腾讯云 COS 要求桶名放在 Host(.cos..myqcloud.com)、 + // 路径为 /,直接 PUT / 可能返回 200 但并未真正建桶。 + // 因此重建后再 ListBuckets 确认桶确实出现,避免前端看到"假的创建成功"。 + verified := false + if vresp, verr := s3Request("GET", p.Endpoint, p.Region, p.AccessKey, p.SecretKey, "/", p.Insecure); verr == nil { + vbody, _ := io.ReadAll(io.LimitReader(vresp.Body, 1<<20)) + vresp.Body.Close() + var vlist struct { + Buckets struct { + Bucket []struct { + Name string `xml:"Name"` + } `xml:"Bucket"` + } `xml:"Buckets"` + } + _ = xml.Unmarshal(vbody, &vlist) + for _, b := range vlist.Buckets.Bucket { + if b.Name == p.Bucket { + verified = true + break + } + } + } + if !verified { + result.BucketExists = false + result.BucketCreated = false + result.Message = fmt.Sprintf( + "凭证有效,但桶「%s」创建后校验失败:桶列表里找不到它。"+ + "请确认 Endpoint / Region / 桶名是否正确"+ + "(腾讯云 COS 桶名需带 APPID,例如 mybucket-1250000000),"+ + "或先到云控制台手动创建该桶", p.Bucket) + return result + } result.BucketExists = true result.BucketCreated = true - result.Message = fmt.Sprintf("连接成功,桶「%s」不存在,已自动创建 ✓", p.Bucket) + result.Message = fmt.Sprintf("连接成功,桶「%s」不存在,已自动创建并校验通过 ✓", p.Bucket) return result } diff --git a/frontend/src/pages/storage/components/StorageFormDialog.tsx b/frontend/src/pages/storage/components/StorageFormDialog.tsx index b12e6c3..2c3e5f1 100644 --- a/frontend/src/pages/storage/components/StorageFormDialog.tsx +++ b/frontend/src/pages/storage/components/StorageFormDialog.tsx @@ -91,7 +91,10 @@ export const StorageFormDialog = memo(function StorageFormDialog({ form.reset(editing ? { name: editing.name, storageType: editing.storageType, endpoint: editing.endpoint || '', bucket: editing.bucket, - region: editing.region || '', secretKey: '', + region: editing.region || '', + // 后端已返回明文凭据,编辑时回填,避免每次都提示"请先填写 Access Key / Secret Key" + accessKey: editing.accessKey || '', + secretKey: editing.secretKey || '', pathPrefix: editing.pathPrefix || '', insecure: editing.insecure, isDefault: editing.isDefault, } as never : DEFAULT_VALUES) setTestResult(null) @@ -102,7 +105,14 @@ export const StorageFormDialog = memo(function StorageFormDialog({ const handleTest = async () => { const values = form.getValues() if (!values.bucket.trim() || !values.accessKey?.trim() || !values.secretKey?.trim()) { - toast.error('请先填写 Bucket 与 Access Key / Secret Key') + // 提示显示在弹框内(原来用 toast 弹到右下角,用户看不到是哪个字段的问题) + setTestResult({ + reachable: false, + authenticated: false, + bucketExists: false, + bucketCreated: false, + message: '请先填写 Bucket 与 Access Key / Secret Key', + }) return } setTesting(true) @@ -301,7 +311,7 @@ export const StorageFormDialog = memo(function StorageFormDialog({ name="secretKey" render={({ field }) => ( - Secret Key{editing && '(留空保持不变)'} + Secret Key diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 1541d08..afada41 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -101,6 +101,9 @@ export interface StorageConfig { pathPrefix: string; insecure: boolean; isDefault: boolean; + /** 明文返回,便于管理员在编辑弹框里查看/测试连接 */ + accessKey?: string; + secretKey?: string; hasSecretKey?: boolean; createdAt: string; updatedAt: string;