使

使用 Viper 从 Golang 中的文件和环境变量加载配置

2025-06-10

使用 Viper 从 Golang 中的文件和环境变量加载配置

在开发和部署后端 Web 应用程序时,我们通常必须针对不同的环境(例如开发、测试、登台和生产)使用不同的配置。

今天我们将学习如何使用Viper从文件或环境变量加载配置。

以下是:

为什么要使用文件和环境变量

替代文本

从文件中读取值可以让我们轻松地指定本地开发和测试的默认配置。

在使用 docker 容器将应用程序部署到暂存或生产环境时,从环境变量读取值将帮助我们覆盖默认设置。

为什么选择 Viper

替代文本

Viper是一个非常流行的用于此目的的 Golang 包。

  • 它可以从配置文件中查找、加载和解组值。
  • 它支持多种类型的文件,例如 JSON、TOML、YAML、ENV 或 INI。
  • 它还可以从环境变量或命令行标志中读取值。
  • 它使我们能够设置或覆盖默认值。
  • 此外,如果您希望将设置存储在远程系统(如EtcdConsul)中,那么您可以使用 viper 直接从其中读取数据。
  • 它适用于未加密和加密的值。
  • Viper 还有一个有趣的地方是,它可以监视配置文件中的变化,并将其通知应用程序。
  • 我们还可以使用 viper 保存对文件所做的任何配置修改。

有很多有用的功能,对吧?

我们将做什么

在我们简单的银行项目的当前代码中,我们在文件dbDriver对的某些常量进行了硬编码,并且在文件对的又一个常量进行了硬编码dbSourcemain_test.goserverAddressmain.go



const (
    dbDriver      = "postgres"
    dbSource      = "postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable"
    serverAddress = "0.0.0.0:8080"
)


Enter fullscreen mode Exit fullscreen mode

因此在本教程中,我们将学习如何使用 Viper 从文件和环境变量中读取这些配置。

安装 Viper

好的,让我们开始安装 Viper!打开浏览器并搜索golang viper

然后打开它的Github 页面。向下滚动一点,复制此go get命令并在终端中运行它来安装该包:



❯ go get github.com/spf13/viper


Enter fullscreen mode Exit fullscreen mode

在此之后,在我们的项目go.mod文件中,我们可以看到 Viper 已被添加为依赖项。

创建配置文件

现在我要创建一个新文件app.env来存储我们用于开发的配置值。然后,让我们从main.go文件中复制这些变量并将它们粘贴到这个配置文件中。由于我们使用的是点 env 格式,因此我们必须更改声明这些变量的方式。它应该类似于我们声明环境变量的方式:

  • 每个变量都应该在单独的行上声明。
  • 变量的名称是大写的,并且其单词用下划线分隔。
  • 变量值后面跟着等号后面的名称。


DB_DRIVER=postgres
DB_SOURCE=postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable
SERVER_ADDRESS=0.0.0.0:8080


Enter fullscreen mode Exit fullscreen mode

就这样!这个app.env文件现在包含了我们本地开发环境的默认配置。接下来,我们将使用 Viper 加载这个配置文件。

加载配置文件

让我们在包config.go内创建一个新文件util

然后在此文件中声明一个新的Config结构体类型。该Config结构体将保存我们从文件或环境变量中读取的应用程序的所有配置变量。目前,我们只有 3 个变量:

  • 首先,DBDriver字符串类型,
  • 第二,DBSource也是字符串类型。
  • 最后ServerAddress也是字符串类型。


type Config struct {
    DBDriver      string `mapstructure:"DB_DRIVER"`
    DBSource      string `mapstructure:"DB_SOURCE"`
    ServerAddress string `mapstructure:"SERVER_ADDRESS"`
}


Enter fullscreen mode Exit fullscreen mode

为了获取变量的值并将其存储在此结构体中,我们需要使用 Viper 的解组功能。Viper内部使用了mapstructuremapstructure包来解组值,因此我们使用标签来指定每个配置字段的名称。

