如何在Golang Viper中读取多配置文件与环境变量?
问题:Viper配置加载优先级不符合预期
我使用Viper作为配置管理工具,需要实现以下加载逻辑:
- 首先加载
config.json中的所有配置 - 加载
.env中的非空变量,空值或不存在的变量不覆盖JSON中的值 - 加载系统环境变量中的非空变量,空值或不存在的变量不覆盖JSON中的值
尝试了以下代码但未达到预期效果:
package main import ( "github.com/spf13/viper" "log" ) type ServerConfiguration struct { Port int } type DatabaseConfiguration struct { ConnectionUri string } type Configuration struct { Server ServerConfiguration Database DatabaseConfiguration } func main() { var configuration Configuration readFromConfigFile() readFromEnvFile() viper.AutomaticEnv() err := viper.Unmarshal(&configuration) if err != nil { log.Fatalf("unable to decode into struct, %v", err) } log.Printf("database uri is %s", configuration.Database.ConnectionUri) log.Printf("port for this application is %d", configuration.Server.Port) } func readFromConfigFile() { viper.SetConfigName("config") viper.AddConfigPath(".") if err := viper.ReadInConfig(); err != nil { log.Fatalf("Error reading config file, %s", err) } } func readFromEnvFile() { viper.SetConfigName(".env") viper.SetConfigType("env") viper.AddConfigPath(".") if err := viper.MergeInConfig(); err != nil { log.Fatalf("Error reading config file, %s", err) } }
config.json内容:
{ "database": { "connectionUri": "test uri" }, "server": { "port": 8283 } }
.env文件内容:
DATABASE_CONNECTION_URI="test2 uri"
预期结果:
2023/03/15 17:07:42 database uri is test2 uri 2023/03/15 17:07:42 port for this application is 8283
实际结果:
2023/03/15 17:07:42 database uri is test uri 2023/03/15 17:07:42 port for this application is 8283
解决方案
问题核心在于:Viper默认不会自动将下划线分隔的环境变量名(如DATABASE_CONNECTION_URI)映射到嵌套的结构体键(如database.connectionUri),导致.env中的变量无法正确覆盖JSON配置。
修改步骤及完整代码
package main import ( "github.com/spf13/viper" "log" "strings" ) type ServerConfiguration struct { Port int } type DatabaseConfiguration struct { ConnectionUri string } type Configuration struct { Server ServerConfiguration Database DatabaseConfiguration } func main() { var configuration Configuration // 1. 加载JSON基础配置 viper.SetConfigName("config") viper.SetConfigType("json") viper.AddConfigPath(".") if err := viper.ReadInConfig(); err != nil { log.Fatalf("Error reading config file: %s", err) } // 2. 加载.env文件,合并到现有配置 viper.SetConfigFile(".env") viper.SetConfigType("env") if err := viper.MergeInConfig(); err != nil { log.Fatalf("Error reading .env file: %s", err) } // 3. 配置环境变量映射规则:下划线转点,匹配嵌套结构 viper.AutomaticEnv() viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // 反序列化为结构体 err := viper.Unmarshal(&configuration) if err != nil { log.Fatalf("Unable to decode into struct: %v", err) } log.Printf("database uri is %s", configuration.Database.ConnectionUri) log.Printf("port for this application is %d", configuration.Server.Port) }
关键说明
- 环境变量映射:通过
SetEnvKeyReplacer(strings.NewReplacer(".", "_")),Viper可以将DATABASE_CONNECTION_URI转换为database.connectionUri,实现与JSON嵌套配置的匹配。 - 加载顺序:严格遵循JSON(基础)→ .env(覆盖非空)→ 系统环境变量(最高优先级覆盖非空)的顺序,符合需求。
- 简化.env加载(Viper v1.15+):如果你的Viper版本≥v1.15,可改用
viper.LoadEnvFromFile(".env")替代SetConfigFile+MergeInConfig,代码更简洁:if err := viper.LoadEnvFromFile(".env"); err != nil { log.Fatalf("Error loading .env file: %s", err) }
验证结果
运行修改后的代码,将得到预期输出:
2023/03/15 17:07:42 database uri is test2 uri 2023/03/15 17:07:42 port for this application is 8283
内容的提问来源于stack exchange,提问作者Sujit Baniya
相关产品推荐
相关产品推荐

