33-viper 配置文件加载

仇炜
2023-12-01

配置文件加载:

  1. 主流的配置文件格式:toml、yaml、json、xml、ini(conf、properties)
  2. 配置的来源:文本文件、环境变量、配置中心(携程开源的apollo配置中心、nacos)
  3. 支持配置热加载

viper是适用于GO应用程序的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式。

安装

go get github.com/spf13/viper

什么是viper?

viper是适用于Go应用程序的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式,支持以下特性:

  • 设置默认值
  • 从JSON,TOML,YAML,HCL,envfile和java properties格式的配置文件中读取
  • 实时监控和重新读取配置文件
  • 从环境变量中读取
  • 从远程配置中心(etcd或consul)读取并监控配置变化
  • 从命令行参数读取配置
  • 从buffer读取配置
  • 显式配置值

为什么选择viper?

viper能够为你执行下列操作:

  1. 查找、加载和反序列化JSONTOMLYAMLHCLINIenvfileJava properties格式的配置文件。
  2. 提供一种机制为你的不同配置选项设置默认值。
  3. 提供一种机制来通过命令行参数覆盖指定选项的值
  4. 提供别名系统,以便在不破坏现有代码的情况下轻松重命名参数。
  5. 当用户提供了与默认值相同的命令行或配置文件时,可以很轻松得分辨区别。

Viper会按照下面的优先级。每个项目的优先级都高于它下面的项目:

  • 显示调用Set设置值
  • 命令行参数(flag)
  • 环境变量
  • 配置文件
  • Key/value存储
  • 默认值

重要:目前viper配置的键是大小写不敏感的。目前正在讨论是否将这一选项设为可选。

把值存入Viper

建立默认值

一个好的配置系统应该支持默认值。键不需要默认值,但如果没有通过配置文件、环境变量、远程配置或命令行标志(flag)设置键,则默认值非常有用。

例如:

viper.SetDefault("ContentDir","content")
viper.SetDefault("LayoutDir","layouts")
viper.SetDefault("Taxonomies",map[string]string{"tag":"tags","category":"categories"})
读取配置文件

Viper需要最少知道在哪里查找配置文件的配置。Viper支持JSONTOMLYAMLHCLenvfileJava properties格式的配置文件。Viper可以搜索多个路径,但目前单个Viper实例只支持单个配置文件。Viper不默认任何配置搜索路径,将默认决策留给应用程序。

下面是一个如何使用viper搜索和读取配置文件的示例。不需要任何特定的路径,但是至少应该提供一个配置文件预期出现的路径。

viper.SetConfigFile("./config.yaml") // 指定配置文件路径
viper.SetConfigName("config") // 配置文件名称(无扩展名)
viper.SetConfigType("yaml")  // 如果配置文件的名称没有扩展名,则需要配置此项
viper.AddConfigPath("/etc/appname/")  //  查找配置文件所在的路径
viper.AddConfigPath("$HOME/.appname") // 多次调用以添加多个搜索路径
viper.AddConfigPath(".")       // 还可以在工作目录中查找配置
err := viper.ReadInConfig()   // 查找并读取配置文件
if err!=nil {   // 处理读取配置文件的错误
  panic(fmt.Errorf("Fatal error config file: %s \n", err))
}

在加载配置文件出错时,可以像下面这样处理找不到配置文件的特定情况:

if err := viper.ReadInConfig(); err != nil{
  if _,ok :=err.(viper.ConfigFileNotFoundError); ok{
    // 配置文件未找到错误,如果需要可以忽略
  } else {
    // 配置文件被找到,但产生了另外的错误
  }
}

// 配置文件找到并成功解析
写入配置文件

从配置文件中读取配置文件是有用的,但是有时你想要存储在运行时所作的所有修改。为此,可以使用下面一组命令,每个命令都有自己的用途:

  • WriteConfig - 将当前的viper配置写入预定义的路径并覆盖(如果存在的话)。如果没有预定义的路径,则报错。
  • SafeWriteConfig - 将当前的viper配置写入预定义的路径。如果有没有预定义的路径,则报错。如果存在,将不会覆盖当前的配置文件。
  • WriteConfigAs - 将当前的viper配置写入给定的文件路径。将覆盖给定的文件(如果它存在的话)。
  • SafeWriteConfigAs - 将当前的viper配置写入给定的文件路径。不会覆盖给定的文件(如果它存在的话)。

根据经验,标记为safe的所有方法都不会覆盖任何文件,而是直接创建(如果不存在),而默认行为是创建或截断。

示例:

