Golang配置解析神器go?viper使用詳解
前言
對于現(xiàn)代應(yīng)用程序,尤其大中型的項目來說,在程序啟動和運(yùn)行時,往往需要傳入很多參數(shù)來控制程序的行為,這些參數(shù)可以通過以下幾種方式傳遞給程序:
- 命令行參數(shù)
- 環(huán)境變量
- 配置文件
顯然,對于Go項目而言,單個去讀取命令行、環(huán)境變量、配置文件并不難,但一個個讀取卻是很麻煩,有沒有一個第三方庫可以幫我們一次性讀取上面幾種數(shù)據(jù)源的配置呢?
有的,這里推薦使用viper庫,viper支持讀取不同數(shù)據(jù)源和不同格式的配置文件,是Go項目讀取配置的神器,今天跟著這篇文章,一起來探究一下吧!~
viper簡介
viper是一個很完善的Go項目配置解決方案,很多著名的開源項目都在使用,比如Hugo,Docker都使用了該庫,使用viper可以讓我們專注于自己的項目代碼,而不用自己寫那些配置解析代碼。
功能
- 支持配置key默認(rèn)值設(shè)置
- 支持讀取JSON,TOML,YAML,HCL,envfile和java properties等多種不同類型配置文件
- 可以監(jiān)聽配置文件的變化,并重新加載配置文件
- 讀取系統(tǒng)環(huán)境變量的值
- 讀取存儲在遠(yuǎn)程配置中心的配置數(shù)據(jù),如ectd,Consul,firestore等系統(tǒng),并監(jiān)聽配置的變化
- 從命令行讀取配置
- 從buffer讀取配置
- 可以顯示設(shè)置配置的值
viper配置優(yōu)先級
viper支持從多個數(shù)據(jù)源讀取配置值,因此當(dāng)同一個配置key在多個數(shù)據(jù)源有值時,viper讀取的優(yōu)先級如下:
- 顯示使用Set函數(shù)設(shè)置值
- flag:命令行參數(shù)
- env:環(huán)境變量
- config:配置文件
- key/value store:key/value存儲系統(tǒng),如(etcd)
- default:默認(rèn)值
優(yōu)先級示意圖

