Go项目学习(2)-viper
前言
详细教程可直接参考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结合。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!