对于现代应用程序,尤其大中型的项目来说,在程序启动和运行时,往往需要传入很多参数来控制程序的行为,这些参数可以通过以下几种方式传递给程序:
显然,对于Go项目而言,单个去读取命令行、环境变量、配置文件并不难,但一个个读取却是很麻烦,有没有一个第三方库可以帮我们一次性读取上面几种数据源的配置呢?有的,就是使用 viper 库,viper支持读取不同数据源和不同格式的配置文件,是Go项目读取配置的神器。
viper支持从多个数据源读取配置值,因此当同一个配置key在多个数据源有值时,viper读取的优先级如下:
优先级示意图
demo环境有两个配置文件config.yaml和config.json,其中config.yaml配置的是mysql,而config.json配置的是gin。demo环境的目录结构如下:
viper支持JSON
、TOML
、YAML
、HCL
、envfile
和Java properties
格式的配置文件。
第一种方式:直接指定文件路径
viper.SetConfigType("yaml")
viper.SetConfigFile("./connect/config.yaml") // 注意:如果使用相对路径,则是以main.go为当前位置与配置文件之间的路径
err := viper.ReadInConfig() // 查找并读取配置文件
if err != nil { // 处理读取配置文件的错误
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
mysql := viper.Get("mysql")
fmt.Println(mysql)
SetConfigType方法指定了viper读去配置文件的类型,SetConfigFile方法指定配置文件路径。上面的例子中指定读取yaml文件,则json配置文件就不会被读取,反之亦然。
但是,如果没有指定配置文件的类型,指定了两次配置文件的路径(想把.json文件和.yaml文件都读取),那么结果是怎样的呢?其实SetConfigFile方法中的路径有被覆盖的特点,也就是只能指定一个路径。如果指定了多个,谁在下面谁就会生效。所以在下面的例子中,config.json配置文件被读取。
viper.SetConfigFile("./config/config.yaml") //被覆盖了,不会被读取
viper.SetConfigFile("./config/config.json") //配置文件被读取,gin框架,端口:8080
err := viper.ReadInConfig() // 查找并读取配置文件
if err != nil { // 处理读取配置文件的错误
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
mysql := viper.Get("mysql")
gin := viper.Get("gin")
fmt.Println(mysql)
fmt.Println(gin)
运行结果如下:
第二种方式:多路径查找
viper.SetConfigName("config")
viper.AddConfigPath("D:\\GolandProjects\\ViperDemo\\config")
err := viper.ReadInConfig() // 查找并读取配置文件
if err != nil { // 处理读取配置文件的错误
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
mysql := viper.Get("mysql")
gin := viper.Get("gin")
fmt.Println(mysql)
fmt.Println(gin)
SetConfigName方法用来指定配置文件的名称(无扩展名),设置成功后viper只会读取以config开头的配置文件。AddConfigPath方法用来添加配置文件的路径,可以添加多个。但是,如果在同一个路径下有多个相同的配置文件名(扩展名不同),则只会读取第一个配置文件。看看我上面的config目录下的文件顺序,所以只会读取config.json的配置信息,而config.yaml的配置信息会被忽略。运行结果如下:
viper.Set("name", "green")
//为name设置一个username的别名
viper.RegisterAlias("username", "name")
//通过username可以读取到name的值
fmt.Println(viper.Get("username")) //green
//修改name的配置值,username的值也发生改变
viper.Set("name", "blue")
fmt.Println(viper.Get("username")) //blue
//修改username的值,name的值也发生改变
viper.Set("username", "red")
fmt.Println(viper.Get("name")) //red
一个好的配置系统应该支持默认值。如果没有通过配置文件、环境变量、远程配置或命令行标志等设置键的值,则默认值非常有用。例如:
func Viper() {
viper.SetDefault("secret_key", "123abc")
viper.SetDefault("mysql", map[string]interface{}{"host": "127.0.0.1", "port": 3306})
fmt.Println(viper.Get("secret_key"))
fmt.Println(viper.Get("MYSQL")) //viper中配置的key值是不区分大小写的
}
运行结果如下:
这种方式的优先级最高,会覆盖该key用其他方式设置的值。
viper.Set("gin", map[string]interface{}{"port": 8080})
viper.Set("gin", map[string]interface{}{"port": 8888}) //使用set设置值是可以被覆盖的
fmt.Println(viper.Get("gin"))
运行结果如下:
除了读取配置文件外,viper也支持将配置值写入配置文件,viper提供了四个函数,用于将配置写回文件。
viper.SetConfigFile("./config/config.yaml")
err := viper.ReadInConfig()
if err != nil {
panic(fmt.Errorf("failed to read config file: %s", err))
}
// 设置配置项
viper.Set("mysql.password", "password")
// 写入配置文件
err = viper.WriteConfig()
if err != nil {
panic(fmt.Errorf("failed to write config file: %s", err))
}
// 设置配置项
viper.Set("mysql.password", "password") //在上面的例子中已经将这个配置写入到了配置文件中
// 写入配置文件
err = viper.SafeWriteConfig() //返回一个错误
if err != nil {
panic(fmt.Errorf("failed to write config file: %s", err))
}
运行结果如下:
data := map[string]interface{}{
"database": map[string]interface{}{
"host": "localhost",
"port": 3306,
"username": "root",
"password": "password",
"dbname": "demo",
},
"server": map[string]interface{}{
"host": "127.0.0.1",
"port": 8080,
},
}
viper.SetConfigFile("./config/config.yaml")
err := viper.ReadInConfig()
if err != nil {
panic(fmt.Errorf("failed to read config file: %s", err))
}
//将后端生成的配置数据设置到 Viper 实例中
for key, value := range data {
viper.Set(key, value)
}
// 写入配置文件
err = viper.WriteConfigAs("./config/config.toml") //参数是配置文件的路径
if err != nil {
panic(fmt.Errorf("failed to write config file: %s", err))
}
WriteConfigAs方法后面的参数是配置文件的存放路径,如果没有相应的配置文件,就会新建一个配置文件。如果有就会直接把数据写入文件中。
Viper预先定义了许多配置源,如文件、环境变量、标志和远程K/V存储,但这只是预定义,其实自己也能实现自己所需的配置源并将其提供给viper。
viper.SetConfigType("toml") //设置格式
var tomlExample = []byte(`name="bing"`)//必须要是toml格式
if err := viper.ReadConfig(bytes.NewBuffer(tomlExample)); err != nil {
panic(err)
}
fmt.Println(viper.Get("name")) //输出“bing”
viper支持监听配置文件,并会在配置文件发生变化,重新读取配置文件和回调函数,这样可以避免每次配置变化时,都需要重启启动应用的麻烦。
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println(e.Name)
fmt.Println(viper.Get("mysql.password"))
})
如果被监听的配置文件是config.yaml,当config.yaml的内容发生变化时就会调用回调函数,回调函数把e.Name输出,这个e.Name就是config.yaml这个文件名,当然也可以输出自己想要的信息。
最后,关于viper的功能还有很多,由于项目中还没有遇到其他类型的需求,所以暂时总结这么多,关于viper的其他功能等以后再来补充。
参考链接: