diff --git a/server/app/goauto/sybimport/import_handler.go b/server/app/goauto/sybimport/import_handler.go index cd2bf5d..0fdfd62 100644 --- a/server/app/goauto/sybimport/import_handler.go +++ b/server/app/goauto/sybimport/import_handler.go @@ -53,8 +53,17 @@ func (handler Handler) Import(c *gin.Context) { settings := config.ExtConfig.SYB.Resolved() if !settings.HasCredentials() { + // Name the file that was actually consulted. The previous wording only + // mentioned environment variables, which sent operators looking in the + // wrong place once config.yaml became the normal way to configure this. + source := "未找到 config.yaml(依次查找 GOAUTO_CONFIG、服务端工作目录、可执行文件同级目录)" + if path := config.LocalConfigPath(); path != "" { + source = "已读取 " + path + ",但其中没有 syb.username / syb.password" + } writeError(c, invalidRequest( - "顺云宝账号未配置:请设置环境变量 GOAUTO_SYB_USERNAME 和 GOAUTO_SYB_PASSWORD 后重启服务端")) + "顺云宝账号未配置:"+source+ + "。请在 config.yaml 的 syb 段填写 username 和 password,或设置环境变量 "+ + "GOAUTO_SYB_USERNAME / GOAUTO_SYB_PASSWORD,然后重启服务端")) return } diff --git a/server/cmd/api/server.go b/server/cmd/api/server.go index ed13f0c..eb02e06 100644 --- a/server/cmd/api/server.go +++ b/server/cmd/api/server.go @@ -65,6 +65,7 @@ func setup() { file.NewSource(file.WithPath(configYml)), ext.ApplyLocalConfig, ext.ApplyEnvironment, + ext.LogEffectiveConfig, database.Setup, storage.Setup, ) diff --git a/server/config/local.go b/server/config/local.go index b74af03..45b33f8 100644 --- a/server/config/local.go +++ b/server/config/local.go @@ -2,6 +2,7 @@ package config import ( "fmt" + "log" "os" "path/filepath" "strconv" @@ -68,6 +69,10 @@ type localFile struct { func ApplyLocalConfig() { path := LocalConfigPath() if path == "" { + // `[必须]` Say so out loud. Silently doing nothing here surfaces much + // later as a puzzling "credential not configured" error that never + // mentions the file the operator actually edited. + log.Printf("本地配置:未找到 %s(依次查找 GOAUTO_CONFIG、当前目录、可执行文件同级目录),仅使用 settings.yml 和环境变量", LocalConfigName) return } raw, err := os.ReadFile(path) @@ -83,6 +88,25 @@ func ApplyLocalConfig() { applyLocalDatabase(file.Database) applyLocalPorts(file.Ports) ApplyLocalSYB(file.SYB) + log.Printf("本地配置:已加载 %s", path) +} + +// LogEffectiveConfig prints what the layered configuration actually resolved +// to, without printing any secret. It runs after every layer has been applied. +// +// `[必须]` Report presence, never values. An operator needs to know whether the +// credentials arrived, not what they are — server logs get pasted into tickets. +func LogEffectiveConfig() { + syb := ExtConfig.SYB.Resolved() + source := "未配置" + switch { + case strings.TrimSpace(os.Getenv("GOAUTO_SYB_USERNAME")) != "": + source = "环境变量" + case syb.HasCredentials(): + source = LocalConfigName + } + log.Printf("顺云宝配置:凭据=%v(来源:%s)base_url=%s 验证码识别=%v", + syb.HasCredentials(), source, syb.BaseURL, syb.OcrURL != "") } func applyLocalDatabase(database map[string]any) { diff --git a/server/config/local_test.go b/server/config/local_test.go index 710532c..a002aeb 100644 --- a/server/config/local_test.go +++ b/server/config/local_test.go @@ -201,3 +201,20 @@ func TestScalarRendersYAMLTypesWithoutExponentOrTrailingZero(t *testing.T) { } } } + +// 报错信息必须指向操作员真正编辑的那个文件。只提环境变量会把人引到错的地方。 +func TestLocalConfigPathIsReportableWhenAbsent(t *testing.T) { + restoreConfigState(t) + t.Setenv("GOAUTO_CONFIG", "") + if LocalConfigPath() != "" { + t.Fatal("测试环境不应找到 config.yaml") + } +} + +func TestLocalConfigPathPrefersExplicitEnvOverCwd(t *testing.T) { + restoreConfigState(t) + explicit := writeLocalConfig(t, "syb:\n username: a\n password: \"b\"\n") + if got := LocalConfigPath(); got != explicit { + t.Fatalf("GOAUTO_CONFIG 应优先于其它位置: %q", got) + } +}