Golang配置解析神器go?viper使用詳解
前言
對(duì)于現(xiàn)代應(yīng)用程序,尤其大中型的項(xiàng)目來說,在程序啟動(dòng)和運(yùn)行時(shí),往往需要傳入很多參數(shù)來控制程序的行為,這些參數(shù)可以通過以下幾種方式傳遞給程序:
- 命令行參數(shù)
- 環(huán)境變量
- 配置文件
顯然,對(duì)于Go項(xiàng)目而言,單個(gè)去讀取命令行、環(huán)境變量、配置文件并不難,但一個(gè)個(gè)讀取卻是很麻煩,有沒有一個(gè)第三方庫(kù)可以幫我們一次性讀取上面幾種數(shù)據(jù)源的配置呢?
有的,這里推薦使用viper
庫(kù),viper支持讀取不同數(shù)據(jù)源和不同格式的配置文件,是Go項(xiàng)目讀取配置的神器,今天跟著這篇文章,一起來探究一下吧!~
viper簡(jiǎn)介
viper是一個(gè)很完善的Go項(xiàng)目配置解決方案,很多著名的開源項(xiàng)目都在使用,比如Hugo
,Docker
都使用了該庫(kù),使用viper
可以讓我們專注于自己的項(xiàng)目代碼,而不用自己寫那些配置解析代碼。
功能
- 支持配置key默認(rèn)值設(shè)置
- 支持讀取JSON,TOML,YAML,HCL,envfile和java properties等多種不同類型配置文件
- 可以監(jiān)聽配置文件的變化,并重新加載配置文件
- 讀取系統(tǒng)環(huán)境變量的值
- 讀取存儲(chǔ)在遠(yuǎn)程配置中心的配置數(shù)據(jù),如ectd,Consul,firestore等系統(tǒng),并監(jiān)聽配置的變化
- 從命令行讀取配置
- 從buffer讀取配置
- 可以顯示設(shè)置配置的值
viper配置優(yōu)先級(jí)
viper支持從多個(gè)數(shù)據(jù)源讀取配置值,因此當(dāng)同一個(gè)配置key在多個(gè)數(shù)據(jù)源有值時(shí),viper讀取的優(yōu)先級(jí)如下:
- 顯示使用Set函數(shù)設(shè)置值
- flag:命令行參數(shù)
- env:環(huán)境變量
- config:配置文件
- key/value store:key/value存儲(chǔ)系統(tǒng),如(etcd)
- default:默認(rèn)值
優(yōu)先級(jí)示意圖
安裝viper
viper的安裝非常簡(jiǎn)單,如同其他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)用包級(jí)別下的函數(shù)
使用viper.New()
函數(shù)創(chuàng)建一個(gè)Viper Struct,如:
viper := viper.New() viper.SetDefault("key2","value2")
其實(shí),這就是Go包的編程慣例,對(duì)實(shí)現(xiàn)功能對(duì)象再進(jìn)行封裝,并通過包名來調(diào)用。
因此,下面所有示例中調(diào)用函數(shù)使用viper,可以是指包名viper,或者通過viper.New()返回的對(duì)象。
配置默認(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 { // 處理錯(cuò)誤 panic(fmt.Errorf("Fatal error config file: %w \n", err)) }
讀取配置文件時(shí),可能會(huì)出現(xiàn)錯(cuò)誤,如果我們想判斷是否是因?yàn)檎也坏轿募鴪?bào)錯(cuò)的,可以判斷err是否為ConfigFileNotFoundError
。
if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { } else { } }
寫配置文件
除了讀取配置文件外,viper也支持將配置值寫入配置文件,viper提供了四個(gè)函數(shù),用于將配置寫回文件。
WriteConfig
WriteConfig函數(shù)會(huì)將配置寫入預(yù)先設(shè)置好路徑的配置文件中,如果配置文件存在,則覆蓋,如果沒有,則創(chuàng)建。
SafeWriteConfig
SafeWriterConfig與WriteConfig函數(shù)唯一的不同是如果配置文件存在,則會(huì)返回一個(gè)錯(cuò)誤。
WriteConfigAs
WriteConfigAs與WriteConfig函數(shù)的不同是需要傳入配置文件保存路徑,viper會(huì)根據(jù)文件后綴判斷寫入格式。
SafeWriteConfigAs
SafeWriteConfigAs與WriteConfigAs的唯一不同是如果配置文件存在,則返回一個(gè)錯(cuò)誤。
監(jiān)聽配置文件
viper支持監(jiān)聽配置文件,并會(huì)在配置文件發(fā)生變化,重新讀取配置文件和回調(diào)函數(shù),這樣可以避免每次配置變化時(shí),都需要重啟啟動(dòng)應(yīng)用的麻煩。
viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println("Config file changed:", e.Name) }) viper.WatchConfig()
從io.Reader讀取配置
除了支持從配置文件讀取配置外,viper也支持從實(shí)現(xiàn)了io.Reader接口的實(shí)例中讀取配置(其實(shí)配置文件也實(shí)現(xiàn)了io.Reader),如:
viper.SetConfigType("json") //設(shè)置格式 var yamlExample = []byte(` { "name":"小明" } `) viper.ReadConfig(bytes.NewBuffer(yamlExample)) fmt.Println(viper.Get("name")) //輸出“小明”
顯示設(shè)置配置項(xiàng)
也可以使用Set
函數(shù)顯示為某個(gè)key設(shè)置值,這種方式的優(yōu)先級(jí)最高,會(huì)覆蓋該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
注冊(cè)和使用別名
為某個(gè)配置key設(shè)置別名,這樣可以方便我們?cè)诓桓淖僰ey的情況下,使用別的名稱訪問該配置。
viper.Set("name", "test") //為name設(shè)置一個(gè)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", "測(cè)試") fmt.Println(viper.Get("name"))
讀取環(huán)境變量
對(duì)于讀取操作系統(tǒng)環(huán)境變量,viper提供了下面五個(gè)函數(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)用這個(gè)函數(shù),則下面無法讀取到path的值 viper.AutomaticEnv() //會(huì)從環(huán)境變量讀取到該值,注意不用區(qū)分大小寫 fmt.Println(viper.Get("path"))
- 使用BindEnv綁定某個(gè)環(huán)境變量
//將p綁定到環(huán)境變量PATH,注意這里第二個(gè)參數(shù)是環(huán)境變量,這里是區(qū)分大小寫的 viper.BindEnv("p", "PATH") //錯(cuò)誤綁定方式,path為小寫,無法讀取到PATH的值 //viper.BindEnv("p","path") fmt.Println(viper.Get("p"))//通過p可以讀取PATH的值
使用函數(shù)SetEnvPrefix可以為所有環(huán)境變量設(shè)置一個(gè)前綴,這個(gè)前綴會(huì)影響AutomaticEnv
和BindEnv
函數(shù)
os.Setenv("TEST_PATH","test") viper.SetEnvPrefix("test") viper.AutomaticEnv() //無法讀取path的值,因?yàn)榇藭r(shí)加上前綴,viper會(huì)去讀取TEST_PATH這個(gè)環(huán)境變量的值 fmt.Println(viper.Get("path"))//輸出:nil fmt.Println(viper.Get("test_path"))//輸出:test
環(huán)境變量大多是使用下劃號(hào)(_)作為分隔符的,如果想替換,可以使用SetEnvKeyReplacer
函數(shù),如:
//設(shè)置一個(gè)環(huán)境變量 os.Setenv("USER_NAME", "test") //將下線號(hào)替換為-和. 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)境變量不存在,而是其值為空),會(huì)繼續(xù)向優(yōu)化級(jí)更低數(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"))//輸出為空,因?yàn)榄h(huán)境變量USERNAME空 fmt.Println(viper.Get("password"))//輸出:123456
與命令行參數(shù)搭配使用
viper可以和解析命令行庫(kù)相關(guān)flag庫(kù)一起工作,從命令行讀取配置,其內(nèi)置了對(duì)pflag庫(kù)的支持,同時(shí)也留有接口讓我們可以支持?jǐn)U展其他的flag庫(kù)
pflag
pflag.Int("port", 8080, "server http port") pflag.Parse() viper.BindPFlags(pflag.CommandLine) fmt.Println(viper.GetInt("port"))//輸出8080
擴(kuò)展其他flag
如果我們沒有使用pflag庫(kù),但又想讓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存儲(chǔ)支持
viper支持存儲(chǔ)或者讀取遠(yuǎn)程配置存儲(chǔ)中心和NoSQL(目前支持etcd,Consul,firestore)的配置,并可以實(shí)時(shí)監(jiān)聽配置的變化,不過需要在代碼中引入下面的包:
import _ "github.com/spf13/viper/remote"
現(xiàn)在遠(yuǎn)程配置中心存儲(chǔ)著以下JSON的配置信息
{ "hostname":"localhost", "port":"8080" }
那么我們可以通過下面的方面連接到系統(tǒng),并讀取配置,也可以單獨(dú)開啟一個(gè)Goroutine實(shí)時(shí)監(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)行配置傳輸時(shí),也支持加解密,這樣可以更加安全,如果想要實(shí)現(xiàn)加密傳輸可以把AddRemoteProvider
函數(shù)換為SecureRemoteProvider
。
viper.SecureRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
訪問配置
viper可以幫我們讀取各個(gè)地方的配置,那讀到配置之后,要怎么用呢?
直接訪問
{ "mysql":{ "db":"test" }, "host":{ "address":"localhost" "ports":[ "8080", "8081" ] } }
對(duì)于多層級(jí)配置key,可以用逗號(hào)隔號(hào),如:
viper.Get("mysql.db") viper.GetString("user.db") viper.Get("host.address")//輸出:localhost
數(shù)組,可以用序列號(hào)訪問,如:
viper.Get("host.posts.1")//輸出: 8081
也可以使用sub
函數(shù)解析某個(gè)key的下級(jí)配置,如:
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) }
對(duì)于多層級(jí)的配置,viper也支持序列化到一個(gè)復(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang使用viper加載配置文件實(shí)現(xiàn)自動(dòng)反序列化到結(jié)構(gòu)
這篇文章主要為大家介紹了golang使用viper加載配置文件實(shí)現(xiàn)自動(dòng)反序列化到結(jié)構(gòu)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08Go雪花算法的作用領(lǐng)域及實(shí)現(xiàn)方法示例
這篇文章主要為大家介紹了Go雪花算法的作用領(lǐng)域及實(shí)現(xiàn)方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10golang中實(shí)現(xiàn)graphql請(qǐng)求的方法
這篇文章主要介紹了如何在golang中實(shí)現(xiàn)graphql請(qǐng)求,在本文中,我們介紹了如何使用gqlgen來構(gòu)建GraphQL服務(wù),需要的朋友可以參考下2023-04-04go 判斷兩個(gè) slice/struct/map 是否相等的實(shí)例
這篇文章主要介紹了go 判斷兩個(gè) slice/struct/map 是否相等的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go語言中同一個(gè)package中函數(shù)互相調(diào)用為undefined的解決
這篇文章主要介紹了Go語言中同一個(gè)package中函數(shù)互相調(diào)用為undefined的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03golang的時(shí)區(qū)和神奇的time.Parse的使用方法
這篇文章主要介紹了golang的時(shí)區(qū)和神奇的time.Parse的使用方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04