viper
Viper是一个应用程序配置系统
支持的配置方式优先级(由高到低)如下
- flag:读取命令行指定的参数,设置配置值
- env:通过环境变量,设置配置值
- config:读取本地配置文件,设置配置值
- key/value store:读取远程配置文件,设置配置值
- default:设置默认值
为什么使用Viper
- 支持多种配置方式
- 对于配置文件方式,支持多种配置文件格式:
"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "dotenv", "env", "ini" - 对于远程服务,同样支持多种方式:
"etcd", "consul", "firestore" - 支持反序列化到结构体,绑定解析出来的配置值
安装
安装viper
$ mkdir example && cd example
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
$ go mod init viper-test
go: creating new go.mod: module viper-test
go: to add module requirements and sums:
go mod tidy
$ go get github.com/spf13/viper
go get: added github.com/spf13/viper v1.8.1
安装etcd
wget https://github.com/etcd-io/etcd/releases/download/v2.3.8/etcd-v2.3.8-linux-amd64.tar.gz
tar zxvf etcd-v2.3.8-linux-amd64.tar.gz
mv etcd-v2.3.8-linux-amd64 etcd-v2.3.8
cd etcd-v2.3.8
运行./etcd启动,在另起一个终端,进入到etcd-v2.3.8目录,执行如下命令,写入配置内容
ETCDCTL_API=2 ./etcdctl set /configs/config.json <<EOF
{
"source": "key/value store",
"account": {
"name": "antfoot1",
"age": 28
}
}
EOF
设置默认值
package main
import (
"fmt"
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
)
var newViper *viper.Viper
func main() {
newViper = viper.New()
fmt.Println("=====设置默认值方式=====")
SetDefault()
source := newViper.Get("source")
mysql := newViper.Get("mysql")
user := newViper.Get("user")
account := newViper.Get("account")
fmt.Printf("source: %T, %v, \n", source, source)
fmt.Printf("mysql: %T, %v, \n", mysql, mysql)
fmt.Printf("account的别名user: %T, %v, \n", user, user)
fmt.Printf("account: %T, %v, \n", account, account)
}
func SetDefault() {
newViper.SetDefault("SOURCE", "default")
newViper.SetDefault("mysql.host", "127.0.0.1")
newViper.RegisterAlias("user", "account")
newViper.SetDefault("user", map[string]interface{} {
"Name": "AntFoot",
"age": 18,
})
}
运行结果如下
# go run main.go
=====设置默认值方式=====
source: string, default,
mysql: map[string]interface {}, map[host:127.0.0.1],
account的别名user: map[string]interface {}, map[age:18 name:AntFoot],
account: map[string]interface {}, map[age:18 name:AntFoot],
- 配置键不区分大小写,
newViper.SetDefault("SOURCE", "default")设置SOURCE值为default,获取配置source := newViper.Get("source")使用小写source,这是由于在设置key之前,通过strings.ToLower(key)进行了格式转换 Viper默认分隔符v.keyDelim = ".",根据分隔符解析key的内容为map结构,newViper.SetDefault("mysql.host", "127.0.0.1")解析结果为map[string]interface {}, map[host:127.0.0.1]。- 自定义分隔符,则可以通过如下方式定义
newViper = viper.NewWithOptions(viper.KeyDelimiter("-"))替换newViper = viper.New() newViper.RegisterAlias("user", "account")注册别名
设置默认值 + 读取远程文件设置配置方式(etcd)
当前etcd中的配置内容
# ./etcdctl get /configs/config.json
{
"source": "key/value store",
"account": {
"name": "antfoot1",
"age": 28
}
}
编辑main.go文件,添加设置读取远程文件的配置方式
package main
import (
"fmt"
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
)
var newViper *viper.Viper
func main() {
newViper = viper.New()
fmt.Println("=====key/value store=====")
SetKeyValStore()
fmt.Println("=====设置默认值方式=====")
SetDefault()
source := newViper.Get("source")
mysql := newViper.Get("mysql")
user := newViper.Get("user")
account := newViper.Get("account")
fmt.Printf("source: %T, %v, \n", source, source)
fmt.Printf("mysql: %T, %v, \n", mysql, mysql)
fmt.Printf("account的别名user: %T, %v, \n", user, user)
fmt.Printf("account: %T, %v, \n", account, account)
}
func SetKeyValStore() {
if err := newViper.AddRemoteProvider("etcd", "http://127.0.0.1:2379","/configs/config.json"); err != nil {
fmt.Println("add remote provider: ", err)
return
}
newViper.SetConfigType("json")
if err := newViper.ReadRemoteConfig();err != nil {
fmt.Println("read remote config: ", err)
return
}
}
func SetDefault() {
newViper.SetDefault("SOURCE", "default")
newViper.SetDefault("mysql.host", "127.0.0.1")
newViper.RegisterAlias("user", "account") // 注册别名
newViper.SetDefault("user", map[string]interface{} {
"Name": "AntFoot",
"age": 18,
})
}
运行结果
# go run main.go
=====key/value store=====
=====设置默认值方式=====
source: string, key/value store,
mysql: map[string]interface {}, map[host:127.0.0.1],
account的别名user: map[string]interface {}, map[age:28 name:antfoot1],
account: map[string]interface {}, map[age:28 name:antfoot1],
先执行SetKeyValStore函数,读取etcd中的配置,并设置,然后执行SetDefault函数,设置默认值
打印结果source: string, key/value store, source字段的值为key/value store,name和account字段的值也被更新为etcd中的值
读取远程配置文件,设置配置值方式优先级高于设置默认值
同时使用所有配置方式
main.go同级目录下创建config.json文件,编写如下配置
{
"source": "config",
"config": "config.json"
}
编辑main.go文件如下
package main
import (
"flag"
"fmt"
"github.com/spf13/pflag"
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
"os"
)
var newViper *viper.Viper
func main() {
newViper = viper.New()
fmt.Println("=====flag=====")
SetFlag()
fmt.Println("=====env环境变量=====")
SetEnv()
fmt.Println("=====config配置文件方式=====")
SetConfig()
fmt.Println("=====key/value store=====")
SetKeyValStore()
fmt.Println("=====设置默认值方式=====")
SetDefault()
source := newViper.Get("source")
mysql := newViper.Get("mysql")
user := newViper.Get("user")
account := newViper.Get("account")
config := newViper.Get("config")
age := newViper.Get("age")
fmt.Printf("source: %T, %v, \n", source, source)
fmt.Printf("mysql: %T, %v, \n", mysql, mysql)
fmt.Printf("account的别名user: %T, %v, \n", user, user)
fmt.Printf("account: %T, %v, \n", account, account)
fmt.Printf("来自config方式 - config: %T, %v, \n", config, config)
fmt.Printf("来自flag方式 - age: %T, %v, \n", age, age)
}
// 使用标准库 "flag" 包
func SetFlag() {
flag.Int("age", 18, "年龄")
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
if err := newViper.BindPFlags(pflag.CommandLine); err != nil {
fmt.Println("fail to bind pflags err", err)
return
}
}
// 使用环境变量
func SetEnv() {
newViper.SetEnvPrefix("spf")
if err := newViper.BindEnv("source"); err != nil {
fmt.Println("bind env err", err)
}
if err := os.Setenv("SPF_SOURCE", "env"); err != nil {
fmt.Println("set env err ", err)
}
}
func SetConfig() {
newViper.SetConfigFile("config.json")
// 查找并读取配置文件
if err := newViper.ReadInConfig(); err != nil {
fmt.Println("fail to set config ", err)
return
}
}
func SetKeyValStore() {
if err := newViper.AddRemoteProvider("etcd", "http://127.0.0.1:2379","/configs/config.json"); err != nil {
fmt.Println("add remote provider: ", err)
return
}
newViper.SetConfigType("json")
if err := newViper.ReadRemoteConfig();err != nil {
fmt.Println("read remote config: ", err)
return
}
}
func SetDefault() {
newViper.SetDefault("SOURCE", "default")
newViper.SetDefault("mysql.host", "127.0.0.1")
newViper.RegisterAlias("user", "account") // 注册别名
newViper.SetDefault("user", map[string]interface{} {
"Name": "AntFoot",
"age": 18,
})
}
运行结果
# go run main.go --age=18
=====flag=====
=====env环境变量=====
=====config配置文件方式=====
=====key/value store=====
=====设置默认值方式=====
source: string, env,
mysql: map[string]interface {}, map[host:127.0.0.1],
account的别名user: map[string]interface {}, map[age:28 name:antfoot1],
account: map[string]interface {}, map[age:28 name:antfoot1],
来自config方式 - config: string, config.json,
来自flag方式 - age: int, 18,
Viper结构体
Viper定义的结构体如下
type Viper struct {
// 分隔符,分隔用于一次性访问嵌套值的键列表
keyDelim string
// 查找配置文件的一组路径
configPaths []string
// 读取配置的文件系统
fs afero.Fs
// 提供一组用于搜索配置的远程程序
remoteProviders []*defaultRemoteProvider
// 要在路径中查找的文件的名称
configName string
configFile string
configType string
configPermissions os.FileMode
envPrefix string
// 用于ini解析的特定命令
iniLoadOptions ini.LoadOptions
automaticEnvApplied bool
envKeyReplacer StringReplacer
allowEmptyEnv bool
config map[string]interface{}
override map[string]interface{}
defaults map[string]interface{} // 存储默认设置
kvstore map[string]interface{} // 保存远程服务配置
pflags map[string]FlagValue
env map[string][]string
aliases map[string]string
typeByDefValue bool
// 在对象上存储读取属性,以便我们可以按注释顺序写回。
// 仅当配置读取为属性文件时,才会使用此选项。
properties *properties.Properties
onConfigChange func(fsnotify.Event)
}
Viper支持的多种配置,每个配置的读取之后,保存到如下属性字段中
config保存配置文件方式读取到的配置内容defaults保存设置的默认值kvstore保存远程服务读取的配置pflags保存通过flag方式获取的配置env保存通过环境变量设置的配置
在读取配置的时候 newViper.Get("source"),会按照优先级,以此读取对应的属性字段的内容,如果存在则返回,否则继续向下查找
读取配置,反序列化到结构体
修改main.go文件,添加反序列化,完整代码如下
package main
import (
"flag"
"fmt"
"github.com/spf13/pflag"
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
"os"
)
type Mysql struct {
Host string `mapstructure:"host"`
}
type Account struct {
Name string `mapstructure:"name"`
Age int `mapstructure:"age"`
}
type GlobalConfig struct {
Source string `mapstructure:"source"`
Mysql Mysql `mapstructure:"mysql"`
Account Account `mapstructure:"account"`
User Account `mapstructure:"user"`
Config string `mapstructure:"config"`
Age int `mapstructure:"age"`
}
var newViper *viper.Viper
var c GlobalConfig
func main() {
newViper = viper.New()
fmt.Println("=====flag=====")
SetFlag()
fmt.Println("=====env环境变量=====")
SetEnv()
fmt.Println("=====config配置文件方式=====")
SetConfig()
fmt.Println("=====key/value store=====")
SetKeyValStore()
fmt.Println("=====设置默认值方式=====")
SetDefault()
source := newViper.Get("source")
mysql := newViper.Get("mysql")
user := newViper.Get("user")
account := newViper.Get("account")
config := newViper.Get("config")
age := newViper.Get("age")
fmt.Printf("source: %T, %v, \n", source, source)
fmt.Printf("mysql: %T, %v, \n", mysql, mysql)
fmt.Printf("account的别名user: %T, %v, \n", user, user)
fmt.Printf("account: %T, %v, \n", account, account)
fmt.Printf("来自config方式 - config: %T, %v, \n", config, config)
fmt.Printf("来自flag方式 - age: %T, %v, \n", age, age)
if err := newViper.Unmarshal(&c); err != nil {
fmt.Println("反序列化 err ", err)
return
}
fmt.Printf("反序列化结果: %T, %+v \n", c, c)
}
// 使用标准库 "flag" 包
func SetFlag() {
flag.Int("age", 18, "年龄")
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
if err := newViper.BindPFlags(pflag.CommandLine); err != nil {
fmt.Println("fail to bind pflags err", err)
return
}
}
// 使用环境变量
func SetEnv() {
newViper.SetEnvPrefix("spf")
if err := newViper.BindEnv("source"); err != nil {
fmt.Println("bind env err", err)
}
if err := os.Setenv("SPF_SOURCE", "env"); err != nil {
fmt.Println("set env err ", err)
}
}
func SetConfig() {
newViper.SetConfigFile("config.json")
// 查找并读取配置文件
if err := newViper.ReadInConfig(); err != nil {
fmt.Println("fail to set config ", err)
return
}
}
func SetKeyValStore() {
if err := newViper.AddRemoteProvider("etcd", "http://127.0.0.1:2379","/configs/config.json"); err != nil {
fmt.Println("add remote provider: ", err)
return
}
newViper.SetConfigType("json")
if err := newViper.ReadRemoteConfig();err != nil {
fmt.Println("read remote config: ", err)
return
}
}
func SetDefault() {
newViper.SetDefault("SOURCE", "default")
newViper.SetDefault("mysql.host", "127.0.0.1")
newViper.RegisterAlias("user", "account") // 注册别名
newViper.SetDefault("user", map[string]interface{} {
"Name": "AntFoot",
"age": 18,
})
}
运行结果
root@e9e43e92bedb:/data/packages/viper/example# go run main.go --age=18
=====flag=====
=====env环境变量=====
=====config配置文件方式=====
=====key/value store=====
=====设置默认值方式=====
source: string, env,
mysql: map[string]interface {}, map[host:127.0.0.1],
account的别名user: map[string]interface {}, map[age:28 name:antfoot1],
account: map[string]interface {}, map[age:28 name:antfoot1],
来自config方式 - config: string, config.json,
来自flag方式 - age: int, 18,
反序列化结果: main.GlobalConfig, {Source:env Mysql:{Host:127.0.0.1} Account:{Name:antfoot1 Age:28} User:{Name:antfoot1 Age:28} Config:config.json Age:18}
可以看到,通过newViper.Unmarshal(&c)解析到结构体中的数据和通过newViper.Get方法查询的结果是一样的