01
什么是 Viper?
Viper 是适用于 Go 应用程序(包括 Twelve-Factor App)的完整配置解决方案。它被设计为在应用程序中工作,并且可以处理所有类型的配置需求和格式。它支持:
- 设置默认值
- 可以读取 JSON,TOML,YAML,HCL,envfile 和 Java properties 格式的配置文件
- 实时监控和重新读取配置文件(可选)
- 读取环境变量中的配置信息
- 读取远程配置系统(etcd 或 Consul)中的配置信息,并监控配置信息发生改变
- 读取命令行参数中的配置信息
- 读取 buffer 中的配置信息
- 显式设置配置项
可以将 Viper 视为满足您所有应用程序配置需求的注册表。
02
为什么使用 Viper?
在构建现代应用程序时,您无需担心配置文件格式;您想专注于构建出色的软件。Viper 的出现就是为了在这方面给您提供帮助。
Viper 为您执行以下操作:
- 查找,加载和反序列化 JSON,TOML,YAML,HCL,INI,envfile 或 Java properties 格式的配置文件。
- 提供一种机制来为您的不同配置选项设置默认值。
- 提供一种机制来通过命令行参数覆盖指定的选项的值。
- 提供别名系统,以在不会破坏现有代码的情况下轻松重命名参数。
- 用户提供了与默认值相同的命令行或配置文件时,可以容易地于区分它们的区别。
Viper 使用以下优先顺序。每个项目优先于其下面的项目:
- 显式调用 Set 方法设置值
- flag(命令行参数)
- env(环境变量)
- config(配置文件)
- key/value 存储
- 默认值
重要:Viper 配置项的 Key 不区分大小写。正在讨论是否设置为可选项。
03
怎么将配置项写入 Viper?
安装
go get github.com/spf13/viper
建立默认值
一个好的配置系统应该支持默认值。默认值对于 Key 不是必须的,但是如果未通过配置文件,环境变量,远程配置或标志(flag)设置 Key 的值,那么 Key 的默认值很有用。
示例:
viper.SetDefault("ContentDir", "content") viper.SetDefault("LayoutDir", "layouts") viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
读取配置文件
Viper 需要最少的配置,以便它知道在哪里寻找配置文件。Viper 支持JSON,TOML,YAML,HCL,INI,envfile 和 Java Properties 格式的文件。Viper 可以搜索多个路径,但是当前单个 Viper 实例仅支持单个配置文件。Viper 不会默认使用任何配置搜索路径,而会将默认决定留给应用程序。
下面是如何使用 Viper 搜索和读取配置文件的示例。不需要任何特定路径,但至少需要提供一个配置文件的预期路径(见代码 3-5 行)。
viper.SetConfigName("config") // name of config file (without extension) viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name viper.AddConfigPath("/etc/appname/") // path to look for the config file in viper.AddConfigPath("$HOME/.appname") // call multiple times to add many search paths viper.AddConfigPath(".") // optionally look for config in the working directory err := viper.ReadInConfig() // Find and read the config file if err != nil { // Handle errors reading the config file panic(fmt.Errorf("Fatal error config file: %s \n", err)) }
您可以处理未找到配置文件的特定情况,如下所示:
if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { // Config file not found; ignore error if desired } else { // Config file was found but another error was produced } } // Config file found and successfully parsed
注意 [自 1.6]:您也可以有一个没有扩展名的文件, 并以编程方式指定格式。对于位于用户 $HOME 目录中的配置文件,没有任何扩展名,如 .bashrc
写入配置文件
从配置文件中读取文件很有用,但有时您希望存储运行时所做的所有修改。为此,有一堆命令可用,每个命令都有自己的用途:
- WriteConfig - 将当前 viper 配置写入预定义路径并覆盖(如果存在)。如果没有预定义的路径,则返回错误。
- SafeWriteConfig - 将当前 viper 配置写入预定义路径。如果没有预定义的路径,则返回错误。如果存在,不会覆盖当前配置文件。
- WriteConfigAs - 将当前 viper 配置写入给定的文件路径。将覆盖给定的文件(如果存在)。
- SafeWriteConfigAs - 将当前 viper 配置写入给定的文件路径。如果存在,不会覆盖给定文件。
根据经验,所有标有 safe 标记的方法都不会覆盖任何文件,而是直接创建(如果不存在),而默认行为是创建或截断。
一个小示例:
viper.WriteConfig() // writes current config to predefined path set by 'viper.AddConfigPath()' and 'viper.SetConfigName' viper.SafeWriteConfig() viper.WriteConfigAs("/path/to/my/.config") viper.SafeWriteConfigAs("/path/to/my/.config") // will error since it has already been written viper.SafeWriteConfigAs("/path/to/my/.other_config")
监控和重新读取配置文件
Viper 支持在运行时让应用程序实时读取配置文件的能力。
需要重新启动服务器才能使配置生效的日子已经一去不复返了,viper 支持的应用程序可以在运行时读取对配置文件的更新,并且不会错过任何更新。
只需告诉 viper 实例 watchConfig。您可以为 Viper 提供一个回调函数,在每次发生更改时运行的该函数。
请确保在调用 WatchConfig() 之前添加了所有配置路径
viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println("Config file changed:", e.Name) })
从 io.Reader 读取配置
Viper 预定义许多配置源(如文件、环境变量、命令行参数和远程 K/V 存储,但您不受他们的约束。您还可以实现自己所需的配置源,并提供给 viper。
viper.SetConfigType("yaml") // or viper.SetConfigType("YAML") // any approach to require this configuration into your program. var yamlExample = []byte(` Hacker: true name: steve hobbies: - skateboarding - snowboarding - go clothing: jacket: leather trousers: denim age: 35 eyes : brown beard: true `) viper.ReadConfig(bytes.NewBuffer(yamlExample)) viper.Get("name") // this would be "steve"
覆盖设置
这些可能是来自命令行参数,也可以来自您自己的应用程序逻辑。
viper.Set("Verbose", true) viper.Set("LogFile", LogFile)
注册和使用别名
别名允许由多个键引用单个值
viper.RegisterAlias("loud", "Verbose") viper.Set("verbose", true) // same result as next line viper.Set("loud", true) // same result as prior line viper.GetBool("loud") // true viper.GetBool("verbose") // true
使用环境变量
Viper 完全支持环境变量。这使 Twelve-Factor App 开箱即用。有五种方法可以帮助使用 ENV:
- AutomaticEnv()
- BindEnv(string...) : error
- SetEnvPrefix(string)
- SetEnvKeyReplacer(string...) *strings.Replacer
- AllowEmptyEnv(bool)
使用 ENV 变量时,必须认识到 Viper 将 ENV 变量视为对大小敏感。
Viper 提供了一种机制,用于尝试确保 ENV 变量是唯一的。通过使用 SetEnvPrefix,您可以告诉 Viper 在从环境变量读取时使用前缀。BindEnv 和AutomaticEnv 都将使用前缀。
BindEnv 采用一个或多个参数。第一个参数是键名称,其余参数是要绑定到此键的环境变量的名称。如果提供了多个,它们将按指定顺序优先。环境变量的名称是大小写敏感。如果未提供 ENV 变量名称,则 Viper 将自动假定 ENV 变量与以下格式匹配:前缀 + "_" + 所有 CAPS 中的键名称。当您显式提供 ENV 变量名称(第二个参数)时,它不会自动添加前缀。例如,如果第二个参数为"id",Viper 将查找 ENV 变量"ID"。
使用 ENV 变量时,需要注意的一个重要问题是每次访问该值时都会重新读取该值。调用 BindEnv 时,viper 不会固定该值。
AutomaticEnv 是一个强大的帮助器,尤其是当与SerenvPrefix 结合。调用时,viper 将会在发出 viper.Get 请求时,随时检查环境变量。它将应用以下规则。如果使用 EnvPrefix 设置了前缀,它将检查一个环境变量的名称是否与键匹配。
SetEnvKeyReplacer 允许您使用 strings.Replacer 对象将 Env 键在一定程度上重写。如果您想要使用 - 或者其它符号在 Get() 调用中,但希望环境变量使用 _ 分隔符,这非常有用。使用它的示例可以在 viper_test.go 中找到。
或者,您也可以将 EnvKeyReplacer 与 NewWithOptions 工厂函数一起使用。与 SetEnvKeyReplacer 不同,它接受 StringReplacer 接口,允许您编写自定义字符串替换逻辑。
默认情况下,空环境变量被视为未设置,并将回退到下一个配置源。若要将空环境变量视为已设置,请使用 AllowEmptyEnv 方法。
环境变量-示例代码:
SetEnvPrefix("spf") // will be uppercased automatically BindEnv("id") os.Setenv("SPF_ID", "13") // typically done outside of the app id := Get("id") // 13