Viper简介
Viper是一个完整的Go语言项目的配置解决方案。它可以处理所有类型的配置需求和格式,相关链接如下
包文档:https://pkg.go.dev/github.com/spf13/viper
github:https://github.com/spf13/viper
Viper的优势
在构建Golang程序时可以不必担心配置文件格式而更专注于实现。
viper主要包含以下操作:
- 查找、加载和反序列化 “json”, “toml”, “yaml”, “yml”, “properties”, “props”, “prop”, “hcl”, “tfvars”, “dotenv”, “env”, “ini”
- 提供一种机制来为不同的配置选项设置默认值。
- 提供一种机制来为通过命令行参数设置指定覆盖值。
- 提供别名,以在不破坏现有代码的情况下轻松重命名参数。
- 使区分用户何时提供与默认值相同的命令行或配置文件变得容易。
每个项目的优先级都高于它下面的项目,Viper优先顺序。
- 显式调用
Set
- 命令行参数(flag)
- 环境变量
- 配置文件
- key/value存储
- 默认值
重要提示: Viper 配置键不区分大小写。正在进行关于使之成为可选项的讨论。
Viper使用场景
- 设置默认值
- “json”, “toml”, “yaml”, “yml”, “properties”, “props”, “prop”, “hcl”, “tfvars”, “dotenv”, “env”, “ini”文件中读取载入
- 实时观看和重新读取配置文件(可选)
- 从环境变量中读取
- 从远程配置系统(etcd 或 Consul)读取,并观察变化
- 从命令行标志读取
- 从缓冲区读取
- 设置显式值
Viper 可以被认为是满足所有应用程序配置需求的注册表
Viper的安装
1 |
go get -u -v github.com/spf13/viper |
Viper使用实例
使用默认值
一个好的配置系统对于默认值拥有良好的支持,其重要性不言而喻。在Viper中的默认值使用如下
1 |
package main |
覆盖设置
这些可能来自命令行标志,也可能来自你自己的应用程序逻辑。
1 |
viper.Set("Verbose", true) |
注册和使用别名
别名允许多个键引用单个值
1 |
viper.RegisterAlias("loud", "Verbose") // 注册别名(此处loud和Verbose建立了别名) |
配置文件使用
读取配置文件
抽离统一化管理成为配置文件,将所有的配置写在文件中便于管理修改与编辑。Viper 支持 “json”, “toml”, “yaml”, “yml”, “properties”, “props”, “prop”, “hcl”, “
tfvars”, “dotenv”, “env”, “ini” 属性文件。Viper 可以搜索多个路径,但目前单个 Viper 实例仅支持单个配置文件。Viper
不会默认任何配置搜索路径,将默认决定留给应用程序。不需要任何特定路径,但应至少提供一个需要配置文件的路径。以下是如何使用 Viper 搜索和读取配置文件的示例。
1 |
// 指定配置文件路径 |
注意若采用
setConfigName
则只会使用第一个配置文件夹推荐使用
SetConfigFile("path/file_name")
来完成配置文件的载入
从io.Reader读取配置
Viper预先定义了许多配置源,如文件、环境变量、标志和远程K/V存储,但也可以实现自己所需的配置源并将其提供给viper。
1 |
import ( |
写入配置文件
从配置文件中读取是很有用的,但有时你想存储在运行时所作的所有修改都比较繁琐。viper提供了相关功能
- WriteConfig - 将当前的
viper
配置写入预定义的路径并覆盖(如果存在的话)。如果没有预定义的路径,则报错。 - SafeWriteConfig - 将当前的
viper
配置写入预定义的路径。如果没有预定义的路径,则报错。如果存在,将不会覆盖当前的配置文件。 - WriteConfigAs - 将当前的
viper
配置写入给定的文件路径。将覆盖给定的文件(如果它存在的话)。 - SafeWriteConfigAs - 将当前的
viper
配置写入给定的文件路径。不会覆盖给定的文件(如果它存在的话)。
根据经验,标记为
safe
的所有方法都不会覆盖任何文件,而是直接创建(如果不存在),而默认行为是创建或截断。
监听配置文件
Viper支持在运行时实时读取配置文件的功能。
需要重新启动服务器以使配置生效的日子已经一去不复返了,viper驱动的应用程序可以在运行时读取配置文件的更新,而不会错过任何消息。
只需告诉viper实例watchConfig。可选地,你可以为Viper提供一个回调函数,以便在每次发生更改时运行。
确保在调用
WatchConfig()
之前添加了所有的配置路径。
1 |
viper.WatchConfig() |
实例
1 |
package main |
环境变量
Viper完全支持环境变量。以下几种方法进行对ENV协作:
1 |
// AllowEmptyEnv 告诉 Viper 将设置但为空的环境变量视为有效值,而不是回退。出于向后兼容性的原因,默认情况下这是错误的 |
使用ENV变量时,务必要意识到Viper将ENV变量视为区分大小写。
1 |
// case 1 |
当第四次输出时
VAL
,将输出change
小技巧:在使用环境变量的时候推荐采用全大写,避免混淆
使用viper获取值
获取函数如下所示,具体作用见名思意
1 |
Get(key string) interface{} |
访问嵌套的键
访问器方法也接受深度嵌套键的格式化路径。例如,如果加载下面的JSON文件:
1 |
{ |
Viper可以通过传入.
分隔的路径来访问嵌套字段:
1 |
GetString("datastore.datastore.warehouse.host") |
这遵守上面建立的优先规则;搜索路径将遍历其余配置注册表,直到找到为止。(译注:因为Viper支持从多种配置来源,例如磁盘上的配置文件>命令行标志位>环境变量>远程Key/Value存储>
默认值,我们在查找一个配置的时候如果在当前配置源中没找到,就会继续从后续的配置源查找,直到找到为止。)
例如,在给定此配置文件的情况下,datastore.metric.host
和datastore.metric.port
均已定义(并且可以被覆盖)。如果另外在默认值中定义了datastore.metric.protocol
,Viper也会找到它。然而,如果datastore.metric
被直接赋值覆盖(被flag,环境变量,set()
方法等等…),那么datastore.metric
的所有子键都将变为未定义状态,它们被高优先级配置级别“遮蔽”(shadowed)了。最后,如果存在与分隔的键路径匹配的键,则返回其值。例如:
1 |
{ |
提取子树
从Viper中提取子树,viper
实例现在代表了以下配置:
1 |
app: |
执行后:
1 |
subv := viper.Sub("app.cache1") |
subv
现在就代表:
1 |
max-items: 100 |
假设我们现在有这么一个函数:
1 |
func NewCache(cfg *Viper) *Cache {...} |
它基于subv
格式的配置信息创建缓存。现在,可以轻松地分别创建这两个缓存,如下所示:
1 |
cfg1 := viper.Sub("app.cache1") |
反序列化
你还可以选择将所有或特定的值解析到结构体、map等。
有两种方法可以做到这一点:
Unmarshal(rawVal interface{}) : error
UnmarshalKey(key string, rawVal interface{}) : error
1 |
type config struct { |
如果你想要解析那些键本身就包含.
(默认的键分隔符)的配置,你需要修改分隔符:
1 |
v := viper.NewWithOptions(viper.KeyDelimiter("::")) |
Viper还支持解析到嵌入的结构体:
1 |
/* |
Viper在后台使用github.com/mitchellh/mapstructure来解析值,其默认情况下使用mapstructure
tag。
注意 当我们需要将viper读取的配置反序列到我们定义的结构体变量中时,一定要使用
mapstructure
tag!
序列化成字符串
你可能需要将viper中保存的所有设置序列化到一个字符串中,而不是将它们写入到一个文件中。你可以将自己喜欢的格式的序列化器与AllSettings()
返回的配置一起使用。
1 |
import ( |
远程Key/Value存储支持
在Viper中启用远程支持,需要在代码中匿名导入viper/remote
这个包。
1 |
import _ "github.com/spf13/viper/remote" |
Viper将读取从Key/Value存储(例如etcd或Consul)中的路径检索到的配置字符串(如JSON
、TOML
、YAML
、HCL
、envfile
和Java properties
格式)。这些值的优先级高于默认值,但是会被从磁盘、flag或环境变量检索到的配置值覆盖。(译注:也就是说Viper加载配置值的优先级为:磁盘上的配置文件>命令行标志位>环境变量>远程Key/Value存储>默认值。)
Viper使用crypt从K/V存储中检索配置,这意味着如果你有正确的gpg密匙,你可以将配置值加密存储并自动解密。加密是可选的。
你可以将远程配置与本地配置结合使用,也可以独立使用。
crypt
有一个命令行助手,你可以使用它将配置放入K/V存储中。crypt
默认使用在http://127.0.0.1:4001的etcd。
1 |
$ go get github.com/bketelsen/crypt/bin/crypt |
确认值已经设置:
1 |
$ crypt get -plaintext /config/hugo.json |
有关如何设置加密值或如何使用Consul的示例,请参见crypt
文档。
远程Key/Value存储示例-未加密
etcd
1 |
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") |
Consul
你需要 Consul Key/Value存储中设置一个Key保存包含所需配置的JSON值。例如,创建一个keyMY_CONSUL_KEY
将下面的值存入Consul key/value 存储:
1 |
viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY") |
Firestore
1 |
viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document") |
当然,你也可以使用SecureRemoteProvider
。
远程Key/Value存储示例-加密
1 |
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") |
监控etcd中的更改-未加密
1 |
// 或者你可以创建一个新的viper实例 |
基于Viper实现的环境变量动态链接
1 |
import ( |