一、什么是Viper

Viper是一个方便Go语言应用程序处理配置信息的库。它可以处理多种格式的配置。它支持的特性:

  • 设置默认值
  • 从JSON、TOML、YAML、HCL和Java properties文件中读取配置数据
  • 可以监视配置文件的变动、重新读取配置文件
  • 从环境变量中读取配置数据
  • 从远端配置系统中读取数据,并监视它们(比如etcd、Consul)
  • 从命令参数中读物配置
  • 从buffer中读取
  • 调用函数设置配置信息

二、为什么要使用Viper

在构建现代应用程序时,您不必担心配置文件格式; 你可以专注于构建出色的软件。
Viper 可以做如下工作:

  • 加载并解析JSON、TOML、YAML、HCL 或 Java properties 格式的配置文件
  • 可以为各种配置项设置默认值
  • 可以在命令行中指定配置项来覆盖配置值
  • 提供了别名系统,可以不破坏现有代码来实现参数重命名
  • 可以很容易地分辨出用户提供的命令行参数或配置文件与默认相同的区别

Viper读取配置信息的优先级顺序,从高到低,如下:

  • 显式调用Set函数
  • 命令行参数
  • 环境变量
  • 配置文件
  • key/value 存储系统
  • 默认值

Viper 的配置项的key不区分大小写。

项目地址:https://github.com/spf13/viper

三、如何使用

3.1 设置默认值

默认值不是必须的,如果配置文件、环境变量、远程配置系统、命令行参数、Set函数都没有指定时,默认值将起作用。

1
2
3
viper.SetDefault("name", "xiaoming")
viper.SetDefault("age", "12")
viper.SetDefault("notifyList", []string{"xiaohong","xiaoli","xiaowang"})

3.2 读取配置文件

Viper支持JSON、TOML、YAML、HCL和Java properties文件。
Viper可以搜索多个路径,但目前单个Viper实例仅支持单个配置文件
Viper默认不搜索任何路径。

3.2.1 读取yaml配置文件

现demo根目录下有个config.yaml配置文件

1
2
3
4
5
6
7
8
9
10
mysql:
Host: "192.168.1.1"
Port: "3306"
User: "root"
Password: "putianhui"
DBName: "config"
Redis:
- 192.168.1.1:7000
- 192.168.1.2:7001
- 192.168.1.3:7002

解析示例

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
package main

import (
"github.com/spf13/viper"
"os"
"fmt"
)

func ReadYaml() {
// 获取当前程序的所在路径
path, err := os.Getwd()
if err != nil {
panic(err)
}
conf := viper.New()
// 设置yaml配置文件路径
conf.AddConfigPath(path)
// yaml配置文件名称
conf.SetConfigName("config.yaml")
// 配置文件的类型
conf.SetConfigType("yaml")

if err:= conf.ReadInConfig(); err != nil{
panic(err)
}

// 直接取对应配置
fmt.Println("======【yaml配置直接读取读取】======")
fmt.Println("Host:",conf.Get("mysql.Host"))
fmt.Println("Port:",conf.Get("mysql.Port"))
fmt.Println("User:",conf.Get("mysql.User"))
fmt.Println("Password:",conf.Get("mysql.Password"))
fmt.Println("DBName:",conf.Get("mysql.DBName"))
fmt.Println(conf.Get("mysql.Redis"))
}

func main() {
ReadYaml()
}

3.2.2 读取json配置文件

现demo根目录下有个config.json配置文件

1
2
3
4
5
6
7
8
9
{
"mysql": {
"User": "root",
"Password": "putianhui",
"Host": "127.0.0.1",
"Port": "3306",
"DBName": "example"
}
}

解析示例

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
package main

import (
"github.com/spf13/viper"
"os"
"fmt"
)

func ReadJson() {
// 获取当前程序的所在路径
path, err := os.Getwd()
if err != nil {
panic(err)
}
conf := viper.New()
// 设置yaml配置文件路径
conf.AddConfigPath(path)
// yaml配置文件名称
conf.SetConfigName("config.json")
// 配置文件的类型
conf.SetConfigType("json")

if err:= conf.ReadInConfig(); err != nil{
panic(err)
}

// 取对应配置
fmt.Println("======【yaml配置读取】======")
fmt.Println("Host:",conf.Get("mysql.Host"))
fmt.Println("Port:",conf.Get("mysql.Port"))
fmt.Println("User:",conf.Get("mysql.User"))
fmt.Println("Password:",conf.Get("mysql.Password"))
fmt.Println("DBName:",conf.Get("mysql.DBName"))
}

