Viper

viper

我们将使用 Viper 包来作为yaml 和 config 信息的基础库。

Viper 是 Go 生态圈里一个很知名的项目,在 GitHub 上有接近 1.8 万个 star。

它支持以下特性:

  • 设置默认值(存入时设置)
  • 支持格式配置信息的格式包括 JSONTOMLYAMLHCLenvfileJava properties
  • 实时监控和重新读取配置文件(可选)
  • 从环境变量中读取
  • 从远程配置系统(etcd 或 Consul)读取并监控配置变化
  • 从命令行参数读取配置
  • 从 buffer 读取配置
  • 显式配置值

Viper 的功能比较丰富,这意味着面对多变的需求时我们可以很灵活。且作为明星项目,严格测试、使用广泛,很适合作为我们的配置模块的底层包。

安装

1
2
go get github.com/fsnotify/fsnotify
go get github.com/spf13/viper

viper基本用法

  • config.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"port": 10666,
"mysql": {
"url": "(127.0.0.1:3306)/biezhi",
"username": "root",
"password": "123456"
},
"redis": ["127.0.0.1:6377", "127.0.0.1:6378", "127.0.0.1:6379"],
"smtp": {
"enable": true,
"addr": "mail_addr",
"username": "mail_user",
"password": "mail_password",
"to": ["xxx@gmail.com", "xxx@163.com"]
}
}
  • config.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
port: 10666
mysql:
url: "(127.0.0.1:3306)/biezhi"
username: root
password: 123456
redis:
127.0.0.1:6377
127.0.0.1:6378
127.0.0.1:6379
smtp:
enable: true
addr: mail_addr
username: mail_user
password: mail_password
to:
xxx@gmail.com
xxx@163.com

建立默认值

一个好的配置系统应该支持默认值。键不需要默认值,但如果没有通过配置文件、环境变量、远程配置或命令行标志(flag)设置键,则默认值非常有用。

例如:

1
2
3
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})

读取配置文件

Viper需要最少知道在哪里查找配置文件的配置。Viper支持JSONTOMLYAMLHCLenvfileJava properties格式的配置文件。Viper可以搜索多个路径,但目前单个Viper实例只支持单个配置文件。Viper不默认任何配置搜索路径,将默认决策留给应用程序。

下面是一个如何使用Viper搜索和读取配置文件的示例。不需要任何特定的路径,但是至少应该提供一个配置文件预期出现的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
viper.SetConfigFile("./config.yaml") // 指定配置文件路径

viper.SetConfigName("config") // 配置文件名称(无扩展名)

viper.SetConfigType("yaml") // 如果配置文件的名称中没有扩展名,则需要配置此项

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))
}

在加载配置文件出错时,你可以像下面这样处理找不到配置文件的特定情况:

1
2
3
4
5
6
7
8
9
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件未找到错误;如果需要可以忽略
} else {
// 配置文件被找到,但产生了另外的错误
}
}

// 配置文件找到并成功解析

注意[自1.6起]: 你也可以有不带扩展名的文件,并以编程方式指定其格式。对于位于用户$HOME目录中的配置文件没有任何扩展名,如.bashrc

这里补充两个问题供读者解答并自行验证

当你使用如下方式读取配置时,viper会从./conf目录下查找任何以config为文件名的配置文件,如果同时存在./conf/config.json./conf/config.yaml两个配置文件的话,viper会从哪个配置文件加载配置呢?

1
2
viper.SetConfigName("config")
viper.AddConfigPath("./conf")

在上面两个语句下搭配使用viper.SetConfigType("yaml")指定配置文件类型可以实现预期的效果吗?

使用环境变量

Viper完全支持环境变量。这使Twelve-Factor App开箱即用。有五种方法可以帮助与ENV协作:

  • AutomaticEnv()
  • BindEnv(string...) : error
  • SetEnvPrefix(string)
  • SetEnvKeyReplacer(string...) *strings.Replacer
  • AllowEmptyEnv(bool)

使用ENV变量时,务必要意识到Viper将ENV变量视为区分大小写。

Viper提供了一种机制来确保ENV变量是惟一的。通过使用SetEnvPrefix,你可以告诉Viper在读取环境变量时使用前缀。BindEnvAutomaticEnv都将使用这个前缀。

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
2
3
4
5
6
SetEnvPrefix("spf") // 将自动转为大写
BindEnv("id")

os.Setenv("SPF_ID", "13") // 通常是在应用程序之外完成的

id := Get("id") // 13

从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
2
3
4
viper.GetString("logfile") // 不区分大小写的设置和获取
if viper.GetBool("verbose") {
fmt.Println("verbose enabled")
}

Viper
http://example.com/2023/01/17/viper/
Author
WYX
Posted on
January 17, 2023
Licensed under