12.viper配置管理
# 01.viper介绍
# 1.1 viper是什么?
Viper (opens new window)是适用于Go应用程序的完整配置解决方案。
它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式
viper功能
- 设置默认值
- 从
JSON
、TOML
、YAML
、HCL
、envfile
和Java properties
格式的配置文件读取配置信息 - 实时监控和重新读取配置文件(可选)
- 从环境变量中读取
- 从远程配置系统(etcd或Consul)读取并监控配置变化
- 从命令行参数读取配置
- 从buffer读取配置
- 显式配置值
# 1.2 为什么选择Viper?
在构建现代应用程序时,你无需担心配置文件格式;
Viper能够为你执行下列操作:
- 查找、加载和反序列化JSON、TOML、YAML、HCL、INI、envfile和Java properties格式的配置文件。
- 提供一种机制为你的不同配置选项设置默认值。
- 提供一种机制来通过命令行参数覆盖指定选项的值。
- 提供别名系统,以便在不破坏现有代码的情况下轻松重命名参数。
- 当用户提供了与默认值相同的命令行或配置文件时,可以很容易地分辨出它们之间的区别。
Viper会按照下面的优先,每个项目的优先级都高于它下面的项目
- 显示调用
Set
设置值 - 命令行参数(flag)
- 环境变量
- 配置文件
- key/value存储
- 默认值
- 显示调用
重要: 目前Viper配置的键(Key)是
大小写不敏感的
# 1.3 viper安装
go get github.com/spf13/viper
# 02.viper设置配置
# 2.1 建立默认值
一个好的配置系统应该支持默认值。
键不需要默认值,但如果没有通过配置文件、环境变量、远程配置或命令行标志(flag)设置键,则默认值非常有用。
例如:
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
2
3
# 2.2 读取配置文件
- Viper需要最少知道在哪里查找配置文件的配置。
- Viper支持
JSON
、TOML
、YAML
、HCL
、envfile
和Java properties
格式的配置文件。 - Viper可以搜索多个路径,但目前单个Viper实例只支持单个配置文件。
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))
}
2
3
4
5
6
7
8
9
10
# 2.3 写入配置文件
- 从配置文件中读取配置文件是有用的,但是有时你想要存储在运行时所做的所有修改。
- 为此,可以使用下面一组命令,每个命令都有自己的用途
viper.WriteConfig() // 将当前配置写入“viper.AddConfigPath()”和“viper.SetConfigName”设置的预定义路径
viper.SafeWriteConfig()
viper.WriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.config") // 因为该配置文件写入过,所以会报错
viper.SafeWriteConfigAs("/path/to/my/.other_config")
2
3
4
5
# 2.4 监控并重新读取配置文件
- 确保在调用
WatchConfig()
之前添加了所有的配置路径。
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
// 配置文件发生变更之后会调用的回调函数
fmt.Println("Config file changed:", e.Name)
})
2
3
4
5
# 2.4 覆盖设置
- 这些可能来自命令行标志,也可能来自你自己的应用程序逻辑。
viper.Set("Verbose", true)
viper.Set("LogFile", LogFile)
2
# 03.viper读取配置
# 3.1 几种访问值的方法
在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{}
例如:
viper.GetString("logfile") // 不区分大小写的设置和获取
if viper.GetBool("verbose") {
fmt.Println("verbose enabled")
}
2
3
4
# 3.2 访问嵌套的键
- 问器方法也接受深度嵌套键的格式化路径
- 例如,如果加载下面的JSON文件
{
"host": {
"address": "localhost",
"port": 5799
},
"datastore": {
"metric": {
"host": "127.0.0.1",
"port": 3099
},
"warehouse": {
"host": "198.0.0.1",
"port": 2112
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- Viper可以通过传入
.
分隔的路径来访问嵌套字段:
GetString("datastore.metric.host") // (返回 "127.0.0.1")
# 3.3 提取子树
- 例如,
viper
实例现在代表了以下配置:
app:
cache1:
max-items: 100
item-size: 64
cache2:
max-items: 200
item-size: 80
2
3
4
5
6
7
- 执行后:
subv := viper.Sub("app.cache1")
subv
现在就代表:
max-items: 100
item-size: 64
2
- 假设我们现在有这么一个函数:
func NewCache(cfg *Viper) *Cache {...}
- 它基于
subv
格式的配置信息创建缓存。现在,可以轻松地分别创建这两个缓存,如下所示:
cfg1 := viper.Sub("app.cache1")
cache1 := NewCache(cfg1)
cfg2 := viper.Sub("app.cache2")
cache2 := NewCache(cfg2)
2
3
4
5
# 3.4 反序列化
你还可以选择将所有或特定的值解析到结构体、map等。
有两种方法可以做到这一点:
Unmarshal(rawVal interface{}) : error
UnmarshalKey(key string, rawVal interface{}) : error
main.go
package main
import (
"fmt"
"github.com/spf13/viper"
)
type Config struct {
Port int `mapstructure:"port"`
Version string `mapstructure:"version"`
MySQLConfig `mapstructure:"mysql"`
}
type MySQLConfig struct {
Host string `mapstructure:"host"`
DbName string `mapstructure:"dbname"`
Port int `mapstructure:"port"`
}
func main() {
// 读取配置文件
viper.SetConfigFile("./config.yaml") // 指定配置文件路径
err := viper.ReadInConfig() // 查找并读取配置文件
if err != nil { // 处理读取配置文件的错误
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
var c Config
if err := viper.Unmarshal(&c); err != nil {
fmt.Printf("viper.Unmarshal failed, err:%v\n", err)
return
}
fmt.Printf("c:%#v\n", c)
}
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
- config.yaml
port: 8081
version: "v0.0.2"
mysql:
host: "127.0.0.1"
port: 13306
dbname: "sql_demo"
2
3
4
5
6
7
# 04.使用Viper示例
- 目录结构
# 4.1 ./conf/config.yaml
port: 8123
version: "v1.2.3"
2
# 4.2 gin中使用viper案例
- 这里用一个demo演示如何在gin框架搭建的web项目中使用
viper
,使用viper加载配置文件中的信息 - 并在代码中直接使用
viper.GetXXX()
方法获取对应的配置值。
package main
import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
"net/http"
)
func main() {
// 第一:viper配置
viper.AddConfigPath(".") // 还可以在工作目录中查找配置
viper.SetConfigName("config") // 配置文件名称(无扩展名)
viper.SetConfigType("yaml") // 如果配置文件的名称中没有扩展名,则需要配置此项
viper.AddConfigPath("./conf/") // 指定查找配置文件的路径
err := viper.ReadInConfig() // 读取配置信息
if err != nil { // 读取配置信息失败
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
// 第二:实时监控配置文件的变化
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
// 配置文件发生变更之后会调用的回调函数
fmt.Println("Config file changed:", e.Name)
})
// 第三:读取配置
r := gin.Default()
r.GET("/version", func(c *gin.Context) {
c.String(http.StatusOK, viper.GetString("version"))
})
r.Run()
}
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
# 4.3 结构体变量保存配置
package main
import (
"fmt"
"github.com/spf13/viper"
)
type Config struct {
Port int `mapstructure:"port"`
Version string `mapstructure:"version"`
}
var Conf = new(Config)
func main() {
// 第一:viper配置
viper.AddConfigPath(".") // 还可以在工作目录中查找配置
viper.SetConfigName("config") // 配置文件名称(无扩展名)
viper.SetConfigType("yaml") // 如果配置文件的名称中没有扩展名,则需要配置此项
viper.AddConfigPath("./conf/") // 指定查找配置文件的路径
err := viper.ReadInConfig() // 读取配置信息
if err != nil { // 读取配置信息失败
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
// 第二:将读取的配置信息保存至全局变量Conf
if err := viper.Unmarshal(Conf); err != nil {
panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err))
}
fmt.Printf("Conf:%#v\n", Conf) // 打印
}
/*
Conf:&main.Config{Port:8123, Version:"v1.2.3"}
*/
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