golang-viper详解
cobra 命令生成工具
viper 提供了配置文件的解析工作
pflag 提供命令行的解析工作
cfg := &ServerConfig{
RedisURL: viper.GetString("redis_port_6379_tcp") | viper.GetString("database_url"),
}
viper 是什么?
viper 为 Go 应用程序提供了一个完整的配置解决方案。它被设计用来和应用程序一起工作,能够处理所有类型的配置,它支持:
设置默认值
从 JSON、TOML、YAML、HCL 和 Java 特性配置文件中读取配置
可一直观察配置文件的改变并且重新读取配置文件
从环境变量中读取配置
从远程配置系统(etcd、Consul)中读取配置,并且一直观察配置的改变
从命令行的 flags 中读取配置
从 buffer 中读取配置
通过Set()明确指定的值
- setting defaults
- reading from JSON, TOML, YAML, HCL, and Java properties config files
- live watching and re-reading of config files (optional)
- reading from
environment
variables - reading from remote config systems (
etcd
orConsul
), and watching changes - reading from
command line
flags - reading from
buffer
- setting explicit values 明确指定的
viper 可以被认为是一个你的应用程序所需要的所有配置的注册中心。
如何获取配置参数 Getting Values From Viper
In Viper, there are a few ways to get a value depending on the value’s type.
The following functions and methods exist:
Get(key string) : interface{}
GetBool(key string) : bool
GetFloat64(key string) : float64
GetInt(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{}
为何使用viper?
viper 使用以下优先级,越往下优先级越高
default // 6. 默认参数
key/value store // 5. 通过etcd、consul加载的参数
config // 4. 通过配置文件加载的参数
env // 3. 通过环境变量加载的参数
flag // 2. 通过命令行指定的参数
explicit call to Set() // 1. 通过Set()明确指定的参数
支持的配置文件格式
目前支持的解析类型为5种 "json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"
但是不管使用何种解析格式,一个 Viper 的实例只支持一种配置文件格式.
ConfigType 有两种方式获取:
- 强制指定解析类型
viper.SetConfigType("yaml")
根据所使用的文件扩展名进行推断
config.yaml
查看正在使用的配置文件
viper.ConfigFileUsed()
1. 使用别名(Registering and Using Aliases)
Aliases允许一个值多个键引用,例如
viper.RegisterAlias("Loud", "Verbose")
viper.Set("loud", "haha") //viper 不区分大小写
fmt.Println("Loud ", viper.Get("Loud")) //输出:Loud haha
fmt.Println("verbose ", viper.Get("verbose"))//输出:verbose haha
2. 强制指定参数value (Setting Overrides)
viper.Set("Verbose", true)
viper.Set("LogFile", LogFile)
3. 使用命令行参数 flags
Viper 可以绑定到 flags,Viper 支持 Pflags (也在 cobra 库中使用)。
像 BindEnv,当绑定函数被调用的时候,值是没有被设置的,直到被访问的时候。这意味着你可以想多早就多早绑定,甚至可以在 init 函数内。
var (
configFile string
)
func init() {
flag.StringVar(&configFile, "c", "", "config file")
flag.Parse()
// // using standard library "flag" package
// flag.Int("port", 8081, "Port to listen on")
// flag.String("blacklist", "blacklist.conf", "Location of blacklist file")
// pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
// pflag.Parse()
// viper.BindPFlags(pflag.CommandLine)
viper.AddConfigPath(".")
viper.AddConfigPath("$HOME/.config/")
// viper.SetConfigFile(configFile)
viper.ReadInConfig()
// viper.SetEnvPrefix("") //注意 spf 会被自动转换为 SPF
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
if viper.GetString("mysql.host") == "" {
panic("config error")
}
}
func main() {
fmt.Printf("previous mysql.host = %s\n", viper.GetString("mysql.host"))
os.Setenv("MYSQL_HOST", "192.168.1.1")
fmt.Printf("after mysql.host = %s\n", viper.GetString("mysql.host"))
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Printf("Config file changed: %v\n", e)
fmt.Printf("reload mysql.host = %s\n", viper.GetString("mysql.host"))
})
go func() {
for {
time.Sleep(10 * time.Second)
fmt.Printf("timer load mysql.host = %s\n", viper.GetString("mysql.host"))
fmt.Printf("os load mysql.host = %s\n", os.Getenv("MYSQL_HOST"))
}
}()
// shell command "export MYSQL_HOST=127.0.0.3" doesn't work
time.Sleep(86400 * time.Second)
}
4. 使用环境变量
viper 使用以下四种方法与环境变量打交道:
AutomaticEnv()
BindEnv(string...) : error
SetEnvPrefix(string)
SetEnvKeyReplacer(string...) *strings.Replacer
需要认识到的是,viper 与环境变量打交道时,是大小写敏感的。
viper 提供了一种机制来保证环境变量是唯一的,通过使用SetEnvPrefix(string)
告诉 viper 读取环境变量的时候自动加上前缀,
格式形为:prefix + '_' + env;AutomaticEnv 和 BindEnv(string...) 都会使用该前缀。
BindEnv
函数需要一个或两个参数,第一个参数为 key,第二个参数为环境变量的名称,当只有一个参数的时候,viper 会自动去环境变量中匹配第一个 key 参数(注意,此时 key 会被 viper 实例自动转换为大写);当有两个参数的时候,viper 会将该 key 与指定的环境变量名称绑定。注意:如果显示指定了两个参数,则 BindEnv 不会自动加上前缀。
需要认识到的是,每一次 viper 实例访问环境变量的时候,都会重新读取,BindEnv 并不会将 key 与值相固定
AutomaticEnv
与 SetEnvPrefix 结合的时候相当强大,AutomaticEnv 被调用后,每一次调用viper.Get,viper 将会检查环境变量。viper 实例会自动将前缀加在 key (自动被转换为大写字母)上,如果 SetEnvPrefix 被调用的话。
SetEnvKeyReplacer
允许你使用 strings.Replacer 对象在某种程度上重写环境变量,比如你想在 viper.Get 调用中使用 - 或者别的间隔符,而在环境变量中使用 _ 间隔符的时候,SetEnvKeyReplacer 是很有用的。
viper.Automatic()
viper.SetEnvPrefix("spf") //注意 spf 会被自动转换为 SPF
os.Setenv("SPF_HASH", "12345")
os.Setenv("HASH", "54321")
fmt.Println("SPF_HASH ", viper.Get("hash")) //输出:12345
//需将viper.Automatic注释掉,否则会有冲突,当有冲突的时候以viper.Automatic为准
viper.BindEnv("hash", "HASH")
fmt.Println(viper.Get("hash")) //输出:54321
viper.BindEnv("hash")
fmt.Println(viper.Get("hash")) //输出:12345
5. 读取配置文件
当前,一个 Viper 的实例只支持一种配置文件格式.
加载配置文件的方法只有一个 ReadInConfig() ,在配置文件设置之后调用.err := viper.ReadInConfig() //读取配置文件
但是配置文件的加载有两种方式:
通过源码我们可以发现,如果通过 SetConfigFile(configFile) 指定config文件之后,Viper将不在对目录搜索,而是直接将指定的配置文件返回
直接设置配置文件为一个文件(如果该项设置,以此为准,不在继续搜索)
viper.SetConfigFile(configFile)
设置配置文件名称及查找路径
Viper 并没有默认配置文件的搜索路径,所以应用程序需要告诉 Viper 实例配置文件的路径,这里有一个如何使用 Viper 搜索并读取配置文件的例子:
v.configName = "config" // viper在初始化的时候就已经设置了默认的搜索文件名为config
了
viper.SetConfigName("config") //配置文件的名称(不需要扩展名)
viper.AddConfigPath(".") //将当前目录加入到 viper 的搜索路径中,优先搜索先添加的目录.
viper.AddConfigPath("$HOME/somedir") //可多次调用AddConfigPath函数,注意:若多个搜索路径中均包含指定的配置文件,默认读取的是一个添加的搜索路径里的配置文件
// viper.SetConfigFile(configFile) //强制使用指定的配置文件,不在进行搜索
err := viper.ReadInConfig() //搜索并且读取配置文件
if err != nil {
panic(fmt.Errorf("Fatal error config file : %s \n", err))
}
fmt.Println(viper.Get("name")) //假设配置文件中包含name属性,那么 Get 可以得到该属性对应的值
6. 自定义配置文件源
只要实现了 io.Reader Reading Config from io.Reader
ReadInConfig() 只支持fs的配置文件读取,但是ReadConfig(in io.Reader) 就强大多了,只要实现io.Reader接口的都可以传递进来
但是由于使用了自定义的配置源,所以自动推断方式就可能出错,
比如 指定的配置文件为json格式 自定义的却是yaml格式,那么解析就会出错.
所以当使用 viper.ReadConfig(io.Reader)
时,必须设置解析文件的格式.
viper 预定义了很多配置源,例如文件、环境变量、flags 和远程 key/value 存储,
但是你不会被局限于此,你可以定义自己的配置文件源,并将其送给 viper, 例如
var yaml_ex = []byte(`
name : kk
age : 23
wife :
name : ss //注意:yaml文件只支持前面是空格,不支持tab键,所以确保属性的前面是空格而不是tab键
age : 23
subject :
- math
- computer
`)
viper.SetConfigType("yaml") // 1. 先设置要解析的文件类型
viper.ReadConfig(bytes.NewBuffer(yaml_ex)) // 2. 对文件类型进行解析
fmt.Println("name ", viper.Get("name"))
7. 通过etcd、consul加载的参数 Remote Key/Value Store Support
To enable remote support in Viper, do a blank importimport _ "github.com/spf13/viper/remote"
//1. http方式 Unencrypted
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
err := viper.ReadRemoteConfig()
//2. https方式 Encrypted
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
err := viper.ReadRemoteConfig()
// unmarshal config
viper.Unmarshal(&runtime_conf)
// 2. consul 方式
var runtimeViper = viper.New()
err := runtimeViper.AddRemoteProvider("consul", "localhost:8500", "dev/app/etc/conf")
if err != nil {
fmt.Println(err)
}
runtimeViper.SetConfigType("json")
// err := runtimeViper.Unmarshal(&runtime_conf)
go func() {
for {
time.Sleep(time.Second * 5)
err := runtimeViper.WatchRemoteConfig()
if err != nil {
fmt.Println(err)
continue
}
// runtimeViper.Unmarshal(&runtime_conf)
port := runtimeViper.GetInt("http.port")
address := runtimeViper.GetString("http.address")
fmt.Println(address, port)
}
}
8. 使用参数的默认值
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
9. 监控重新读取配置文件(Watching and re-reading config files)
viper 支持你的应用程序运行时观察到并读取配置文件的改变,重启服务才能让你的配置文件生效已经成为过去,viper 可以很轻松的帮助你读取到更新过后的文件,告诉 viper 实例观察配置文件(WatchConfig),你还可以传进去一个函数来告诉 viper 实例当配置文件改变时应该做什么,例如
viper.WatchConfig()
viper.OnConfigChange(fun (e fsnotify.Event) {
fmt.Println("config files changed, afer change, the name become : ", viper.Get("name"))
})