安裝viper
viper的安裝非常簡單,如同其他Go第三方包一樣,只需要go get命令即可安裝,如:
安裝
go get github.com/spf13/viper
使用
import "github.com/spf13/viper"
支持哪些文件格式
我們一直在說,viper支持多種不同格式的配置文件,到底是哪些格式呢?如下:
- json
- toml
- yaml
- yml
- properties
- props
- prop
- hcl
- tfvars
- dotenv
- env
- ini
key大小寫問題
viper的配置的key值是不區(qū)分大小寫,如:
# 小寫的key
viper.set("test","this is a test value")
# 大寫的key,也可以讀到值
fmt.Println(viper.get("TEST"))//輸出"this is a test value"使用指南
在了解了viper是什么之后,下面我們來看看要怎么使用viper去幫我們讀取配置。
如何訪問viper的功能
使用包名viper,如:
viper.SetDefault("key1","value")//調(diào)用包級別下的函數(shù)使用viper.New()函數(shù)創(chuàng)建一個Viper Struct,如:
viper := viper.New()
viper.SetDefault("key2","value2")其實,這就是Go包的編程慣例,對實現(xiàn)功能對象再進(jìn)行封裝,并通過包名來調(diào)用。
因此,下面所有示例中調(diào)用函數(shù)使用viper,可以是指包名viper,或者通過viper.New()返回的對象。
配置默認(rèn)值
viper.SetDefault("key1","value1")
viper.SetDefault("key2","value2")讀取配置文件
直接指定文件路徑
viper.SetConfigFile("./config.yaml")
viper.ReadInConfig()
fmt.Println(viper.Get("test"))多路徑查找
viper.SetConfigName("config") // 配置文件名,不需要后綴名
viper.SetConfigType("yml") // 配置文件格式
viper.AddConfigPath("/etc/appname/") // 查找配置文件的路徑
viper.AddConfigPath("$HOME/.appname") // 查找配置文件的路徑
viper.AddConfigPath(".") // 查找配置文件的路徑
err := viper.ReadInConfig() // 查找并讀取配置文件
if err != nil { // 處理錯誤
panic(fmt.Errorf("Fatal error config file: %w \n", err))
}讀取配置文件時,可能會出現(xiàn)錯誤,如果我們想判斷是否是因為找不到文件而報錯的,可以判斷err是否為ConfigFileNotFoundError。
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
} else {
}
}寫配置文件
除了讀取配置文件外,viper也支持將配置值寫入配置文件,viper提供了四個函數(shù),用于將配置寫回文件。
WriteConfig
WriteConfig函數(shù)會將配置寫入預(yù)先設(shè)置好路徑的配置文件中,如果配置文件存在,則覆蓋,如果沒有,則創(chuàng)建。
SafeWriteConfig
SafeWriterConfig與WriteConfig函數(shù)唯一的不同是如果配置文件存在,則會返回一個錯誤。
WriteConfigAs
WriteConfigAs與WriteConfig函數(shù)的不同是需要傳入配置文件保存路徑,viper會根據(jù)文件后綴判斷寫入格式。
SafeWriteConfigAs
SafeWriteConfigAs與WriteConfigAs的唯一不同是如果配置文件存在,則返回一個錯誤。
監(jiān)聽配置文件
viper支持監(jiān)聽配置文件,并會在配置文件發(fā)生變化,重新讀取配置文件和回調(diào)函數(shù),這樣可以避免每次配置變化時,都需要重啟啟動應(yīng)用的麻煩。
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})
viper.WatchConfig()從io.Reader讀取配置
除了支持從配置文件讀取配置外,viper也支持從實現(xiàn)了io.Reader接口的實例中讀取配置(其實配置文件也實現(xiàn)了io.Reader),如:
viper.SetConfigType("json") //設(shè)置格式
var yamlExample = []byte(`
{
"name":"小明"
}
`)
viper.ReadConfig(bytes.NewBuffer(yamlExample))
fmt.Println(viper.Get("name")) //輸出“小明”
顯示設(shè)置配置項
也可以使用Set函數(shù)顯示為某個key設(shè)置值,這種方式的優(yōu)先級最高,會覆蓋該key在其他地方的值,如:
viper.SetConfigType("json") //設(shè)置格式
var yamlExample = []byte(`
{
"name":"小明"
}
`)
viper.ReadConfig(bytes.NewBuffer(yamlExample))
fmt.Println(viper.Get("name")) //輸出:小明
viper.Set("name","test")
fmt.Println(viper.Get("name"))//輸出:test注冊和使用別名
為某個配置key設(shè)置別名,這樣可以方便我們在不改變key的情況下,使用別的名稱訪問該配置。
viper.Set("name", "test")
//為name設(shè)置一個username的別名
viper.RegisterAlias("username", "name")
//通過username可以讀取到name的值
fmt.Println(viper.Get("username"))
//修改name的配置值,username的值也發(fā)生改變
viper.Set("name", "小明")
fmt.Println(viper.Get("username"))
//修改username的值,name的值也發(fā)生改變
viper.Set("username", "測試")
fmt.Println(viper.Get("name"))讀取環(huán)境變量
對于讀取操作系統(tǒng)環(huán)境變量,viper提供了下面五個函數(shù):
- AutomaticEnv()
- BindEnv(string...) : error
- SetEnvPrefix(string)
- SetEnvKeyReplacer(string...) *strings.Replacer
- AllowEmptyEnv(bool)
要讓viper讀取環(huán)境變量,有兩種方式:
- 調(diào)用AutomaticEnv函數(shù),開啟環(huán)境變量讀取
fmt.Println(viper.Get("path"))
//開始讀取環(huán)境變量,如果沒有調(diào)用這個函數(shù),則下面無法讀取到path的值
viper.AutomaticEnv()
//會從環(huán)境變量讀取到該值,注意不用區(qū)分大小寫
fmt.Println(viper.Get("path"))- 使用BindEnv綁定某個環(huán)境變量
//將p綁定到環(huán)境變量PATH,注意這里第二個參數(shù)是環(huán)境變量,這里是區(qū)分大小寫的
viper.BindEnv("p", "PATH")
//錯誤綁定方式,path為小寫,無法讀取到PATH的值
//viper.BindEnv("p","path")
fmt.Println(viper.Get("p"))//通過p可以讀取PATH的值使用函數(shù)SetEnvPrefix可以為所有環(huán)境變量設(shè)置一個前綴,這個前綴會影響AutomaticEnv和BindEnv函數(shù)
os.Setenv("TEST_PATH","test")
viper.SetEnvPrefix("test")
viper.AutomaticEnv()
//無法讀取path的值,因為此時加上前綴,viper會去讀取TEST_PATH這個環(huán)境變量的值
fmt.Println(viper.Get("path"))//輸出:nil
fmt.Println(viper.Get("test_path"))//輸出:test環(huán)境變量大多是使用下劃號(_)作為分隔符的,如果想替換,可以使用SetEnvKeyReplacer函數(shù),如:
//設(shè)置一個環(huán)境變量
os.Setenv("USER_NAME", "test")
//將下線號替換為-和.
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
//讀取環(huán)境變量
viper.AutomaticEnv()
fmt.Println(viper.Get("user.name"))//通過.訪問
fmt.Println(viper.Get("user-name"))//通過-訪問
fmt.Println(viper.Get("user_name"))//原來的下劃線也可以訪問默認(rèn)的情況下,如果讀取到的環(huán)境變量值為空(注意,不是環(huán)境變量不存在,而是其值為空),會繼續(xù)向優(yōu)化級更低數(shù)據(jù)源去查找配置,如果想阻止這一行為,讓空的環(huán)境變量值有效,則可以調(diào)用AllowEmptyEnv函數(shù):
viper.SetDefault("username", "admin")
viper.SetDefault("password", "123456")
//默認(rèn)是AllowEmptyEnv(false),這里設(shè)置為true
viper.AllowEmptyEnv(true)
viper.BindEnv("username")
os.Setenv("USERNAME", "")
fmt.Println(viper.Get("username"))//輸出為空,因為環(huán)境變量USERNAME空
fmt.Println(viper.Get("password"))//輸出:123456與命令行參數(shù)搭配使用
viper可以和解析命令行庫相關(guān)flag庫一起工作,從命令行讀取配置,其內(nèi)置了對pflag庫的支持,同時也留有接口讓我們可以支持?jǐn)U展其他的flag庫
pflag
pflag.Int("port", 8080, "server http port")
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)
fmt.Println(viper.GetInt("port"))//輸出8080擴(kuò)展其他flag
如果我們沒有使用pflag庫,但又想讓viper幫我們讀取命令行參數(shù)呢?
package main
import (
"flag"
"fmt"
"github.com/spf13/viper"
)
type myFlag struct {
f *flag.Flag
}
func (m *myFlag) HasChanged() bool {
return false
}
func (m *myFlag) Name() string {
return m.f.Name
}
func (m *myFlag) ValueString() string {
return m.f.Value.String()
}
func (m *myFlag) ValueType() string {
return "string"
}
func NewMyFlag(f *flag.Flag) *myFlag {
return &myFlag{f: f}
}
func main() {
flag.String("username", "defaultValue", "usage")
m := NewMyFlag(flag.CommandLine.Lookup("username"))
viper.BindFlagValue("myFlagValue", m)
flag.Parse()
fmt.Println(viper.Get("myFlagValue"))
}遠(yuǎn)程key/value存儲支持
viper支持存儲或者讀取遠(yuǎn)程配置存儲中心和NoSQL(目前支持etcd,Consul,firestore)的配置,并可以實時監(jiān)聽配置的變化,不過需要在代碼中引入下面的包:
import _ "github.com/spf13/viper/remote"
現(xiàn)在遠(yuǎn)程配置中心存儲著以下JSON的配置信息
{
"hostname":"localhost",
"port":"8080"
}那么我們可以通過下面的方面連接到系統(tǒng),并讀取配置,也可以單獨(dú)開啟一個Goroutine實時監(jiān)聽配置的變化。
連接Consul
viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")連接etcd
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")連接firestore
viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")連接上配置中心后,就可以像讀取配置文件讀取其中的配置了,如:
viper.SetConfigType("json")
err := viper.ReadRemoteConfig()
fmt.Println(viper.Get("port")) // 輸出:8080
fmt.Println(viper.Get("hostname")) // 輸出:localhost監(jiān)聽配置系統(tǒng),如:
go func(){
for {
time.Sleep(time.Second * 5)
err := viper.WatchRemoteConfig()
if err != nil {
log.Errorf("unable to read remote config: %v", err)
continue
}
}
}()另外,viper連接etcd,Consul,firestore進(jìn)行配置傳輸時,也支持加解密,這樣可以更加安全,如果想要實現(xiàn)加密傳輸可以把AddRemoteProvider函數(shù)換為SecureRemoteProvider。
viper.SecureRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")訪問配置
viper可以幫我們讀取各個地方的配置,那讀到配置之后,要怎么用呢?
直接訪問
{
"mysql":{
"db":"test"
},
"host":{
"address":"localhost"
"ports":[
"8080",
"8081"
]
}
}對于多層級配置key,可以用逗號隔號,如:
viper.Get("mysql.db")
viper.GetString("user.db")
viper.Get("host.address")//輸出:localhost數(shù)組,可以用序列號訪問,如:
viper.Get("host.posts.1")//輸出: 8081也可以使用sub函數(shù)解析某個key的下級配置,如:
hostViper := viper.Sub("host")
fmt.Println(hostViper.Get("address"))
fmt.Println(hostViper.Get("posts.1"))viper提供了以下訪問配置的的函數(shù):
- 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
序列化
讀取了配置之后,除了使用上面列舉出來的函數(shù)訪問配置,還可以將配置序列化到struct或map之中,這樣可以更加方便訪問配置。
示例代碼
配置文件:config.yaml
host: localhost username: test password: test port: 3306 charset: utf8 dbName: test
解析代碼:
type MySQL struct {
Host string
DbName string
Port string
Username string
Password string
Charset string
}
func main() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.ReadInConfig()
var mysql MySQL
viper.Unmarshal(&mysql)//序列化
fmt.Println(mysql.Username)
fmt.Println(mysql.Host)
}對于多層級的配置,viper也支持序列化到一個復(fù)雜的struct中,如:
我們將config.yaml改為如下結(jié)構(gòu):
mysql: host: localhost username: test password: test port: 3306 charset: utf8 dbName: test redis: host: localhost port: 6379
示例程序
type MySQL struct {
Host string
DbName string
Username string
Password string
Charset string
}
type Redis struct {
Host string
Port string
}
type Config struct {
MySQL MySQL
Redis Redis
}
func main() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.ReadInConfig()
var config Config
viper.Unmarshal(&config)
fmt.Println(config.MySQL.Username)
fmt.Println(config.Redis.Host)
}判斷配置key是否存在
if viper.IsSet("user"){
fmt.Println("key user is not exists")
}打印所有配置
m := viper.AllSettings() fmt.Println(m)
小結(jié)
到此這篇關(guān)于Golang配置解析神器go viper使用詳解的文章就介紹到這了,更多相關(guān)go viper使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Goland 的安裝及激活教程(window、linux下安裝)
這篇文章主要介紹了Golang Goland 的安裝及激活詳細(xì)教程,包括window下安裝goland和linux下安裝goland,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10

