Go项目学习(2)-viper

前言

GitHub地址 Api地址

详细教程可直接参考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

更多信息参考:GitHub地址 Api地址

结合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结合。

posted @   季夏三  阅读(72)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示