在我们的例子中,我们必须使用 中声明的每个变量的确切名称app.env。例如,DBDriver的标签名称应该是DB_DRIVERDBSource的标签名称应该是DB_SOURCE,类似的ServerAddress, 应该是SERVER_ADDRESS

好的,接下来我要定义一个新函数LoadConfig(),它接受一个path,并返回一个config对象或error。此函数将从路径中的配置文件中读取配置(如果存在),或者使用环境变量(如果提供了)覆盖其值。



func LoadConfig(path string) (config Config, err error) {
    viper.AddConfigPath(path)
    viper.SetConfigName("app")
    viper.SetConfigType("env")

    ...
}


Enter fullscreen mode Exit fullscreen mode

首先,我们调用viper.AddConfigPath()告诉 Viper 配置文件的位置。在本例中,该位置由输入路径参数指定。

接下来,我们调用viper.SetConfigName()来告诉 Viper 查找具有特定名称的配置文件。我们的配置文件是app.env,所以它的名称是app

env我们还通过调用viper.SetConfigFile()并传入 来告诉 Viper 配置文件的类型,在本例中是env。您也可以根据需要使用 JSON、XML 或任何其他格式,只需确保您的配置文件具有正确的格式和扩展名即可。

现在,除了从文件读取配置之外,我们还希望 viper 从环境变量中读取值。因此,我们调用viper.AutomaticEnv()来告诉 viper 自动用相应环境变量的值(如果存在)覆盖从配置文件读取的值。



// LoadConfig reads configuration from file or environment variables.
func LoadConfig(path string) (config Config, err error) {
    viper.AddConfigPath(path)
    viper.SetConfigName("app")
    viper.SetConfigType("env")

    viper.AutomaticEnv()

    err = viper.ReadInConfig()
    if err != nil {
        return
    }

    err = viper.Unmarshal(&config)
    return
}


Enter fullscreen mode Exit fullscreen mode

之后,我们调用viper.ReadInConfig()开始读取配置值。如果error不是nil,则直接返回。

否则,我们调用viper.Unmarshal()来将值解组到目标config对象中。最后,返回该config对象以及任何可能出现的错误。

这样,加载配置的功能就基本完成了。现在我们可以在main.go文件中使用它了。

在主函数中使用 LoadConfig

让我们删除所有之前的硬编码值。然后在main()函数中调用util.LoadConfig()并传入"."此处,这意味着当前文件夹,因为我们的配置文件app.env与此文件位于同一位置main.go

如果出现错误,我们只会写入一条致命日志,提示“无法加载配置”。否则,我们只需将这些变量更改为config.DBDriverconfig.DBSourceconfig.ServerAdress



func main() {
    config, err := util.LoadConfig(".")
    if err != nil {
        log.Fatal("cannot load config:", err)
    }

    conn, err := sql.Open(config.DBDriver, config.DBSource)
    if err != nil {
        log.Fatal("cannot connect to db:", err)
    }

    store := db.NewStore(conn)
    server := api.NewServer(store)

    err = server.Start(config.ServerAddress)
    if err != nil {
        log.Fatal("cannot start server:", err)
    }
}


Enter fullscreen mode Exit fullscreen mode

大功告成!我们来尝试运行这个服务器吧。



❯ make server
go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /accounts                 --> github.com/techschool/simplebank/api.(*Server).createAccount-fm (3 handlers)
[GIN-debug] GET    /accounts/:id             --> github.com/techschool/simplebank/api.(*Server).getAccount-fm (3 handlers)
[GIN-debug] GET    /accounts                 --> github.com/techschool/simplebank/api.(*Server).listAccount-fm (3 handlers)
[GIN-debug] Listening and serving HTTP on 0.0.0.0:8080


Enter fullscreen mode Exit fullscreen mode

一切正常!服务器正在监听localhost端口8080,正如我们在文件中指定的那样app.env

让我们打开 Postman 并发送一个 API 请求。我将调用“列出账户”API。

替代文本

