fix(#48): make configuration resolution visible at startup and in errors

Diagnosing "credential not configured" required reading the source: the
local config layer was a silent no-op when it found no file, and the
import error named only environment variables even though config.yaml is
now the normal place to configure this — sending operators to look in a
file that was never the problem.

Startup now logs which config.yaml was loaded, or that none was found and
where it looked, followed by whether SYB credentials resolved and from
which layer. Presence only, never values: server logs get pasted into
tickets.

The import error now names the file that was actually consulted and
distinguishes "no config.yaml found" from "found it, but it has no syb
credentials" — two problems with different fixes that previously produced
identical text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
QiuSW
2026-08-20 09:37:58 +08:00
co-authored by Claude Opus 5
parent bf0c978794
commit 8001c03abd
4 changed files with 52 additions and 1 deletions
+10 -1
View File
@@ -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
}
+1
View File
@@ -65,6 +65,7 @@ func setup() {
file.NewSource(file.WithPath(configYml)),
ext.ApplyLocalConfig,
ext.ApplyEnvironment,
ext.LogEffectiveConfig,
database.Setup,
storage.Setup,
)
+24
View File
@@ -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) {
+17
View File
@@ -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)
}
}