安全做法是统一用viper.SetConfigName("config-"+os.Getenv("ENV"))配合viper.AddConfigPath("./configs"),并提前调用viper.SetConfigType("yaml");避免硬编码、重复ReadInConfig、点号环境变量未替换、敏感字段写入YAML、缺失struct tag及生产启用WatchConfig。

viper.SetConfigName 配合环境变量动态设名才安全
硬编码 viper.SetConfigName("config-prod") 或用 if 判断环境再调用 ReadInConfig(),会导致配置加载逻辑散落在多处,测试时容易漏掉分支。真正可控的做法是:统一用 viper.SetConfigName("config-" + os.Getenv("ENV")),再固定加路径 viper.AddConfigPath("./configs")。
常见错误现象:
- 配置文件明明存在 config-dev.yaml,但
viper.ReadInConfig()报Config File Not Found - dev 和 prod 配置被意外 merge,比如 prod 启动后日志级别却是 debug
实操建议:
- 约定配置文件命名格式为
config-{env}.yaml,例如config-dev.yaml、config-prod.yaml - 在
viper.ReadInConfig()前,必须先调用viper.SetConfigType("yaml"),否则扩展名缺失时会报Unsupported Config Type "" - 不要多次调用
ReadInConfig()加载不同文件——viper 不支持“只读某一个”,重复调用会触发 merge,污染当前配置 - 若需共用基础字段(如服务名、端口),拆出
config-base.yaml,用viper.MergeConfigFile("./configs/config-base.yaml")显式合并,且 base 必须在 env-specific 之前
viper.AutomaticEnv() 不自动处理点号层级
调了 viper.AutomaticEnv() 却发现 viper.GetString("server.port") 拿不到 SERVER_PORT 的值?因为 AutomaticEnv() 默认只做字符串全匹配,不解析点号。它会把 server.port 当成字面量去查环境变量 SERVER.PORT,而系统里实际是 SERVER_PORT。
实操建议:
- 尽早调用
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")),让server.port→SERVER_PORT - 同时设前缀:
viper.SetEnvPrefix("app"),这样app.server.port就对应APP_SERVER_PORT,避免和其他进程的环境变量冲突 - 敏感字段(如
db.password)务必只从环境变量读,YAML 文件里留空或删掉,杜绝提交密钥到 Git - 别依赖
os.Getenv("SERVER_PORT")直接取值——viper 的优先级链(flag > env > file > default)能帮你兜底,手动读就绕过了整个机制
Unmarshal 嵌套结构体必须逐层加 yaml tag
配置文件写了 redis: { host: "127.0.0.1" },结构体也定义了 Redis RedisConfig 字段,但 viper.Unmarshal(&cfg) 后 cfg.Redis.Host 还是空?Go 反射不认驼峰/下划线转换,viper 只认 struct tag。
实操建议:
- 所有要映射的字段都得显式加
yaml:"host",且值必须和 YAML 键完全一致(区分大小写) - 嵌套结构体每层都要 tag,例如:
type Config struct { Redis RedisConfig `yaml:"redis"` }+type RedisConfig struct { Host string `yaml:"host"` } - 别混用
mapstructure和yamltag——viper 默认用mapstructure,但如果你调了viper.SetConfigType("yaml"),就得统一用yamltag,否则失效 - 启动时加一句
log.Printf("Loaded config: %+v", cfg),比猜字段映射关系快得多
最易被忽略的是:生产环境必须禁用 viper.WatchConfig()。热重载看似方便,但配置语法错误或文件权限变更会直接 panic,没有 fallback 机制。它适合本地开发调试,不适合线上服务。


