viper.WriteConfig() // 将当前配置写入"viper.AddConfigPath()"和“viper.SetConfigName”设置的预定义路径
viper.SafeWriteConfig()
viper.WriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.config") // 因为该配置文件写入过,所以会报错
viper.SafeWriteConfigAs("/path/to/my/.other_config")
监控并重新读取配置文件

viper支持在运行时实时读取配置文件的功能。

需要重新启动服务器使配置生效的日子已经一去不复返了,viper驱动的应用程序可以在运行时读取配置文件的更新,而不会错过任何消息。

只需要告诉viper实例watchconfig。可选的,你可以为viper提供一个回调函数,以便每次发生更改时运行。

viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
  // 配置文件发生变更之后会调用的回调函数
	fmt.Println("Config file changed:", e.Name)
})
从io.Reader读取配置

viper预先定义了许多配置源,如文件,环境变量,标志和远程k/v存储,但你不受其约束。你还可以实现自己所需的配置源并提供给viper。

viper.SetConfigType("yaml") // 或者 viper.SetConfigType("YAML")

// 任何需要将此配置添加到程序中的方法。
var yamlExample = []byte(`
Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:
  jacket: leather
  trousers: denim
age: 35
eyes : brown
beard: true
`)

viper.ReadConfig(bytes.NewBuffer(yamlExample))

viper.Get("name") // 这里会得到 "steve"
覆盖设置

这些可能来自命令行标志,也可能来自你自己的应用程序的逻辑。

viper.Set("Verbose",true)
viper.Set("LogFile",LogFile)
注册和使用别名

别名允许多个键引用单个值

  viper.RegisterAlias("loud", "Verbose")   // loud 是Verbose的别名
	viper.Set("Verbose", true)

	fmt.Println(viper.GetBool("Verbose")) // true
	fmt.Println(viper.GetBool("loud"))    // true
使用环境变量

Viper完全支持环境变量。这使Twelve-Factor App开箱即用。有五种方法可以帮助与ENV协作:

  • AutomaticEnv()
  • BindEnv(string...) : error
  • SetEnvPrefix(string)
  • SetEnvKeyReplacer(string...) *strings.Replacer
  • AllowEmptyEnv(bool)

使用ENV变量时,务必要意识到Viper将ENV变量视为区分大小写。

Viper提供了一种机制来确保ENV变量是惟一的。通过使用SetEnvPrefix,你可以告诉Viper在读取环境变量时使用前缀。BindEnvAutomaticEnv都将使用这个前缀。

BindEnv使用一个或两个参数。第一个参数是键名称,第二个是环境变量的名称。环境变量的名称区分大小写。如果没有提供ENV变量名,那么Viper将自动假设ENV变量与以下格式匹配:前缀+ “_” +键名全部大写。当你显式提供ENV变量名(第二个参数)时,它 不会 自动添加前缀。例如,如果第二个参数是“id”,Viper将查找环境变量“ID”。

在使用ENV变量时,需要注意的一件重要事情是,每次访问该值时都将读取它。Viper在调用BindEnv时不固定该值。

AutomaticEnv是一个强大的助手,尤其是与SetEnvPrefix结合使用时。调用时,Viper会在发出viper.Get请求时随时检查环境变量。它将应用以下规则。它将检查环境变量的名称是否与键匹配(如果设置了EnvPrefix)。

SetEnvKeyReplacer允许你使用strings.Replacer对象在一定程度上重写 Env 键。如果你希望在Get()调用中使用-或者其他什么符号,但是环境变量里使用_分隔符,那么这个功能是非常有用的。可以在viper_test.go中找到它的使用示例。

或者,你可以使用带有NewWithOptions工厂函数的EnvKeyReplacer。与SetEnvKeyReplacer不同,它接受StringReplacer接口,允许你编写自定义字符串替换逻辑。

默认情况下,空环境变量被认为是未设置的,并将返回到下一个配置源。若要将空环境变量视为已设置,请使用AllowEmptyEnv方法。

ENV示例

SetEnvPrefix("spf") // 将自动转为大写
BindEnv("id")

os.Setenv("SPF_ID", "13") // 通常是在应用程序之外完成的

id := Get("id") // 13
使用Flags

viper具有绑定到flag的能力。具体来说,viper支持cobra库中使用的pflag

// 从flag命令行获取参数
	// 1、登记哪些参数是从命令行获取的
	pflag.Int("xxx", 1234, "help message for xxx") // 参数:xxx 默认值1234, 帮助信息

	pflag.Parse() // 解析命令行
	viper.BindPFlags(pflag.CommandLine)

	result := viper.GetInt("xxx") // 从viper而不是从pflag检索值
	fmt.Println("result: ", result)

运行:

./viper 
result: 1234. // 没有加参数,输出默认值

./viper --xxx=45
result :45
 类似资料: