使用 Viper 从 Golang 中的文件和环境变量加载配置
在开发和部署后端 Web 应用程序时,我们通常必须针对不同的环境(例如开发、测试、登台和生产)使用不同的配置。
今天我们将学习如何使用Viper从文件或环境变量加载配置。
以下是:
- YouTube 上完整系列播放列表的链接
- 以及它的Github 仓库
为什么要使用文件和环境变量
从文件中读取值可以让我们轻松地指定本地开发和测试的默认配置。
在使用 docker 容器将应用程序部署到暂存或生产环境时,从环境变量读取值将帮助我们覆盖默认设置。
为什么选择 Viper
Viper是一个非常流行的用于此目的的 Golang 包。
- 它可以从配置文件中查找、加载和解组值。
- 它支持多种类型的文件,例如 JSON、TOML、YAML、ENV 或 INI。
- 它还可以从环境变量或命令行标志中读取值。
- 它使我们能够设置或覆盖默认值。
- 此外,如果您希望将设置存储在远程系统(如Etcd或Consul)中,那么您可以使用 viper 直接从其中读取数据。
- 它适用于未加密和加密的值。
- Viper 还有一个有趣的地方是,它可以监视配置文件中的变化,并将其通知应用程序。
- 我们还可以使用 viper 保存对文件所做的任何配置修改。
有很多有用的功能,对吧?
我们将做什么
在我们简单的银行项目的当前代码中,我们在文件中dbDriver
对的某些常量进行了硬编码,并且在文件中对的又一个常量进行了硬编码。dbSource
main_test.go
serverAddress
main.go
const (
dbDriver = "postgres"
dbSource = "postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable"
serverAddress = "0.0.0.0:8080"
)
因此在本教程中,我们将学习如何使用 Viper 从文件和环境变量中读取这些配置。
安装 Viper
好的,让我们开始安装 Viper!打开浏览器并搜索golang viper
。
然后打开它的Github 页面。向下滚动一点,复制此go get
命令并在终端中运行它来安装该包:
❯ go get github.com/spf13/viper
在此之后,在我们的项目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
就这样!这个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"`
}
为了获取变量的值并将其存储在此结构体中,我们需要使用 Viper 的解组功能。Viper内部使用了mapstructuremapstructure
包来解组值,因此我们使用标签来指定每个配置字段的名称。
在我们的例子中,我们必须使用 中声明的每个变量的确切名称app.env
。例如,DBDriver
的标签名称应该是DB_DRIVER
,DBSource
的标签名称应该是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")
...
}
首先,我们调用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
}
之后,我们调用viper.ReadInConfig()
开始读取配置值。如果error
不是nil
,则直接返回。
否则,我们调用viper.Unmarshal()
来将值解组到目标config
对象中。最后,返回该config
对象以及任何可能出现的错误。
这样,加载配置的功能就基本完成了。现在我们可以在main.go
文件中使用它了。
在主函数中使用 LoadConfig
让我们删除所有之前的硬编码值。然后在main()
函数中调用util.LoadConfig()
并传入"."
此处,这意味着当前文件夹,因为我们的配置文件app.env
与此文件位于同一位置main.go
。
如果出现错误,我们只会写入一条致命日志,提示“无法加载配置”。否则,我们只需将这些变量更改为config.DBDriver
、config.DBSource
和config.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)
}
}
大功告成!我们来尝试运行这个服务器吧。
❯ 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
一切正常!服务器正在监听localhost
端口8080
,正如我们在文件中指定的那样app.env
。
让我们打开 Postman 并发送一个 API 请求。我将调用“列出账户”API。
哎呀,出现了错误500 Internal Server Error
。这是因为我们的 Web 服务器无法连接到端口 上的 Postgres 数据库5432
。让我们打开终端,检查 Postgres 是否正在运行:
❯ docker ps
目前没有正在运行的容器。如果我们运行docker ps -a
,我们可以看到 Postgres 容器已退出。因此,我们必须通过运行以下命令来启动它:
❯ docker start postgres12
好的,现在数据库已经启动并运行了。让我们回到 Postman 并再次发送请求。
这次成功了。我们得到了一个账户列表。所以,我们从文件加载配置的代码运行正常。
让我们尝试用环境变量覆盖这些配置。在调用之前,我会将SERVER_ADDRESS
变量设置为localhost
端口。8081
make 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
这里我们可以看到服务器现在正在监听 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())
}
如果错误不是nil
,那么我们只需写入致命日志。否则,我们应该将这两个值更改为config.DBDriver
和config.DBSource
。
就这样!大功告成!接下来我们来运行整个包的测试。
全部通过!关于从文件和环境变量读取配置的讲座到此结束。
我强烈建议您查看文档并尝试 viper 的一些其他有趣的功能,例如实时观看或从远程系统读取。
祝您编码愉快,下次讲座再见!
如果您喜欢这篇文章,请订阅我们的 Youtube 频道并在 Twitter 上关注我们,以便将来获取更多教程。
如果你想加入我目前在 Voodoo 的优秀团队,请查看我们的职位空缺。你可以远程办公,也可以在巴黎/阿姆斯特丹/伦敦/柏林/巴塞罗那现场办公,但需获得签证担保。
鏂囩珷鏉ユ簮锛�https://dev.to/techschoolguru/load-config-from-file-environment-variables-in-golang-with-viper-2j2d