You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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)
}

关键说明

  1. 环境变量映射:通过SetEnvKeyReplacer(strings.NewReplacer(".", "_")),Viper可以将DATABASE_CONNECTION_URI转换为database.connectionUri,实现与JSON嵌套配置的匹配。
  2. 加载顺序:严格遵循JSON(基础)→ .env(覆盖非空)→ 系统环境变量(最高优先级覆盖非空)的顺序,符合需求。
  3. 简化.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 09:37:21