哎呀,出现了错误500 Internal Server Error。这是因为我们的 Web 服务器无法连接到端口 上的 Postgres 数据库5432。让我们打开终端,检查 Postgres 是否正在运行:



❯ docker ps


Enter fullscreen mode Exit fullscreen mode

替代文本

目前没有正在运行的容器。如果我们运行docker ps -a,我们可以看到 Postgres 容器已退出。因此,我们必须通过运行以下命令来启动它:



❯ docker start postgres12


Enter fullscreen mode Exit fullscreen mode

好的,现在数据库已经启动并运行了。让我们回到 Postman 并再次发送请求。

替代文本

这次成功了。我们得到了一个账户列表。所以,我们从文件加载配置的代码运行正常。

让我们尝试用环境变量覆盖这些配置。在调用之前,我会将SERVER_ADDRESS变量设置为localhost端口8081make server


SERVER_ADDRESS=0.0.0.0:8081 make server
go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /accounts                 --> github.com/techschool/simplebank/api.(*Server).createAccount-fm (3 handlers)
[GIN-debug] GET    /accounts/:id             --> github.com/techschool/simplebank/api.(*Server).getAccount-fm (3 handlers)
[GIN-debug] GET    /accounts                 --> github.com/techschool/simplebank/api.(*Server).listAccount-fm (3 handlers)
[GIN-debug] Listening and serving HTTP on 0.0.0.0:8081


Enter fullscreen mode Exit fullscreen mode

这里我们可以看到服务器现在正在监听 port ,8081而不是像8080以前那样。现在,如果我们尝试在 Postman 中向 port 发送相同的 API 请求8080,我们将收到连接被拒绝的错误:

替代文本

只有当我们把这个端口改为8081,那么请求才会成功:

替代文本

因此,我们可以得出结论,Viper 已成功用环境变量覆盖了从配置文件中读取的值。这在我们以后需要将应用程序部署到不同的环境(例如暂存环境或生产环境)时非常方便。

在测试中使用 LoadConfig

现在,在我们完成之前,让我们更新main_test.go文件以使用新LoadConfig()功能。

首先,删除所有硬编码常量。然后在TestMain()函数中调用util.LoadConfig()

但这一次,main_test.go文件位于db/sqlc文件夹内,而app.env配置文件位于存储库的根目录,因此我们必须传递这个相对路径:"../.."。这两个点..基本上意味着转到父文件夹。



func TestMain(m *testing.M) {
    config, err := util.LoadConfig("../..")
    if err != nil {
        log.Fatal("cannot load config:", err)
    }

    testDB, err = sql.Open(config.DBDriver, config.DBSource)
    if err != nil {
        log.Fatal("cannot connect to db:", err)
    }

    testQueries = New(testDB)

    os.Exit(m.Run())
}



Enter fullscreen mode Exit fullscreen mode

如果错误不是nil,那么我们只需写入致命日志。否则,我们应该将这两个值更改为config.DBDriverconfig.DBSource

就这样!大功告成!接下来我们来运行整个包的测试。

替代文本

全部通过!关于从文件和环境变量读取配置的讲座到此结束。

我强烈建议您查看文档并尝试 viper 的一些其他有趣的功能,例如实时观看从远程系统读取

祝您编码愉快,下次讲座再见!


如果您喜欢这篇文章,请订阅我们的 Youtube 频道在 Twitter 上关注我们,以便将来获取更多教程。


如果你想加入我目前在 Voodoo 的优秀团队,请查看我们的职位空缺。你可以远程办公,也可以在巴黎/阿姆斯特丹/伦敦/柏林/巴塞罗那现场办公,但需获得签证担保。

鏂囩珷鏉ユ簮锛�https://dev.to/techschoolguru/load-config-from-file-environment-variables-in-golang-with-viper-2j2d
PREV
完整的 gRPC 课程 [Protobuf + Go + Java] 你将学到什么:有任何课程要求或先修课程吗?结业证书
NEXT
现代 Sass 文件夹结构