前言
Viper 是适用于Go应用程序(包括Twelve-Factor App)的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式
说白了 就是把配置信息存放到 viper 里面,需要用到配置信息的时候再从 viper 中拿出来
读取配置
设置默认值
go
viper.SetDefault("fileDir", "./")
意思就是,fileDir 是配置文件里面的一个配置,现在我要读取这个配置项,但是这个配置不存在,那么就读取默认值 " ./ "
读取配置文件
go
在这里插入代码片//读取配置文件
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("/etc/appname/")
viper.AddConfigPath("$HOME/.appname")
viper.AddConfigPath(".")
这几行代码就是在告诉 Viper 到哪些地方去找配置文件
查找并读取配置文件
go
err := viper.ReadInConfig() //查找并读取配置文件
这一行就是告诉 Viper 去查找并读取你之前指定的配置文件
err 是为了判断读取配置时是否发生错误,若发生错误则返回
如果出错了 ,想知道是不是因为 找不到配置文件 引发的错误,可以这样判断
go
if err != nil{
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
//配置文件未找到错误,可以自己决定要不要忽略
}else{
//配置文件被找到,但有其他的错误
}
}
写入配置
当然了,可以读取配置文件,也可以写入配置文件
1. viper.WriteConfig()
-
作用 :把当前 Viper 内存里的配置 写入到默认配置文件(即你用 ReadInConfig() 读到的那个文件),如果文件不存在,会报错
goviper.Set("app.name", "myApp") // 修改配置 err := viper.WriteConfig() // 写回文件 if err != nil { fmt.Println("写入失败:", err) }
viper.Set("app.name", "myApp")
= 在内存 里把 app.name 这个配置项的值改成了 "myApp",但是需要注意的是,Set 只是改变了内存中 app.name 这个配置项的值,要改变 viper 中 app.name 这个配置项的值,还需要执行
viper.WriteConfig()
这里写回文件,写到的是前面读取到的配置文件
viper.SetConfigName("config")
- viper.SafeWriteConfig()
-
作用:和 WriteConfig 类似,但是 如果 预定义路径不存在,则报错;存在,则不会覆盖当前的配置文件
goerr := viper.SafeWriteConfig()
- viper.WriteConfigAs(filename string)
-
作用:把 Viper 内存里的当前配置内容,写到指定的 filename 文件里,如果文件已存在,会覆盖
goerr := viper.WriteConfigAs("new_config.yaml")
- viper.SafeWriteConfigAs(filename string)
-
作用 :和
WriteConfigAs
类似,但如果文件已经存在,会报错不会覆盖goerr := viper.SafeWriteConfigAs("new_config.yaml")
监控并重新读取配置文件
Viper 支持在运行时实时读取配置文件的功能,可以理解为 边改边自动更新
有两种方式可以实现
go
//实时监控配置文件的变化
viper.WatchConfig()
//当配置变化之后调用的一个回调函数
viper.OnConfigChange(func(e fsnotify.Event) {
//配置文件发生变更之后会调用的回调函数
fmt.Println("Config file changed:", e.Name)
})
注册和使用别名
别名允许多个键引用单个值
go
//设置别名
viper.RegisterAlias("a1", "a2")
给 a2 起别名 a1
环境变量
-
viper.AutomaticEnv()
开启 自动绑定环境变量模式
意思是:当你调用 viper.Get("XXX") 时,如果配置文件里没有这个 key,它会去 系统环境变量 里找同名的环境变量
-
viper.BindEnv("a1")
手动绑定某个 key 和环境变量
比如你绑定了 a1,那么之后 viper.Get("a1") 会优先去环境变量里找 A1(或者根据前缀规则去找)
BindEnv("a1")
的意思是,告诉 Viper 当我读取 配置项 a1 时,如果在环境变量里能找到对应的值,就用环境变量的值覆盖换句话说,它把配置 key a1 和环境变量绑在了一起
-
大小写关系
环境变量名通常是 大写 ,而配置 key 一般是 小写
所以 Viper 内部会自动把 "a1" 映射成 "A1" 来找环境变量
例如:
export A1=hello
然后 Go 代码:
goviper.BindEnv("a1") fmt.Println(viper.GetString("a1"))
输出就是:
gohello
-
显式绑定环境变量名
也可以 显式绑定环境变量名:
goviper.BindEnv("a1", "MY_CUSTOM_ENV")
这样,配置项 "a1" 对应的环境变量就不再是 "A1",而是 MY_CUSTOM_ENV
-
-
viper.SetEnvPrefix("appname")
设置一个环境变量的前缀
有了这个前缀,viper 会去找 APPNAME_A1 这样的环境变量,而不是直接找 A1
举例:
export APPNAME_A1=123
那么直接
viper.Get("a1")
就能拿到 123
-
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
这是一个替换器,把配置 key 里的 . 转成 _,方便和环境变量对应
因为环境变量一般不支持 .,所以要写:
export APPNAME_DATABASE_HOST=127.0.0.1
然后你在代码里
viper.Get("database.host")
就能拿到 127.0.0.1
-
viper.AllowEmptyEnv(true)
允许环境变量为空字符串也算"合法存在"
默认情况下,如果环境变量存在但是为空,viper 可能会忽略它。加上这个就不会被忽略
使用 Flags
命令行参数是什么
平时我们运行程序时,可以带一些参数:
bash
./app --port 8080 --config ./config.yaml``
- --port 8080 就是告诉程序:端口改成 8080
- --config ./config.yaml 是告诉程序:用 config.yaml 这个配置文件
如果不加这些参数,程序就会使用默认值
Go 里有几种方式处理命令行参数
Go 原生有 flag 包,比如:
go
port := flag.Int("port", 1138, "Port to run the server on")
flag.Parse()
fmt.Println(*port)
运行 ./app --port 8080
,就会打印 8080。
但是 flag 比较简单,缺少子命令、分组管理等功能
Cobra:更强大的命令行框架
Cobra 提供了"命令 + 参数"的模式,比如:
bash
./app server --port 8080
./app client --addr localhost:1234
Cobra 的典型写法:
go
var serverCmd = &cobra.Command{
Use: "server",
Short: "Run the server",
Run: func(cmd *cobra.Command, args []string) {// 这里写启动逻辑
},
}
解释:
- Use
Use: "server",
表示命令的名字
比如,在命令函输入 myapp server
,就会执行 Run 里面的函数
- Short
Short: "Run the server",
当用户执行 myapp help 或 myapp server -h 的时候,就会显示这一行
可以想象为路边的站牌,就是帮助信息
- Run
Run: func(cmd *cobra.Command, args []string) {// 这里写启动逻辑 },
这是命令真正执行的逻辑,当用户在命令行输入 myapp server 的时候,Cobra 就会调用这里的函数
举例:
cmd 参数是当前这个命令对象,args 是命令行后面跟的参数(比如 myapp server 8080 里,args 就是 ["8080"])
Viper 具有绑定到标志 的能力。具体来说,Viper 库支持 Cobra 库中使用的 Pflag
与 BindEnv
类似,该值不是在调用绑定方法时设置的,而是在访问该方法时设置的。这意味着可以根据需要进行绑定
对于单个标志,BindPFlag()
方法提供此功能
go
serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
- serverCmd.Flags()
- 取到 serverCmd(一个 *cobra.Command)的 FlagSet
- FlagSet 就是存放命令行参数的地方
- Int("port", 1138, "Port to run Application server on")
- 给这个 serverCmd 注册了一个命令行参数:
- 名字:port
- 默认值:1138
- 说明:"Port to run Application server on"(打印 --help 时会显示)
- 之后你在命令行里就能写 --port=8080 来覆盖默认值
viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
- serverCmd.Flags().Lookup("port")
- 找到刚才定义的 port 这个 flag
- viper.BindPFlag("port", ...)
- 把这个 flag 绑定到 Viper 的配置系统中,key 是 "port"
- 意味着之后你用 viper.GetInt("port") 就能拿到最终的值(无论是默认值 1138,还是用户通过 --port 设置的值)