应使用 viper + 环境变量驱动配置加载,避免硬编码环境判断;需显式设置 config name、path、type,启用 automaticenv 并配置 key replacer,结构体字段必须加 yaml tag,生产环境禁用 watchconfig。

直接用 Viper + 环境变量驱动加载,别手写 if env == "prod" 分支。Go 原生不识别环境概念,所有“多环境”都是靠外部传入(如 ENV=prod)+ 配置加载逻辑实现的。硬编码判断或编译标签(//go:build)在运行时完全失效,且破坏配置可移植性。
为什么 viper.ReadInConfig() 总是读不到 config.prod.yaml?
因为 viper.ReadInConfig() 默认只找固定名字(如 config.yaml),不会自动拼接环境名。它不解析 ENV 变量,也不会尝试 config.$ENV.yaml。
- 必须显式设置:先
viper.SetConfigName("config-" + os.Getenv("ENV")),再viper.AddConfigPath("./configs") - 如果没设
ENV,要 fallback:比如env := os.Getenv("ENV"); if env == "" { env = "dev" } - 调用
viper.ReadInConfig()前,务必加viper.SetConfigType("yaml"),否则扩展名缺失会报Unsupported Config Type "" - 不要把
config-dev.yaml和config-prod.yaml都ReadInConfig()一遍——viper 会 merge 它们,导致生产环境意外继承 dev 的 debug 开关
环境变量为啥总 fallback 到默认值?
因为 viper 默认忽略系统环境变量,除非你告诉它“去查”。viper.GetString("db.host") 不会自动映射到 DB_HOST,除非你做了绑定。
- 最简方案:尽早调用
viper.AutomaticEnv(),它会把db.host映射为DB_HOST(点号转大写下划线) - 若需自定义映射(比如环境变量叫
PGHOST,但配置项是db.host),用viper.BindEnv("db.host", "PGHOST") - 注意
viper.SetEnvPrefix("app"):设了前缀后,APP_DB_HOST才会映射到db.host;不设就只能匹配裸变量名 -
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))是必须的,否则server.port找不到SERVER.PORT(viper 不处理点号层级,只做字符串替换)
viper.Unmarshal() 后结构体字段还是空?
不是配置没读到,而是 Go 反射找不到对应字段——viper 只认 yaml tag,不猜命名规则。
- 所有嵌套字段都必须显式加 tag:
type Config struct { DB DBConfig `yaml:"db"` }、type DBConfig struct { Host string `yaml:"host"` } - tag 值必须和 YAML 键严格一致(区分大小写),
host≠Host≠HOST - 别指望 “
dbHost字段自动匹配DB_HOST环境变量”,那是AutomaticEnv()的事;对 YAML 文件,它只看 tag - 如果用了
mapstructuretag(比如mapstructure:"host"),得额外调用viper.UnmarshalKey("db", &dbCfg, viper.DecodeHook(mapstructure.DecodeHookFunc())),否则无效
最容易被忽略的是:生产环境必须禁用 viper.WatchConfig()。热重载触发 panic 会导致服务直接退出,而日志里可能只留下一行 panic: interface conversion: interface {} is nil, not string —— 实际是某个配置项在重载时被清空又没校验。











