From 04c6a081ae451bc001b0fc897ab6f1279d5ed1c1 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Mon, 24 Aug 2026 13:25:54 +0800 Subject: [PATCH] =?UTF-8?q?fix=F0=9F=90=9B:=20source=3D3=20uploaded=20to?= =?UTF-8?q?=20aliyun,=20and=20neither=20provider=20was=20ever=20configured?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit thirdUpload dispatched on the source parameter and then built the same zero-value ALiYunOSS in both branches, so source=3 could not have reached qiniu even with credentials. Neither branch had credentials to use. OXS.Setup is the initialisation path and nothing in the repository called it, and no configuration field existed to fill. The store is now taken from extend.fileStore, and a provider that was not configured says so rather than producing the provider's own complaint about an empty bucket name. The two handlers passed errors.New("") to e.Error, discarding what actually went wrong; they now pass the error. --- app/other/apis/file.go | 39 ++++++++++++++++++++++++----------- app/other/apis/upload_test.go | 38 ++++++++++++++++++++++++++++++++++ config/extend.go | 33 +++++++++++++++++++++++++---- config/extend_test.go | 16 ++++++++++++++ config/settings.full.yml | 14 +++++++++++++ 5 files changed, 124 insertions(+), 16 deletions(-) create mode 100644 app/other/apis/upload_test.go create mode 100644 config/extend_test.go diff --git a/app/other/apis/file.go b/app/other/apis/file.go index 8cea78b4..fe8f56d2 100644 --- a/app/other/apis/file.go +++ b/app/other/apis/file.go @@ -14,6 +14,7 @@ import ( "github.com/google/uuid" "go-admin/common/file_store" + "go-admin/config" ) type FileResponse struct { @@ -95,7 +96,7 @@ func (e File) baseImg(c *gin.Context, fileResponse FileResponse, urlPrefix strin source, _ := c.GetPostForm("source") if err := thirdUpload(source, fileName, base64File); err != nil { - e.Error(200, errors.New(""), "上传第三方失败") + e.Error(200, err, "上传第三方失败") return fileResponse } @@ -126,7 +127,7 @@ func (e File) multipleFile(c *gin.Context, urlPrefix string) []FileResponse { fileType, _ := utils.GetType(multipartFileName) if err := thirdUpload(source, fileName, multipartFileName); err != nil { - e.Error(500, errors.New(""), "上传第三方失败") + e.Error(500, err, "上传第三方失败") continue } @@ -176,22 +177,36 @@ func (e File) buildFileResponse(filePath, urlPrefix, fileName, fileType string) } } +// thirdUpload copies the file that was already stored locally to the object +// store the request asked for. source "1", and anything unrecognised, keeps the +// local copy only. +// +// Both branches used to construct a zero-value ALiYunOSS and call UpLoad on it, +// which panicked - and the qiniu branch constructed the aliyun client, so +// source=3 never reached qiniu even in principle. func thirdUpload(source string, name string, path string) error { switch source { case "2": - return ossUpload("img/"+name, path) + return upload(file_store.AliYunOSS, config.ExtConfig.FileStore.AliYun, "img/"+name, path) case "3": - return qiniuUpload("img/"+name, path) + return upload(file_store.QiNiuKodo, config.ExtConfig.FileStore.QiNiu, "img/"+name, path) } return nil } -func ossUpload(name string, path string) error { - oss := file_store.ALiYunOSS{} - return oss.UpLoad(name, path) -} - -func qiniuUpload(name string, path string) error { - oss := file_store.ALiYunOSS{} - return oss.UpLoad(name, path) +func upload(driver file_store.DriverType, store config.ObjectStore, name, path string) error { + if !store.Configured() { + return fmt.Errorf("file store %s is not configured; set it under extend.fileStore", driver) + } + oxs := file_store.OXS{ + Endpoint: store.Endpoint, + AccessKeyID: store.AccessKeyID, + AccessKeySecret: store.AccessKeySecret, + BucketName: store.BucketName, + } + client, err := oxs.Setup(driver) + if err != nil { + return err + } + return client.UpLoad(name, path) } diff --git a/app/other/apis/upload_test.go b/app/other/apis/upload_test.go new file mode 100644 index 00000000..9831ff2d --- /dev/null +++ b/app/other/apis/upload_test.go @@ -0,0 +1,38 @@ +package apis + +import ( + "strings" + "testing" + + "go-admin/config" +) + +// Both branches used to construct a zero-value ALiYunOSS and call UpLoad on it, +// which panicked; the qiniu branch built the aliyun client, so source=3 could +// not have reached qiniu even with credentials. Unconfigured now reports which +// store is missing. +func TestThirdUploadReportsAnUnconfiguredStore(t *testing.T) { + previous := config.ExtConfig.FileStore + config.ExtConfig.FileStore = config.FileStore{} + t.Cleanup(func() { config.ExtConfig.FileStore = previous }) + + for source, want := range map[string]string{"2": "AliYunOSS", "3": "QiNiuKodo"} { + err := thirdUpload(source, "x.png", "/tmp/x.png") + if err == nil { + t.Errorf("source=%s: no error from an unconfigured store", source) + continue + } + if !strings.Contains(err.Error(), want) { + t.Errorf("source=%s: error names %q, want it to mention %s", source, err, want) + } + } +} + +// source 1 and anything unrecognised keep the local copy and do nothing else. +func TestThirdUploadIgnoresLocalAndUnknownSources(t *testing.T) { + for _, source := range []string{"", "1", "9"} { + if err := thirdUpload(source, "x.png", "/tmp/x.png"); err != nil { + t.Errorf("source=%q returned %v, want nil", source, err) + } + } +} diff --git a/config/extend.go b/config/extend.go index bd06e031..e8d6daa6 100644 --- a/config/extend.go +++ b/config/extend.go @@ -3,14 +3,39 @@ package config var ExtConfig Extend // Extend 扩展配置 -// extend: -// demo: -// name: demo-name +// +// extend: +// demo: +// name: demo-name +// // 使用方法: config.ExtConfig......即可!! type Extend struct { - AMap AMap // 这里配置对应配置文件的结构即可 + AMap AMap // 这里配置对应配置文件的结构即可 + FileStore FileStore } type AMap struct { Key string } + +// FileStore 对象存储。上传接口的 source 参数决定走哪一家:2 是阿里云,3 是七牛。 +// 没有填的那一家在被请求时返回明确错误,而不是上传到别处或者崩溃。 +// +// common/file_store 里还实现了华为云 OBS,但上传接口没有对应的 source 取值, +// 所以这里也不为它提供配置。 +type FileStore struct { + AliYun ObjectStore + QiNiu ObjectStore +} + +type ObjectStore struct { + Endpoint string + AccessKeyID string + AccessKeySecret string + BucketName string +} + +// Configured reports whether enough was filled in to attempt a connection. +func (o ObjectStore) Configured() bool { + return o.Endpoint != "" && o.AccessKeyID != "" && o.AccessKeySecret != "" && o.BucketName != "" +} diff --git a/config/extend_test.go b/config/extend_test.go new file mode 100644 index 00000000..230174f4 --- /dev/null +++ b/config/extend_test.go @@ -0,0 +1,16 @@ +package config + +import "testing" + +func TestObjectStoreConfigured(t *testing.T) { + if (ObjectStore{}).Configured() { + t.Fatal("empty store reported as configured") + } + full := ObjectStore{Endpoint: "e", AccessKeyID: "a", AccessKeySecret: "s", BucketName: "b"} + if !full.Configured() { + t.Fatal("complete store reported as unconfigured") + } + if (ObjectStore{Endpoint: "e", AccessKeyID: "a"}).Configured() { + t.Fatal("partial store reported as configured") + } +} diff --git a/config/settings.full.yml b/config/settings.full.yml index bcca7e29..a1fdacce 100644 --- a/config/settings.full.yml +++ b/config/settings.full.yml @@ -56,3 +56,17 @@ settings: extend: # 扩展项使用说明 demo: name: data + # fileStore 对象存储。上传接口的 source 参数决定走哪一家: + # source=1 只存本地,source=2 阿里云 OSS,source=3 七牛 Kodo + # 没有填的那一家在被请求时会返回明确错误,不会静默存到别处。 + fileStore: + aliYun: + endpoint: oss-cn-hangzhou.aliyuncs.com + accessKeyId: "" + accessKeySecret: "" + bucketName: "" + qiNiu: + endpoint: "" + accessKeyId: "" + accessKeySecret: "" + bucketName: ""