项目地址:https://github.com/spf13/viper git
本文翻译自该项目里README.md文件中的内容github
有不少Go语言项目用到了Viper框架,好比:json
Viper是一个方便Go语言应用程序处理配置信息的库。它能够处理多种格式的配置。它支持的特性: 缓存
在构建现代应用程序时,您没必要担忧配置文件格式; 你能够专一于构建出色的软件。
Viper 能够作以下工做:服务器
Viper读取配置信息的优先级顺序,从高到低,以下:app
Viper 的配置项的key不区分大小写。框架
默认值不是必须的,若是配置文件、环境变量、远程配置系统、命令行参数、Set函数都没有指定时,默认值将起做用。
例子:ide
viper.SetDefault("ContentDir", "content") viper.SetDefault("LayoutDir", "layouts") viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
Viper支持JSON、TOML、YAML、HCL和Java properties文件。
Viper能够搜索多个路径,但目前单个Viper实例仅支持单个配置文件。
Viper默认不搜索任何路径。
如下是如何使用Viper搜索和读取配置文件的示例。
路径不是必需的,但最好至少应提供一个路径,以便找到一个配置文件。函数
viper.SetConfigName("config") // 设置配置文件名 (不带后缀) viper.AddConfigPath("/etc/appname/") // 第一个搜索路径 viper.AddConfigPath("$HOME/.appname") // 能够屡次调用添加路径 viper.AddConfigPath(".") // 好比添加当前目录 err := viper.ReadInConfig() // 搜索路径,并读取配置数据 if err != nil { panic(fmt.Errorf("Fatal error config file: %s \n", err)) }
Viper支持让您的应用程序在运行时拥有读取配置文件的能力。
须要从新启动服务器以使配置生效的日子已经一去不复返了,由viper驱动的应用程序能够在运行时读取已更新的配置文件,而且不会错过任何节拍。
只须要调用viper实例的WatchConfig函数,你也能够指定一个回调函数来得到变更的通知。工具
viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println("Config file changed:", e.Name) })
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") // 返回 "steve"
viper.Set("Verbose", true) viper.Set("LogFile", LogFile)
别名能够实现多个key引用单个值。
viper.RegisterAlias("loud", "Verbose") viper.Set("verbose", true) viper.Set("loud", true) // 这两句设置的都是同一个值 viper.GetBool("loud") // true viper.GetBool("verbose") // true
Viper 彻底支持环境变量,这是的应用程序能够开箱即用。
有四个和环境变量有关的方法:
注意,环境变量时区分大小写的。
Viper提供了一种机制来确保Env变量是惟一的。经过SetEnvPrefix,在从环境变量读取时会添加设置的前缀。BindEnv和AutomaticEnv都会使用到这个前缀。
BindEnv须要一个或两个参数。第一个参数是键名,第二个参数是环境变量的名称。环境变量的名称区分大小写。若是未提供ENV变量名称,则Viper会自动假定该键名称与ENV变量名称匹配,而且ENV变量为所有大写。当您显式提供ENV变量名称时,它不会自动添加前缀。
使用ENV变量时要注意,当关联后,每次访问时都会读取该ENV值。Viper在BindEnv调用时不读取ENV值。
AutomaticEnv与SetEnvPrefix结合将会特别有用。当AutomaticEnv被调用时,任何viper.Get请求都会去获取环境变量。环境变量名为SetEnvPrefix设置的前缀,加上对应名称的大写。
SetEnvKeyReplacer容许你使用一个strings.Replacer对象来将配置名重写为Env名。若是你想在Get()中使用包含-的配置名 ,但但愿对应的环境变量名包含_分隔符,就可使用该方法。使用它的一个例子能够在项目中viper_test.go文件里找到。
例子:
SetEnvPrefix("spf") // 将会自动转为大写 BindEnv("id") os.Setenv("SPF_ID", "13") // 一般经过系统环境变量来设置 id := Get("id") // 13
Viper支持绑定pflags参数。
和BindEnv同样,当绑定方法被调用时,该值没有被获取,而是在被访问时获取。这意味着应该尽早进行绑定,甚至是在init()函数中绑定。
利用BindPFlag()方法能够绑定单个flag。
例子:
serverCmd.Flags().Int("port", 1138, "Port to run Application server on") viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
你也能够绑定已存在的pflag集合 (pflag.FlagSet):
pflag.Int("flagname", 1234, "help message for flagname") pflag.Parse() viper.BindPFlags(pflag.CommandLine) i := viper.GetInt("flagname") // 经过viper从pflag中获取值
使用pflag并不影响其余库使用标准库中的flag。经过导入,pflag能够接管经过标准库的flag定义的参数。这是经过调用pflag包中的AddGoFlagSet()方法实现的。
例子:
package main import ( "flag" "github.com/spf13/pflag" ) func main() { // using standard library "flag" package flag.Int("flagname", 1234, "help message for flagname") pflag.CommandLine.AddGoFlagSet(flag.CommandLine) pflag.Parse() viper.BindPFlags(pflag.CommandLine) i := viper.GetInt("flagname") // retrieve value from viper ... }
若是你不想使用pflag,Viper 提供了两个接口来实现绑定其余的flag系统。
使用 FlagValue 接口表明单个flag。下面是实现了该接口的简单的例子:
type myFlag struct {} func (f myFlag) HasChanged() bool { return false } func (f myFlag) Name() string { return "my-flag-name" } func (f myFlag) ValueString() string { return "my-flag-value" } func (f myFlag) ValueType() string { return "string" }
一旦你实现了该接口,就能够绑定它:
viper.BindFlagValue("my-flag-name", myFlag{})
使用 FlagValueSet 接口表明一组flag。下面是实现了该接口的简单的例子:
type myFlagSet struct { flags []myFlag } func (f myFlagSet) VisitAll(fn func(FlagValue)) { for _, flag := range flags { fn(flag) } }
一旦你实现了该接口,就能够绑定它:
fSet := myFlagSet{ flags: []myFlag{myFlag{}, myFlag{}}, } viper.BindFlagValues("my-flags", fSet)
启用该功能,须要导入viper/remot包:
import _ "github.com/spf13/viper/remote"
Viper 能够从例如etcd、Consul的远程Key/Value存储系统的一个路径上,读取一个配置字符串(JSON, TOML, YAML或HCL格式)。
这些值优先于默认值,但会被从磁盘文件、命令行flag、环境变量的配置所覆盖。
Viper 使用 crypt 来从 K/V 存储系统里读取配置,这意味着你能够加密储存你的配置信息,而且能够自动解密配置信息。加密是可选的。
您能够将远程配置与本地配置结合使用,也能够独立使用。
crypt 有一个命令行工具能够帮助你存储配置信息到K/V存储系统,crypt默认使用 http://127.0.0.1:4001 上的etcd。
$ go get github.com/xordataexchange/crypt/bin/crypt $ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json
确认你的值被设置:
$ crypt get -plaintext /config/hugo.json
有关crypt如何设置加密值或如何使用Consul的示例,请参阅文档。
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") viper.SetConfigType("json") // 由于不知道格式,因此须要指定,支持的格式有"json"、"toml"、"yaml"、"yml"、"properties"、"props"、"prop" err := viper.ReadRemoteConfig()
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") viper.SetConfigType("json") // 由于不知道格式,因此须要指定,支持的格式有"json"、"toml"、"yaml"、"yml"、"properties"、"props"、"prop" err := viper.ReadRemoteConfig()
// 您能够建立一个新的viper实例 var runtime_viper = viper.New() runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml") runtime_viper.SetConfigType("yaml") // 由于不知道格式,因此须要指定,支持的格式有"json"、"toml"、"yaml"、"yml"、"properties"、"props"、"prop" // 从远程读取配置 err := runtime_viper.ReadRemoteConfig() // 解析配置到runtime_conf中 runtime_viper.Unmarshal(&runtime_conf) // 经过一个goroutine远程的配置变化 go func(){ for { time.Sleep(time.Second * 5) // delay after each request // currently, only tested with etcd support err := runtime_viper.WatchRemoteConfig() if err != nil { log.Errorf("unable to read remote config: %v", err) continue } // 解析新的配置到一个结构体变量中,你也可使用channel实现一个信号通知的方式 runtime_viper.Unmarshal(&runtime_conf) } }()
在Viper中,有一些根据值的类型获取值的方法。存在一下方法:
若是Get函数未找到值,则返回对应类型的一个零值。能够经过 IsSet() 方法来检测一个健是否存在。
例子:
viper.GetString("logfile") // Setting & Getting 不区分大小写 if viper.GetBool("verbose") { fmt.Println("verbose enabled") }
访问方法也接受嵌套的键。例如,若是加载了如下JSON文件:
{ "host": { "address": "localhost", "port": 5799 }, "datastore": { "metric": { "host": "127.0.0.1", "port": 3099 }, "warehouse": { "host": "198.0.0.1", "port": 2112 } } }
Viper能够经过.分隔符来访问嵌套的字段:
GetString("datastore.metric.host") // (returns "127.0.0.1")
这遵照前面确立的优先规则; 会搜索路径中全部配置,直到找到为止。
例如,上面的文件,datastore.metric.host和 datastore.metric.port都已经定义(而且可能被覆盖)。若是另外 datastore.metric.protocol的默认值,Viper也会找到它。
可是,若是datastore.metric值被覆盖(经过标志,环境变量,Set方法,...),则全部datastore.metric的子键将会未定义,它们被优先级更高的配置值所“遮蔽”。
最后,若是存在相匹配的嵌套键,则其值将被返回。例如:
{ "datastore.metric.host": "0.0.0.0", "host": { "address": "localhost", "port": 5799 }, "datastore": { "metric": { "host": "127.0.0.1", "port": 3099 }, "warehouse": { "host": "198.0.0.1", "port": 2112 } } } GetString("datastore.metric.host") // returns "0.0.0.0"
能够从viper中提取子树。例如, viper配置为:
app: cache1: max-items: 100 item-size: 64 cache2: max-items: 200 item-size: 80
执行后:
subv := viper.Sub("app.cache1")
subv 就表明:
max-items: 100 item-size: 64
假如咱们有以下函数:
func NewCache(cfg *Viper) *Cache {...}
它的功能是根据配置信息建立缓存缓存。如今很容易分别建立这两个缓存:
cfg1 := viper.Sub("app.cache1") cache1 := NewCache(cfg1) cfg2 := viper.Sub("app.cache2") cache2 := NewCache(cfg2)
您还能够选择将全部或特定值解析到struct、map等。
有两个方法能够作到这一点:
例如:
type config struct { Port int Name string PathMap string `mapstructure:"path_map"` } var C config err := Unmarshal(&C) if err != nil { t.Fatalf("unable to decode into struct, %v", err) }
Viper随时准备使用开箱即用。没有任何配置或初始化也可使用Viper。因为大多数应用程序都但愿使用单个存储中心进行配置,所以viper包提供了此功能。它相似于一个单例模式。
在上面的全部示例中,他们都演示了如何使用viper的单例风格的方式。
您还能够建立多不一样的viper实例以供您的应用程序使用。每实例都有本身独立的设置和配置值。每一个实例能够从不一样的配置文件,K/V存储系统等读取。viper包支持的全部函数也都有对应的viper实例方法。
例子:
x := viper.New() y := viper.New() x.SetDefault("ContentDir", "content") y.SetDefault("ContentDir", "foobar") //...
当使用多个viper实例时,用户须要本身管理每一个实例。
问:为何不使用INI文件?
答:Ini文件很是糟糕。没有标准格式,并且很难验证。Viper设计使用JSON、TOML或YAML文件。若是有人真的想要添加此功能,项目的做者很乐意合并它。指定应用程序容许的格式很容易。
问:为何叫“Viper”?
答:Viper是Cobra项目的同伴。虽然二者均可以彻底独立运做,但它们一块儿能够为您的应用程序作很是多的基础工做。
问:为何叫“Cobra”?答:还能为commander取一个更好的名字吗?