viper 讀取配置檔案

qinhan發表於2019-09-12

viper

專案地址 :github.com/spf13/viper

viper是什麼

  • go開發工具,主要是用於處理各種格式的配置檔案,簡化程式配置的讀取問題
  • viper支援:
    • 設定預設配置
    • 支援讀取JSON TOML YAML HCL 和Java屬性配置檔案
    • 監聽配置檔案變化,實時讀取讀取配置檔案內容
    • 讀取環境變數值
    • 讀取遠端配置系統(etcd Consul)和監控配置變化
    • 讀取命令Flag值
    • 讀取buffer值
    • 讀取確切值

安裝

go get github.com/fsnotify/fsnotify
go get github.com/spf13/viper

viper的基本用法

配置檔案

  • json配置檔案(config.json)

    {
    "port": 10666,
    "mysql": {
      "url": "(127.0.0.1:3306)/biezhi",
      "username": "root",
      "password": "123456"
    },
    "redis": ["127.0.0.1:6377", "127.0.0.1:6378", "127.0.0.1:6379"],
    "smtp": {
      "enable": true,
      "addr": "mail_addr",
      "username": "mail_user",
      "password": "mail_password",
      "to": ["xxx@gmail.com", "xxx@163.com"]
    }
    }
  • yaml配置檔案(config1.yaml)

    port: 10666
    mysql:
    url: "(127.0.0.1:3306)/biezhi"
    username: root
    password: 123456
    redis:
    - 127.0.0.1:6377
    - 127.0.0.1:6378
    - 127.0.0.1:6379
    smtp:
    enable: true
    addr: mail_addr
    username: mail_user
    password: mail_password
    to: 
    - xxx@gmail.com
    - xxx@163.com

本地配置檔案讀取方式

  • 將上述兩個配置檔案和下面的 main.go 放在統一目錄之下,即可實現讀取配置檔案
package main

import (
    "fmt"
    "log"

    "github.com/spf13/viper"
)

func init() {
    // viper.SetConfigName("config1") // 讀取yaml配置檔案
    viper.SetConfigName("config") // 讀取json配置檔案
    //viper.AddConfigPath("/etc/appname/")   //設定配置檔案的搜尋目錄
    //viper.AddConfigPath("$HOME/.appname")  // 設定配置檔案的搜尋目錄
    viper.AddConfigPath(".")      // 設定配置檔案和可執行二進位制檔案在用一個目錄
    if err := viper.ReadInConfig(); err != nil {
        if _, ok := err.(viper.ConfigFileNotFoundError); ok {
            // Config file not found; ignore error if desired
            log.Println("no such config file")
        } else {
            // Config file was found but another error was produced
            log.Println("read config error")
        }
        log.Fatal(err) // 讀取配置檔案失敗致命錯誤
    }
}

func main() {
    fmt.Println("獲取配置檔案的port", viper.GetInt("port"))
    fmt.Println("獲取配置檔案的mysql.url", viper.GetString(`mysql.url`))
    fmt.Println("獲取配置檔案的mysql.username", viper.GetString(`mysql.username`))
    fmt.Println("獲取配置檔案的mysql.password", viper.GetString(`mysql.password`))
    fmt.Println("獲取配置檔案的redis", viper.GetStringSlice("redis"))
    fmt.Println("獲取配置檔案的smtp", viper.GetStringMap("smtp"))
}
  • 程式碼詳解
    • viper.SetConfigName("config") 設定配置檔名為config, 不需要配置副檔名, 配置檔案的型別 viper會自動根據副檔名自動匹配.
    • viper.AddConfigPath(".")設定配置檔案搜尋的目錄, . 表示和當前編譯好的二進位制檔案在同一個目錄. 可以新增多個配置檔案目錄,如在第一個目錄中找到就不不繼續到其他目錄中查詢.
    • viper.ReadInConfig() 載入配置檔案內容
    • viper.Get*** 獲取配置檔案中配置項的資訊

viper的一些高階用法

  • viper設定配置項的預設值
// set default config
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})

fmt.Println(viper.GetBool("ContentDir"))
fmt.Println(viper.GetString("LayoutDir"))
fmt.Println(viper.GetStringMapString("Taxonomies"))
  • 監聽和重新讀取配置檔案
    • import "github.com/fsnotify/fsnotify"
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
    //viper配置發生變化了 執行響應的操作
    fmt.Println("Config file changed:", e.Name)
})

從環境變數變數中讀取

  • 主要用到的是下面三個個方法
// AutomaticEnv has Viper check ENV variables for all.
// keys set in config, default & flags
AutomaticEnv()

// BindEnv binds a Viper key to a ENV variable.
// ENV variables are case sensitive.
// If only a key is provided, it will use the env key matching the key, uppercased.
// EnvPrefix will be used when set when env name is not provided.
BindEnv(string…) : error

// SetEnvPrefix defines a prefix that ENVIRONMENT variables will use.
// E.g. if your prefix is "spf", the env registry will look for env
// variables that start with "SPF_".
SetEnvPrefix(string)
  • 簡單的使用demo如下所示
package main 

