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安装
1
| go get github.com/spf13/viper
|
02.viper设置配置
2.1 建立默认值
- 一个好的配置系统应该支持默认值。
- 键不需要默认值,但如果没有通过配置文件、环境变量、远程配置或命令行标志(flag)设置键,则默认值非常有用。
- 例如:
1 2 3
| viper.SetDefault("ContentDir", "content") viper.SetDefault("LayoutDir", "layouts") viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
|
2.2 读取配置文件
- Viper需要最少知道在哪里查找配置文件的配置。
- Viper支持
JSON、TOML、YAML、HCL、envfile和Java properties格式的配置文件。 - Viper可以搜索多个路径,但目前单个Viper实例只支持单个配置文件。
1 2 3 4 5 6 7 8 9 10
| 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 写入配置文件
- 从配置文件中读取配置文件是有用的,但是有时你想要存储在运行时所做的所有修改。
- 为此,可以使用下面一组命令,每个命令都有自己的用途
1 2 3 4 5
| viper.WriteConfig() viper.SafeWriteConfig() viper.WriteConfigAs("/path/to/my/.config") viper.SafeWriteConfigAs("/path/to/my/.config") viper.SafeWriteConfigAs("/path/to/my/.other_config")
|
2.4 监控并重新读取配置文件
- 确保在调用
WatchConfig()之前添加了所有的配置路径。
1 2 3 4 5
| viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println("Config file changed:", e.Name) })
|
2.4 覆盖设置
- 这些可能来自命令行标志,也可能来自你自己的应用程序逻辑。
1 2
| viper.Set("Verbose", true) viper.Set("LogFile", LogFile)
|
03.viper读取配置
3.1 几种访问值的方法
- 在Viper中,有几种方法可以根据值的类型获取值
Get(key string) : interface{}GetBool(key string) : boolGetFloat64(key string) : float64GetInt(key string) : intGetIntSlice(key string) : []intGetString(key string) : stringGetStringMap(key string) : map[string]interface{}GetStringMapString(key string) : map[string]stringGetStringSlice(key string) : []stringGetTime(key string) : time.TimeGetDuration(key string) : time.DurationIsSet(key string) : boolAllSettings() : map[string]interface{}
例如:
1 2 3 4
| viper.GetString("logfile") if viper.GetBool("verbose") { fmt.Println("verbose enabled") }
|
3.2 访问嵌套的键
- 问器方法也接受深度嵌套键的格式化路径
- 例如,如果加载下面的JSON文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "host": { "address": "localhost", "port": 5799 }, "datastore": { "metric": { "host": "127.0.0.1", "port": 3099 }, "warehouse": { "host": "198.0.0.1", "port": 2112 } } }
|
- Viper可以通过传入
.分隔的路径来访问嵌套字段:
1
| GetString("datastore.metric.host")
|
3.3 提取子树
1 2 3 4 5 6 7
| app: cache1: max-items: 100 item-size: 64 cache2: max-items: 200 item-size: 80
|
1
| subv := viper.Sub("app.cache1")
|
1 2
| max-items: 100 item-size: 64
|
1
| func NewCache(cfg *Viper) *Cache {...}
|
- 它基于
subv格式的配置信息创建缓存。现在,可以轻松地分别创建这两个缓存,如下所示:
1 2 3 4 5
| cfg1 := viper.Sub("app.cache1") cache1 := NewCache(cfg1)
cfg2 := viper.Sub("app.cache2") cache2 := NewCache(cfg2)
|
3.4 反序列化
你还可以选择将所有或特定的值解析到结构体、map等。
有两种方法可以做到这一点:
Unmarshal(rawVal interface{}) : errorUnmarshalKey(key string, rawVal interface{}) : error

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
| 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) }
|
1 2 3 4 5 6 7
| port: 8081 version: "v0.0.2"
mysql: host: "127.0.0.1" port: 13306 dbname: "sql_demo"
|
04.使用Viper示例

4.1 ./conf/config.yaml
1 2
| port: 8123 version: "v1.2.3"
|
4.2 gin中使用viper案例
- 这里用一个demo演示如何在gin框架搭建的web项目中使用
viper,使用viper加载配置文件中的信息 - 并在代码中直接使用
viper.GetXXX()方法获取对应的配置值。
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
| package main import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/gin-gonic/gin" "github.com/spf13/viper" "net/http" )
func main() { 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() }
|
4.3 结构体变量保存配置
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
| 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.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)) }
if err := viper.Unmarshal(Conf); err != nil { panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err)) } fmt.Printf("Conf:%#v\n", Conf) }
|