Skip to content

Viper

倉庫地址:spf13/viper: Go configuration with fangs (github.com)

文檔地址:spf13/viper: Go configuration with fangs (github.com)

TIP

官方正在討論向Viper2過渡,感興趣可以了解:Viper2

安裝

go get github.com/spf13/viper

介紹

Viper,直譯為眼鏡蛇,是一個針對 go 應用程序的完整的配置文件解決方案,可以處理幾乎所有類型的配置需求和格式,方便管理項目的配置文件,並且具有以下特色:

  • 默認值設置
  • 支持格式 JSON, TOML, YAML, HCL, envfile,Java properties
  • 支持實時監測和重載配置文件
  • 支持環境變量中讀取
  • 支持遠程配置系統讀取配置並監測變化
  • 支持讀取命令行標記
  • 支持緩沖區讀取
  • 支持顯示設置值

官方稱Viper可以滿足所有應用程序配置需求,開發者只需要專注於構建應用程序,讓Viper來負責配置管理,許多知名的項目都使用Viper

DANGER

Viper並不負責配置文件的加密與解密,也就是不會對配置文件做任何的安全處理。

讀取順序

Viper 使用如下的優先級來讀取配置:

  1. 顯示的值設置
  2. 命令行標記標記
  3. 環境變量
  4. 配置文件
  5. 鍵值存儲
  6. 默認值

TIP

Viper 配置中的鍵是不區分大小寫的,後續討論可能會將其置為可選項。

默認值

一個良好的配置系統應當支持默認值設置,雖然有時候並不一定需要,但在沒有設置配置文件的時候將會非常有用,下方是一個例子。

go
viper.SetDefault("filePath","./dir/img/usr")
viper.SetDefault("root","123456")

讀取配置文件

Viper 只需要很少的配置,就知道在哪裡查找配置文件。Viper 支持 JSON、 TOML、 YAML、 HCL、 INI、 envfile 和 JavaProperties 文件。Viper 可以同時搜索多個路徑,但目前單個 Viper 實例只支持單個配置文件。Viper 不會默認配置搜索路徑,將默認決策留給應用程序。

下面是使用 Viper 讀取配置文件的一個示例,不需要指定一個完整路徑,但在使用時至少應當提供一個配置文件。

go
func TestReadConfigFile(t *testing.T) {
   viper.SetConfigName("config.yml") // 讀取名為config的配置文件,沒有設置特定的文件後綴名
   viper.SetConfigType("yaml")       // 當沒有設置特定的文件後綴名時,必須要指定文件類型
   viper.AddConfigPath("./")         // 在當前文件夾下尋找
   viper.AddConfigPath("$HOME/")     // 使用變量
   viper.AddConfigPath(".")          // 在工作目錄下查找
   err := viper.ReadInConfig() //讀取配置
   if err != nil {
      log.Fatalln(err)
   }
}

也可以單獨處理配置文件未找到的情況

go
if err := viper.ReadInConfig(); err != nil {
  if _, ok := err.(viper.ConfigFileNotFoundError); ok {
    // 配置文件未找到
  } else {
    // 其他類型的錯誤
  }
}

以下是訪問配置的全部函數

  • Get(key string) : interface{}
  • GetBool(key string) : bool
  • GetFloat64(key string) : float64
  • GetInt(key string) : int
  • GetIntSlice(key string) : []int
  • GetString(key string) : string
  • GetStringMap(key string) : map[string]interface{}
  • GetStringMapString(key string) : map[string]string
  • GetStringSlice(key string) : []string
  • GetTime(key string) : time.Time
  • GetDuration(key string) : time.Duration
  • IsSet(key string) : bool
  • AllSettings() : map[string]interface{}

當訪問嵌套配置的時候通過.分隔符進行訪問,例如:

{
  "server":{
    "database":{
      "url": "mysql...."
    }
  }
}

可以通過GetString("server.database.url")來進行嵌套訪問

寫入配置文件

Viper 提供了一系列函數來方便開發者將運行時存儲的配置寫入配置文件中。

go
// WriteConfig 將配置寫入原配置文件中,不存在會報錯,存在的話會覆蓋
func WriteConfig() error { return v.WriteConfig() }

// SafeWriteConfig 將配置安全的寫入原配置文件中,不存在時會寫入,存在的話則不會覆蓋
func SafeWriteConfig() error { return v.SafeWriteConfig() }

// WriteConfigAs 將當前的配置寫入指定文件,文件不存在時會返回錯誤,存在時會覆蓋原有配置
func WriteConfigAs(filename string) error { return v.WriteConfigAs(filename) }

// SafeWriteConfigAs 如果指定的文件存在的話,將不會覆蓋原配置文件,文件存在的話會返回錯誤
func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) }

下方是一些示例:

go
func TestWritingConfig(t *testing.T) {
   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 以便每次發生變化時運行該函數。

go
func TestWatchingConfig(t *testing.T) {
  viper.OnConfigChange(func(e fsnotify.Event) {
    fmt.Println("配置文件已更改:", e.Name)
  })
  viper.WatchConfig()
}

別名

go
func TestAliases(t *testing.T) {
   viper.RegisterAlias("a", "b")
   viper.Set("a", 1)
   viper.Set("b", 2) //將會覆蓋掉a的配置
   fmt.Println(viper.GetInt("a"))
}

提取子結構

前面提到了通過.分隔符來訪問嵌套配置,其實還可以通過viper.Sub()函數來提取子結構,其返回值是一個 Viper 實例,如下示例:

yaml
cache:
  cache1:
    max-items: 100
    item-size: 64
  cache2:
    max-items: 200
    item-size: 80
go
cache1Config := viper.Sub("cache.cache1")
if cache1Config == nil { // 如果不存在返回nil
  panic("cache1配置不存在")
}

設置嵌套分隔符

當想要指定的 key 中包含.時,就必須要手動指定一個其他的分隔符,以防出現誤解析,例如:

go
viper.KeyDelimiter("/") //將分隔符設置為 /

反序列化

Viper 提供了兩個函數可以將配置反序列化到一個結構體或者 map,同樣支持嵌套結構:

  • Unmarshal(rawVal interface{}) : error
  • UnmarshalKey(key string, rawVal interface{}) : error
go
type config struct {
  Port int
  Name string
  PathMap string `mapstructure:"path_map"`
}

var C config

err := viper.Unmarshal(&C)
if err != nil {
  t.Fatalf("無法反序列化為結構體, %v", err)
}

序列化

將當前配置按照特定的格式序列化成字符串以便存入配置文件,通常情況支持 JSON, TOML, YAML, HCL, envfile,Java properties,

go
import (
  yaml "gopkg.in/yaml.v2"
  // ...
)

func yamlStringSettings() string {
  c := viper.AllSettings()
  bs, err := yaml.Marshal(c)
  if err != nil {
    log.Fatalf("無法將配置序列化為YAML: %v", err)
  }
  return string(bs)
}

多個實例

通常情況下使用 Viper 提供的全局實例足夠使用,但是由於一個實例只能映射一個配置文件,可以自行創建多個實例以實現更多的操作,例如:

go
x := viper.New()
y := viper.New()

x.SetDefault("ContentDir", "content")
y.SetDefault("ContentDir", "foobar")

//...

Golang學習網由www.golangdev.cn整理維護