import (
    "fmt"
    "os"

    "github.com/spf13/viper"
)

func main() {
    prefix := "PROJECTNAME"
    envs := map[string]string{
        "LOG_LEVEL":      "INFO",
        "MODE":           "DEV",
        "MYSQL_USERNAME": "root",
        "MYSQL_PASSWORD": "xxxx",
    }
    for k, v := range envs {
        os.Setenv(fmt.Sprintf("%s_%s", prefix, k), v)
    }

    v := viper.New()
    v.SetEnvPrefix(prefix)
    v.AutomaticEnv()

    for k, _ := range envs {
        fmt.Printf("env `%s` = %s\n", k, v.GetString(k))
    }
}

獲取遠端配置

  • 使用github.com/spf13/viper/remote包 import _ "github.com/spf13/viper/remote"

  • Viper 可以從例如etcd、Consul的遠端Key/Value儲存系統的一個路徑上,讀取一個配置字串(JSON, TOML, YAML或HCL格式). 這些值優先於預設值,但會被從磁碟檔案、命令列flag、環境變數的配置所覆蓋.

  • 本人對consul比較熟悉,用它來做例子

    • 首先在本地啟動consul

      consul agent -dev
    • 並在consul上設定名為config的json配置檔案
      viper

    • 程式碼如下

    package main
    
    import (
        "fmt"
        "log"
    
        "github.com/spf13/viper"
        _ "github.com/spf13/viper/remote"
    )
    
    func main() {
        v := viper.New()
        v.AddRemoteProvider("consul", "localhost:8500", "config")
        v.SetConfigType("json") // Need to explicitly set this to json
        if err := v.ReadRemoteConfig(); err != nil {
              log.Println(err)
              return
        }
    
        fmt.Println("獲取配置檔案的port", v.GetInt("port"))
        fmt.Println("獲取配置檔案的mysql.url", v.GetString(`mysql.url`))
        fmt.Println("獲取配置檔案的mysql.username", v.GetString(`mysql.username`))
        fmt.Println("獲取配置檔案的mysql.password", v.GetString(`mysql.password`))
        fmt.Println("獲取配置檔案的redis", v.GetStringSlice("redis"))
        fmt.Println("獲取配置檔案的smtp", v.GetStringMap("smtp"))
    }

從io.Reader中讀取配置資訊

  • 首先給大家來段例子
package main

import (
    "bytes"
    "fmt"

    "github.com/spf13/viper"
)

func main() {
    v := viper.New()
    v.SetConfigType("json") // 設定配置檔案的型別

    // 配置檔案內容
    var jsonExample = []byte(`
{
  "port": 10666,
  "mysql": {
    "url": "(127.0.0.1:3306)/biezhi",
    "username": "root",
    "password": "123456"
  },
  "redis": ["127.0.0.1:6377", "127.0.0.1:6378", "127.0.0.1:6379"],
  "smtp": {
    "enable": true,
    "addr": "mail_addr",
    "username": "mail_user",
    "password": "mail_password",
    "to": ["xxx@gmail.com", "xxx@163.com"]
  }
}
`)
    //建立io.Reader
    v.ReadConfig(bytes.NewBuffer(jsonExample))

    fmt.Println("獲取配置檔案的port", v.GetInt("port"))
    fmt.Println("獲取配置檔案的mysql.url", v.GetString(`mysql.url`))
    fmt.Println("獲取配置檔案的mysql.username", v.GetString(`mysql.username`))
    fmt.Println("獲取配置檔案的mysql.password", v.GetString(`mysql.password`))
    fmt.Println("獲取配置檔案的redis", v.GetStringSlice("redis"))
    fmt.Println("獲取配置檔案的smtp", v.GetStringMap("smtp"))
}
  • 這個功能日常的使用情況較少,例如這樣的一個情景:
    • 配置檔案放在oss上或者github某個私有倉庫上,viper並沒有提供直接的介面去獲取,這樣我們可以基於第三方託管平臺的sdk寫一套獲取配置檔案bytes的工具,將結果放入io.Reader中,再進行配置檔案的解析。
  • 上述流程感覺好像比較雞肋,複雜了整個流程:我既然可以通過第三方的sdk直接拿到bytes,為何不自己直接進行解析呢?而要藉助viper來解析。可能有人會說,配置檔案如果格式不同呢?確實,viper的出現就是為了針對多種格式的配置檔案。但是在正式的專案中,配置檔案的格式一般不會變,可以自己寫一套解析的工具,也就沒有使用viper的需求了。而且對於某一種特定格式的配置檔案(JSON,YAML...),Golang已經有足夠強大的包來進行解析了。
  • 但是不得不承認 viper 的實現確實是很流弊的。在一般的快速開發過程中,直接使用 viper 確實可以幫助我們省去很多的麻煩,讓我們集中精力針對於業務邏輯的實現。
  • 個人覺得可以根據實際需求在 viper 再進行一層封裝,接入一些常用的第三方平臺的sdk(github,aliyun oss...),這樣即可以讀取本地配置檔案,也可以讀取遠端的配置檔案,可以通過命令列引數來實現 dev 模式和 deploy 模式的切換。

相關文章