func main() {
ReadJson()
}

3.2.3 读取toml配置文件

现demo根目录下有个config.toml配置文件

1
2
3
4
5
6
[mysql]
Host = "192.168.1.1"
Port = "3306"
User = "root"
Password = "putianhui"
DBName = "config"

解析示例

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
package main

import (
"github.com/spf13/viper"
"os"
"fmt"
)

func ReadToml() {
// 获取当前程序的所在路径
path, err := os.Getwd()
if err != nil {
panic(err)
}
conf := viper.New()
// 设置yaml配置文件路径
conf.AddConfigPath(path)
// yaml配置文件名称
conf.SetConfigName("config.toml")
// 配置文件的类型
conf.SetConfigType("toml")

if err:= conf.ReadInConfig(); err != nil{
panic(err)
}

// 取对应配置
fmt.Println("======【toml配置读取】======")
fmt.Println("Host:",conf.Get("mysql.Host"))
fmt.Println("Port:",conf.Get("mysql.Port"))
fmt.Println("User:",conf.Get("mysql.User"))
fmt.Println("Password:",conf.Get("mysql.Password"))
fmt.Println("DBName:",conf.Get("mysql.DBName"))
}

func main() {
ReadToml()
}

3.2.4 读取命令行参数配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
"os"
"fmt"
)

func main() {
pflag.String("ip", "127.0.0.1", "Server running address") // 设置ip参数默认值为127.0.0.1
pflag.Int64("port", 8080, "Server running port") // 设置port参数默认值为8080
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)
fmt.Printf("ip :%s , port:%s\n", viper.GetString("ip"), viper.GetString("port"))
}

运行示例

1
2
$ go run main.go --ip=192.168.1.1
ip :192.168.1.1 , port:8080

3.2.5 读取环境变量参数

一般获取环境变量使用os包,比如:

1
2
getenv := os.Getenv("USER")
fmt.Print(getenv)

Viper也提供了一种方式:

1
2
3
4
5
6
7
8
//表示 先预加载匹配的环境变量
viper.AutomaticEnv()
//读取已经加载到default中的环境变量
if env := viper.Get("USER"); env == nil {
println("error!")
} else {
fmt.Println(env)
}

因为无论是线上环境还是测试环境,肯定有一些参数是公共不变的,那么这一部分参数是否可以抽出来作为一个单独的配置文件呢。所以这样配置文件可以分为两个部分:

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
"github.com/gobuffalo/packr"


func initConfig() (err error) {
box := packr.NewBox("./configs")
configType := "yml"
defaultConfig, _ := box.Find("default.yml")
v := viper.New()
v.SetConfigType(configType)
err = v.ReadConfig(bytes.NewReader(defaultConfig))
if err != nil {
return
}

configs := v.AllSettings()
// 将default中的配置全部以默认配置写入
for k, v := range configs {
viper.SetDefault(k, v)
}
env := os.Getenv("GO_ENV")
// 根据配置的env读取相应的配置信息
if env != "" {
envConfig, _ := box.Find(env + ".yml")

viper.SetConfigType(configType)
err = viper.ReadConfig(bytes.NewReader(envConfig))
if err != nil {
return
}
}
return
}

首先读取default.yml中的参数,将其写入default中。然后再根据环境变量读取不同环境中的参数。

这里使用了packr包,packr包的作用在于将静态资源打包至应用程序中。

3.3 监视配置文件,重新读取配置数据

Viper支持让你的应用程序在运行时拥有读取配置文件的能力。
只需要调用viper实例的WatchConfig函数,你也可以指定一个回调函数来获得变动的通知。

1
2
3
4
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})

由获取环境变量我们是不是可以想到多环境参数配置呢?针对线上环境,开发环境分别加载不同yml中的参数。

1
2
3
4
5
6
7
8
func initConfig() (err error) {
env := os.Getenv("GO_ENV")
viper.SetConfigName(env)
viper.AddConfigPath("./configs")
viper.SetConfigType("yml")
err = viper.ReadInConfig()
return
}