Viper是Go应用程序的完整配置解决方案,包括12-Factor应用程序。它旨在在应用程序中工作,并可以处理所有类型的配置需求和格式。
1. Viper
Viper可以被认为是所有应用程序配置需求的注册表。
1.1. Viper特性支持
- 变量读入
- 支持从JSON,TOML,YAML,HCL和Java属性配置文件格式读取
- 支持从远程配置中心系统(etcd或Consul)读取,并检测变更
- 支持从环境变量读取
- 支持从命令行标志读取
- 支持从缓冲区读取
- 支持默认值设置
- 支持精确的值设置
- 实时检测和重载配置文件(可选)
1.2. Viper读取配置顺序顺序(优先级从高到低)
Viper配置键不区分大小写。
- explicit call to Set
- flag
- env
- config
- key/value store
- default
1.3. 默认值、配置扩展名支持、变量别名
1.3.1. 默认值
1
2
3
| viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
|
1.3.2. 扩展名支持
1
| var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"}
|
1.3.3. 值覆盖
1
2
3
| viper.Set("name", "clark")
viper.Set("age", 31)
fmt.Printf("name:%+v, age:%d\n", viper.Get("name"), viper.GetInt("age"))
|
1.3.4. 注册变量别名
1
2
| // 通过注册loud别名,当修改verbose时,loud也将改变,反之亦然
viper.RegisterAlias("loud", "Verbose")
|
1.4. 从配置文件读取
1.4.1. 指定多个配置文件路径读取(按先后优先读取最先获取到的)
目前单个Viper实例仅支持单个配置文件,应该在预期配置文件的位置提供至少一个路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 指定了config配置名称,后缀从支持扩展名中选择
viper.SetConfigName("config") // name of config file (without extension)
// 指定配置搜索路径
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("./testdata/config/") // call multiple times to add many search paths
viper.AddConfigPath(".") // optionally look for config in the working directory
// 从搜索路径中,寻找最新的配置,读入到viper.config中(解析配置文件,读入到配置)
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))
}
|
1.4.2. 写入到配置文件
支持文件存在覆盖写入、文件存在不覆盖。
- 标有安全safe的写入操作,若配置文件存在,则不会覆盖已有配置文件;
- 非安全的写入操作,若配置文件存在,则覆盖写入;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| // 写入成功,按配置文件最新寻找到的配置文件,覆盖写
err = viper.WriteConfig() // writes current config to predefined path set by 'viper.AddConfigPath()' and 'viper.SetConfigName'
if err != nil {
fmt.Printf("1. write config file err: %s\n", err)
}
// 写入失败,文件存在,不会覆盖已有配置写入
err = viper.SafeWriteConfig()
if err != nil {
fmt.Printf("2. write config file err: %s\n", err)
}
// 写入成功,文件不存在,创建文件,写入
err = viper.WriteConfigAs("./testdata/temp/config.yml")
if err != nil {
fmt.Printf("3. write config file err: %s\n", err)
}
// 写入失败,文件存在,不会覆盖已有配置写入
err = viper.SafeWriteConfigAs("./testdata/temp/config.yml") // will error since it has already been written
if err != nil {
fmt.Printf("4. write config file err: %s\n", err)
}
err = viper.SafeWriteConfigAs("./testdata/temp/other_config.yml")
if err != nil {
fmt.Printf("5. write config file err: %s\n", err)
}
--- // 输出
2. write config file err: File: /data/go/proj/go-example/testdata/config/config.yml exists. Use WriteConfig to overwrite.
4. write config file err: File: ./testdata/temp/config.yml exists. Use WriteConfig to overwrite.
5. write config file err: open ./testdata/temp/other_config.yml: no such file or directory
|
1.4.3. 检测&重载配置文件
Viper支持在运行时让应用程序实时读取配置文件。
只需告诉viper实例watchConfig即可,同时,可以选择为Viper提供每次配置发生更改时运行的回调函数。
1
2
3
4
5
6
7
8
9
10
11
| // 配置监听,在修改配置时候,会自动检查并做对应的回调事件处理
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})
<-make(chan struct{})
--- // 输出
Config file changed: /data/go/proj/go-example/testdata/config/config.yml
Config file changed: /data/go/proj/go-example/testdata/config/config.yml
Config file changed: /data/go/proj/go-example/testdata/config/config.yml
|
1.5. 从io.Reader读取
viper预定义了许多配置源,例如文件,环境变量,标志和远程K/V存储,还可以实现自己的必需配置源并将其提供给viper。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| // 基于IO读取配置信息
viper.SetConfigType("yaml") // or viper.SetConfigType("YAML")
// Yaml字面量配置
var yamlExample = []byte(`
Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- GOLANG
clothing:
jacket: leather
trousers: denim
age: 35
eyes : brown
beard: true
company:
inner:
- alibaba
- tencent
outer:
- google
- aws
- netflix
`)
// 基于io.Reader接口读取
err = viper.ReadConfig(bytes.NewBuffer(yamlExample))
if err != nil {
fmt.Printf("read config err: %s\n", err)
}
fmt.Printf("%+v\n", viper.Get("name"))
fmt.Printf("%+v\n", viper.GetBool("beard"))
fmt.Printf("%+v\n", viper.GetInt("age"))
fmt.Printf("%+v\n", viper.GetStringSlice("hobbies"))
fmt.Printf("%+v\n", viper.GetStringMap("clothing"))
fmt.Printf("%+v\n", viper.GetStringMapStringSlice("company"))
fmt.Printf("outer company => %+v\n", viper.GetStringSlice("company.outer"))
fmt.Printf("clothing.trousers=>%s\n", viper.Get("clothing.trousers"))
--- //输出
steve
true
35
[skateboarding snowboarding go]
map[jacket:leather trousers:denim]
map[inner:[alibaba tencent] outer:[google aws netflix]]
outer company => [google aws netflix]
clothing.trousers=>denim
|
1.6. 从环境变量读取
使用ENV变量时,Viper将ENV变量区分大小写读入,通过使用SetEnvPrefix
,您可以告诉Viper在读取环境变量时使用前缀
- AutomaticEnv()
- BindEnv(string…) : error
- SetEnvPrefix(string)
- SetEnvKeyReplacer(string…) *strings.Replacer
- AllowEmptyEnv(bool)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 环境变量读取
os.Setenv("SPF_ID", "13") // typically done outside of the app
os.Setenv("THIS_YEAR", "2019")
// 设置前缀
viper.SetEnvPrefix("spf") // will be uppercased automatically
// 绑定单个值,则拼接SPF前缀
viper.BindEnv("id")
// 绑定两个值,则第二个值为指定的环境变量全名
viper.BindEnv("year", "THIS_YEAR")
fmt.Printf("id=%s, this year is %s\n", viper.Get("id"), viper.Get("year"))
--- // 输出
id=13, this year is 2019
|
1.7. 与Flags参数一起使用
Viper能够绑定到标志,具体来说,Viper支持Cobra库中使用的Pflags。类似于BindEnv
,基于BindPFlags
进行FLAG绑定
1
2
3
4
5
6
7
8
| // Flag变量读取
pflag.Int("port", 1234, "Port to run Application server on")
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)
fmt.Printf("Flag port: %d\n", viper.Get("port"))
--- // 输出
Flag port: 1234
|
如果您不使用Pflags,Viper提供两个Go接口来绑定其他标志系统:
1.8. 与远端KV配置中心联合使用
需要导入import _ "github.com/spf13/viper/remote"
包。
Viper将读取从键/值存储(如etcd
或Consul
)中的路径检索的配置字符串(如JSON,TOML,YAML或HCL)。这些值优先于默认值,但会被从磁盘,标志或环境变量检索的配置值覆盖。
考虑到安全,Viper读取远程KV参数时候,支持加密传输。
1.8.1. Etcd配置读取
1
2
3
4
5
6
7
8
9
10
11
| // 添加远端Provider提供者etcd
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
// 指定加密传输
// viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
// 远端读取需要指定配置类型,支持 "json", "toml", "yaml", "yml", "properties", "props", "prop"
viper.SetConfigType("json")
// 解析读入远程配置
err := viper.ReadRemoteConfig()
|
1.8.2. Consul配置读取
1
2
3
| viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")
viper.SetConfigType("json") // Need to explicitly set this to json
err := viper.ReadRemoteConfig()
|
1.8.3. Etcd变更检测
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| // alternatively, you can create a new viper instance.
var runtime_viper = viper.New()
runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
runtime_viper.SetConfigType("yaml")
err := runtime_viper.ReadRemoteConfig()
// unmarshal config
runtime_viper.Unmarshal(&runtime_conf)
// open a goroutine to watch remote changes forever
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
}
// unmarshal new config into our runtime config struct. you can also use channel
// to implement a signal to notify the system of the changes
runtime_viper.Unmarshal(&runtime_conf)
}
}()
|
1.9. Viper获取配置值
1.9.1. 获取函数支持
根据值的类型获取值,如果找不到,每个Get函数都将返回零值,IsSet()提供密钥是否存在的检测:
1
2
3
4
5
6
7
8
9
10
11
12
| 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{}
|
1.9.2. 嵌套键访问
传递.
分隔的键路径来访问嵌套字段:
1
| viper.GetString("datastore.metric.host)
|
1.10. Viper提取sub-tree子树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| app:
cache1:
max-items: 100
item-size: 64
cache2:
max-items: 200
item-size: 80
// 获取cache1
subv := viper.Sub("app.cache1")
// subv
max-items: 100
item-size: 64
|
1.11. 解析特定值到struct,map
- Unmarshal(rawVal interface{}) : error
- UnmarshalKey(key string, rawVal interface{}) : error
1.11.1. Unmarshal
1
2
3
4
5
6
7
8
9
10
11
| // 类似于JSON的解析,可以解析到指定的结构体上面
type config struct {
Port int
Name string
PathMap string `mapstructure:"path_map"`
}
var C config
if err := viper.Unmarshal(&C); err != nil {
t.Fatalf("unable to decode into struct, %v", err)
}
|
1.11.2. Marshalling
基于指定文件格式进行排列整理
1
2
3
4
5
6
7
| // Marshalling to string
c := viper.AllSettings()
bs, err := yaml.Marshal(c)
if err != nil {
log.Fatalf("unable to marshal config to YAML: %v", err)
}
fmt.Printf("----\n%s\n", bs)
|
参考