Viper配置文件读取
Go使用Viper加载配置文件
安装
1 | go get github.com/spf13/viper |
1. 初次使用
1 | func main(){ |
2. 读取配置文件
1 | viper.SetConfigFile("./config.yaml") // 指定配置文件路径 |
加载配置文件出错,处理特定错误
1 | if err := viper.ReadInConfig(); err != nil { |
监控并重新读取配置文件
Viper支持在运行时实时读取配置文件的功能。
只需告诉viper实例watchConfig
。可选地,你可以为Viper提供一个回调函数,以便在每次发生更改时运行。
确保在调用WatchConfig()
之前添加了所有的配置路径。
1 | viper.WatchConfig() |
从io.Reader读取配置
1 | viper.SetConfigType("yaml") // 或者 viper.SetConfigType("YAML") |
覆盖配置
1 | viper.Set("Verbose", true) |
注册和使用别名
1 | viper.RegisterAlias("loud", "Verbose") // 注册别名(此处loud和Verbose建立了别名) |
使用环境变量
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变量与以下格式匹配:前缀+ “_” +键名全部大写。当你显式提供ENV变量名(第二个参数)时,它 不会 自动添加前缀。例如,如果第二个参数是“id”,Viper将查找环境变量“ID”。
在使用ENV变量时,需要注意的一件重要事情是,每次访问该值时都将读取它。Viper在调用BindEnv
时不固定该值。
AutomaticEnv
是一个强大的助手,尤其是与SetEnvPrefix
结合使用时。调用时,Viper会在发出viper.Get
请求时随时检查环境变量。它将应用以下规则。它将检查环境变量的名称是否与键匹配(如果设置了EnvPrefix
)。
SetEnvKeyReplacer
允许你使用strings.Replacer
对象在一定程度上重写 Env 键。如果你希望在Get()
调用中使用-
或者其他什么符号,但是环境变量里使用_
分隔符,那么这个功能是非常有用的。可以在viper_test.go
中找到它的使用示例。
或者,你可以使用带有NewWithOptions
工厂函数的EnvKeyReplacer
。与SetEnvKeyReplacer
不同,它接受StringReplacer
接口,允许你编写自定义字符串替换逻辑。
默认情况下,空环境变量被认为是未设置的,并将返回到下一个配置源。若要将空环境变量视为已设置,请使用AllowEmptyEnv
方法。
Env 示例:
1 | SetEnvPrefix("spf") // 将自动转为大写 |
3. 写入配置文件
从配置文件中读取配置文件是有用的,但是有时你想要存储在运行时所做的所有修改。为此,可以使用下面一组命令,每个命令都有自己的用途:
- WriteConfig - 将当前的
viper
配置写入预定义的路径并覆盖(如果存在的话)。如果没有预定义的路径,则报错。 - SafeWriteConfig - 将当前的
viper
配置写入预定义的路径。如果没有预定义的路径,则报错。如果存在,将不会覆盖当前的配置文件。 - WriteConfigAs - 将当前的
viper
配置写入给定的文件路径。将覆盖给定的文件(如果它存在的话)。 - SafeWriteConfigAs - 将当前的
viper
配置写入给定的文件路径。不会覆盖给定的文件(如果它存在的话)。
根据经验,标记为safe
的所有方法都不会覆盖任何文件,而是直接创建(如果不存在),而默认行为是创建或截断。
1 | viper.WriteConfig() // 将当前配置写入“viper.AddConfigPath()”和“viper.SetConfigName”设置的预定义路径 |
4. 读取远程配置
在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
文档。
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 | { |
1 | viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY") |
Firestore
1 | viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document") |
当然,你也可以使用SecureRemoteProvider
远程k/v存储-加密
1 | viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") |
监控etcd中的更改-未加密
1 | // 或者你可以创建一个新的viper实例 |
5. 从Viper中获取值
在Viper中,有几种方法可以根据值的类型获取值。存在以下功能和方法:
Get(key string) : interface{}
GetBool(key string) : bool
GetFloat64(key string) : float64
GetInt(key string) : int
GetIntSlice(key string) : []int
GetString(key string) : string
GetStringMap(key string) : map[string]interface{}
GetStringMapString(key string) : map[string]string
GetStringSlice(key string) : []string
GetTime(key string) : time.Time
GetDuration(key string) : time.Duration
IsSet(key string) : bool
AllSettings() : map[string]interface{}
需要认识到的一件重要事情是,每一个Get方法在找不到值的时候都会返回零值。为了检查给定的键是否存在,提供了IsSet()
方法。
1 | viper.GetString("logfile") // 不区分大小写的设置和获取 |
访问嵌套的键
访问器方法也接受深度嵌套键的格式化路径。例如,如果加载下面的JSON文件:
1 | { |
Viper可以通过传入.
分隔的路径来访问嵌套字段:
1 | GetString("datastore.metric.host") // (返回 "127.0.0.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") |