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配置檔案
-
程式碼如下
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 模式的切換。