前言
详细教程可直接参考Github,已经很详细了,这里只进行简单入门知识总结。
介绍
用来获取配置,配置可来自flag、环境变量、配置文件、远程配置……
获取配置的优先级:Set
,flag,env,config,key/value stroe,default。
配置项的值可以直接通过Viper中一系列get函数获取,也可以将配置解析到结构体中。
配置的key是大小写不敏感的,在获取环境变量时认定系统环境变量是大小写敏感的。
想要进阶学习,就去看一遍Api
安装
新建项目后导入依赖
go get github.com/spf13/viper
使用
配置默认值
// 声明
func SetDefault(key string, value interface{})
func main() {
viper.SetDefault("foo", "bar")
fmt.Println(viper.GetString("foo"))
}
# output
bar
配置文件
直接指定一个文件:
// 声明
func SetConfigFile(in string)
也可以根据文件路径查找:
// 添加文件路径
func AddConfigPath(in string)
// 设置文件名称,不包含扩展名,默认文件名称为 config
func SetConfigName(in string)
// 设置文件类型
func SetConfigType(in string)
优先级问题
- 如果添加多个文件路径,最先添加的路径优先级越高
- 如果既指定文件,又有配置文件路径,好像是
SetConfigFile()
和SetConfigName()
谁后执行谁优先,个人不建议混着写。
配置完后,都要读取配置文件,不然取不到值:
// 声明
func ReadInConfig() error
创建配置文件setting.yaml
a:
b: 12
main.go
// 直接指定文件
func main() {
viper.SetConfigFile("./setting.yaml")
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
fmt.Println("config file not found")
} else {
panic(fmt.Errorf("failed to read config file"))
}
}
fmt.Println(viper.GetInt("a.b"))
}
// 根据指定路径查找
func main() {
viper.SetConfigName("setting")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
fmt.Println("config file not found")
} else {
panic(fmt.Errorf("failed to read config file"))
}
}
fmt.Println(viper.GetInt("a.b"))
}
# output
12
viper可以监听配置文件的变化
func WatchConfig()
除此之外,viper还提供了从io.Reader
中读取功能、配置文件change时间监听功能、写出文件功能,参考GitHub地址 Api地址
环境变量
BindEnv()
方法将viper key与环境变量绑定,调用BindEnv()
时不固定值,所以每次访问都是实时数据。
// 声明
func BindEnv(input ...string) error
func main() {
os.Setenv("AA", "value a")
os.Setenv("bb", "value b")
// 第一个变量是viper key,后面的是环境变量,
// 环境变量按照顺序与viper key进行匹配,直到环境变量有值
viper.BindEnv("a", "aa", "AA", "bb")
viper.BindEnv("b", "bb")
fmt.Println(viper.GetString("a"))
fmt.Println(viper.GetString("b"))
}
# output
value a
value b
AutomaticEnv()
会让viper自动检查环境变量是否匹配viper的key。
func main() {
os.Setenv("AA", "value aa")
viper.AutomaticEnv()
fmt.Println(viper.GetString("aa"))
}
# output
value aa
SetEnvPrefix()
定义环境变量的前缀。
// 声明
func SetEnvPrefix(in string)
需要注意的是,一旦设置了前缀,viper将查找的环境变量都会是大写字母。
func main() {
os.Setenv("V_A", "value a")
os.Setenv("V_b", "value b")
os.Setenv("v_c", "value c")
viper.SetEnvPrefix("v")
viper.AutomaticEnv()
fmt.Println(viper.GetString("a"))
fmt.Println(viper.GetString("b"))
fmt.Println(viper.GetString("c"))
}
# output 只打印了环境变量为"V_A"的值
value a
结合flag
viper团队自己弄了一套pflag,可以无损替换标准库的flag库,只需要在依赖声明的时候取个别名:import flag "github.com/spf13/pflag"
,viper之所以能结合flag,就是靠pflag。
pflag库同样用在cobra中,viper和cobra是同一个团队的,所以能很好的集成到一起:
// 官方示例
serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
不考虑cobra的情况:
func main() {
name := pflag.String("name", "", "usage of name")
_ = pflag.Int("age", 18, "usage of age")
pflag.Parse()
fmt.Println("get from flag: ", *name)
viper.BindPFlags(pflag.CommandLine)
fmt.Println("get from viper: ", viper.GetString("name"))
fmt.Println("get from viper: ", viper.GetString("age"))
}
# output
get from flag: abc
get from viper: abc
get from viper: 18
远程配置
初学暂时用不到,后面弄微服务再补充。Remote Key/Value Store Support
获取值
viper提供了针对不同类型,提供了一系列不同的get方法,详见Api。
需要注意的是如果没有查到对应的key,viper将返回相应类型的零值,可以用IsSet()
方法判断是否有对应的key:
// 声明
func IsSet(key string) bool
在json、yaml等文件中,多个层级的key,在viper中用.
隔开,同java的springboot。需要注意的是,如果key的父级被其他数据源的配置覆盖了,所有的子集都会变为未定义。
viper支持使用Sub()
方法取出子配置,可以作为子模块的配置传递。
// 声明
func Sub(key string) *Viper
将配置解码到结构体
viper支持将配置解析到结构体,类似java的springboot。
// 声明
func Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error
func UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error
这两个方法都差不多,只是后面那个多了一个指定的key。底层的字段映射是用的mapstructure库,opts也是和这个库有关,有必要的时候再研究这个吧。怎么将一个字符串分割成切片放进结构体,也是用这个库,参考这个博客,等需要的时候再研究。
需要注意的是,如果设置了监听文件的变化,结构体数据不会更新,需要在监听事件中重新解析给结构体。
# setting.yaml
user:
name: 张三
age: 18
aaa: aaa # 这里一开始设置的是其他值,在程序执行过程中,再修改成aaa
// main.go
type Setting struct {
User User
FieldA string `mapstructure:"aaa"`
FieldB string `mapstructure:"bbb"`
}
type User struct {
Name string
Age uint
}
func main() {
// 设置配置文件信息,监听变化,读取文件
viper.AddConfigPath(".")
viper.SetConfigName("setting")
viper.WatchConfig()
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
fmt.Println("config file not found")
return
} else {
panic(fmt.Errorf("faild to read config file: %w", err))
}
}
// 这里验证了不同数据源的配置,也能解析到结构体
viper.Set("bbb", "bbb")
// 解析到结构体
var s Setting
viper.Unmarshal(&s)
fmt.Println(s)
// 解析到map
var m map[string]interface{}
viper.Unmarshal(&m)
fmt.Println(m)
// 当监听到配置文件有变动时,需要重新赋值给结构体
viper.OnConfigChange(func(in fsnotify.Event) {
fmt.Println("changed")
viper.Unmarshal(&s)
})
// 验证中途修改配置能否更新到结构体
time.Sleep(10 * time.Second)
viper.Set("bbb", "ccc")
fmt.Println(s)
}
# output
{{张三 18} asd bbb}
map[aaa:asd bbb:bbb user:map[age:18 name:张三]]
# 这是手动修改配置文件的值,触发了change事件
changed
{{张三 18} aaa bbb}
多个viper实例
viper是开箱即用的,不需要额外配置,但也提供了方法创建多个实例,用法都一样。
func New() *Viper
后语
整理GitHub得到的这篇笔记,后面学cobra和viper结合。
标签:key,fmt,学习,Println,viper,func,Go,string From: https://www.cnblogs.com/xujiahao718/p/17715856.html