<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 每日一庫之完整配置解決方案:viper 源碼分析

          共 10050字,需瀏覽 21分鐘

           ·

          2021-02-02 09:36

          包地址:github.com/spf13/viper

          什么是Viper?

          Viper是Go應(yīng)用程序的完整配置解決方案,包括12-Factor應(yīng)用程序。它旨在在應(yīng)用程序中工作,并可以處理所有類型的配置需求和格式。它支持:

          • 設(shè)置默認(rèn)值
          • 從JSON,TOML,YAML,HCL和Java屬性配置文件中讀取
          • 實時觀看和重新讀取配置文件(可選)
          • 從環(huán)境變量中讀取
          • 從遠(yuǎn)程配置系統(tǒng)(etcd或Consul)讀取,并觀察變化
          • 從命令行標(biāo)志讀取
          • 從緩沖區(qū)讀取
          • 設(shè)置顯式值

          Viper可以被認(rèn)為是所有應(yīng)用程序配置需求的注冊表。

          為何選擇Viper?

          構(gòu)建現(xiàn)代應(yīng)用程序時,您不必?fù)?dān)心配置文件格式; 你想專注于構(gòu)建真棒軟件。Viper就是為此提供幫助的。

          Viper為您做了以下事情:

          1. 以JSON,TOML,YAML,HCL或Java屬性格式查找,加載和解組配置文件。
          2. 提供一種機(jī)制來為不同的配置選項設(shè)置默認(rèn)值。
          3. 提供一種機(jī)制來為通過命令行標(biāo)志指定的選項設(shè)置覆蓋值。
          4. 提供別名系統(tǒng),輕松重命名參數(shù),而不會破壞現(xiàn)有代碼。
          5. 可以很容易地區(qū)分用戶提供命令行或配置文件與默認(rèn)值相同的時間。

          Viper使用以下優(yōu)先順序。每個項目優(yōu)先于其下方的項目:

          使用Viper設(shè)置默認(rèn)值

          • explicit call to Set
          • flag
          • env
          • config
          • key/value store
          • default
          viper.SetDefault("ContentDir",?"content")
          viper.SetDefault("LayoutDir",?"layouts")
          viper.SetDefault("Taxonomies",?

          Viper讀取配置文件

          Viper需要最少的配置,因此它知道在哪里查找配置文件。Viper支持JSON,TOML,YAML,HCL和Java Properties文件。Viper可以搜索多個路徑,但目前單個Viper實例僅支持單個配置文件。Viper不會默認(rèn)使用任何配置搜索路徑,而是將默認(rèn)值決定應(yīng)用于應(yīng)用程序。

          以下是如何使用Viper搜索和讀取配置文件的示例。不需要任何特定路徑,但應(yīng)在預(yù)期配置文件的位置提供至少一個路徑。

          viper.SetConfigName("config")?//?name?of?config?file?(without?extension)
          viper.AddConfigPath("/etc/appname/")???//?path?to?look?for?the?config?file?in
          viper.AddConfigPath("$HOME/.appname")??//?call?multiple?times?to?add?many?search?paths
          viper.AddConfigPath(".")???????????????//?optionally?look?for?config?in?the?working?directory
          err?:=?viper.ReadInConfig()?//?Find?and?read?the?config?file
          if?err?!=?nil?{?//?Handle?errors?reading?the?config?file
          ?panic(fmt.Errorf("Fatal?error?config?file:?%s?\n",?err))
          }

          監(jiān)聽并重新讀取配置文件

          Viper支持在運行時讓應(yīng)用程序?qū)崟r讀取配置文件。

          需要重新啟動服務(wù)器以使配置生效的日子已經(jīng)一去不復(fù)返了,viper驅(qū)動的應(yīng)用程序可以在運行時讀取配置文件的更新,而不會錯過任何一個節(jié)拍。

          只需告訴viper實例watchConfig即可。您可以選擇為Viper提供每次發(fā)生更改時運行的功能。

          確保在調(diào)用之前添加所有configPath WatchConfig()

          viper.WatchConfig()
          viper.OnConfigChange(func(e?fsnotify.Event)?{
          ?fmt.Println("Config?file?changed:",?e.Name)
          })

          從io.Reader讀取配置

          Viper預(yù)定義了許多配置源,例如文件,環(huán)境變量,標(biāo)志和遠(yuǎn)程K / V存儲,但您不受它們的約束。您還可以實現(xiàn)自己的必需配置源并將其提供給viper。

          viper.SetConfigType("yaml")?//?或viper.SetConfigType(“YAML”)
          //?任何需要將此配置放入程序的方法
          var?yamlExample?=?[]byte(`
          Hacker:?true
          name:?steve
          hobbies:
          ??-?skateboarding
          ??-?snowboarding
          ??-?go
          clothing:
          jacket:?leather
          trousers:?denim
          age:?35
          eyes?:?brown
          beard:?true
          `
          )

          viper.ReadConfig(bytes.NewBuffer(yamlExample))
          viper.Get("name")

          Viper設(shè)置并覆蓋配置值

          viper.Set("Verbose",?true)
          viper.Set("LogFile",?LogFile)

          Viper注冊和使用別名

          別名允許多個鍵引用單個值

          viper.RegisterAlias("loud",?"Verbose")
          viper.Set("verbose",?true)?//?same?result?as?next?line
          viper.Set("loud",?true)???//?same?result?as?prior?line
          viper.GetBool("loud")?//?trueviper.GetBool("verbose")?//?true

          Viper使用環(huán)境變量

          Viper完全支持環(huán)境變量。有四種方法可以幫助使用ENV:

          AutomaticEnv()
          BindEnv(string...)?:?error
          SetEnvPrefix(string)
          SetEnvKeyReplacer(string...)?*strings.Replacer

          BindEnv需要一個或兩個參數(shù)。第一個參數(shù)是鍵名,第二個是環(huán)境變量的名稱。環(huán)境變量的名稱區(qū)分大小寫。如果未提供ENV變量名,則Viper將自動假設(shè)密鑰名稱與ENV變量名稱匹配,但ENV變量為IN ALL CAPS。當(dāng)您明確提供ENV變量名稱時,它不會自動添加前綴。

          使用ENV變量時要認(rèn)識到的一件重要事情是每次訪問時都會讀取該值。Viper在BindEnv調(diào)用時不會修復(fù)該值。

          AutomaticEnv尤其是當(dāng)與結(jié)合了強(qiáng)大的幫手 SetEnvPrefix。調(diào)用時,Viper將在任何viper.Get請求發(fā)出時檢查環(huán)境變量。它將適用以下規(guī)則。它將檢查一個環(huán)境變量,其名稱與大寫的鍵匹配,并以EnvPrefix前綴。

          SetEnvKeyReplacer允許您使用strings.Replacer對象重寫Env鍵到一定程度。如果要-Get()調(diào)用中使用或使用某些內(nèi)容 ,但希望環(huán)境變量使用_分隔符,則此選項非常有用。可以在中找到使用它的示例viper_test.go

          ENV實例

          SetEnvPrefix("spf")??//將自動大寫
          BindEnv("id")
          os.Setenv("SPF_ID",?"13")?//?通常在應(yīng)用以外完成
          id?:=?Get("id")?//?13

          使用flag

          Viper能夠綁定到flag。

          就像BindEnv,在調(diào)用綁定方法時,不會設(shè)置該值。這意味著您可以盡早綁定,甚至可以在init()函數(shù)中綁定 。

          對于單個標(biāo)志,該BindPFlag()方法提供此功能。

          serverCmd.Flags().Int("port",?1138,?"Port?to?run?Application?server?on")
          viper.BindPFlag("port",?serverCmd.Flags().Lookup("port"))

          您還可以綁定一組現(xiàn)有的pflags(pflag.FlagSet)

          pflag.Int("flagname",?1234,?"help?message?for?flagname")
          pflag.Parse()
          viper.BindPFlags(pflag.CommandLine)i?:=?viper.GetInt("flagname")

          在Viper中使用pflag并不排除使用 標(biāo)準(zhǔn)庫中使用標(biāo)志包的其他包。pflag包可以通過導(dǎo)入這些標(biāo)志來處理為標(biāo)志包定義的標(biāo)志。這是通過調(diào)用名為AddGoFlagSet()的pflag包提供的便利函數(shù)來實現(xiàn)的。

          package?main

          import?(
          ?"flag"
          ?"github.com/spf13/pflag"
          )

          func?main()?{
          ?//?using?standard?library?"flag"?package
          ?flag.Int("flagname",?1234,?"help?message?for?flagname")
          ?pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
          ?pflag.Parse()
          ?viper.BindPFlags(pflag.CommandLine)
          ?i?:=?viper.GetInt("flagname")?//?retrieve?value?from?viper
          ?...
          }

          flag接口

          如果您不使用,Viper提供兩個Go接口來綁定其他標(biāo)志系統(tǒng)Pflags

          FlagValue代表一個標(biāo)志。這是一個關(guān)于如何實現(xiàn)此接口的非常簡單的示例:

          type?myFlag?struct?{}
          func(f?myFlag)?HasChanged()?bool?{
          ?return??false
          }

          func(f?myFlag)?Name()?string?{
          ??return??"my-flag-name"
          }
          func(f?myFlag)?ValueString()?string?{
          ??return?"my?-flag-value"
          }
          func?(f?myFlag)?ValueType()?string?{
          ??return??"string"
          }

          一旦你的flag實現(xiàn)了這個接口,你可以告訴Viper綁定它:

          viper.BindFlagValue("my-flag-name",?myFlag{})

          遠(yuǎn)程key/value存儲

          要在Viper中啟用遠(yuǎn)程支持,請對viper/remote 包進(jìn)行空白導(dǎo)入:

          import?_?"github.com/spf13/viper/remote"

          Viper將讀取key/value存儲(如etcd或Consul)中的路徑檢索的配置字符串(如JSON,TOML,YAML或HCL)。這些值優(yōu)先于默認(rèn)值,但會被從磁盤,標(biāo)志或環(huán)境變量檢索的配置值覆蓋。

          Viper使用crypt從K / V存儲中檢索配置,這意味著您可以存儲加密的配置值,并在擁有正確的gpg密鑰環(huán)時自動解密。加密是可選的。

          您可以將遠(yuǎn)程配置與本地配置結(jié)合使用,也可以獨立使用。

          crypt有一個命令行幫助程序,您可以使用它來將配置放入K / V存儲區(qū)。crypt在http://127.0.0.1:4001上默認(rèn)為etcd 。

          $?go?get?github.com/xordataexchange/crypt/bin/crypt
          $?crypt?set?-plaintext?/config/hugo.json?/Users/hugo/settings/config.json

          確認(rèn)您的值已設(shè)置:

          $?crypt?get?-plaintext?/config/hugo.json

          遠(yuǎn)程key/value存儲示例 - 未加密

          viper.AddRemoteProvider("etcd",?"http://127.0.0.1:4001","/config/hugo.json")
          viper.SetConfigType("json")?//因為字節(jié)流中沒有文件擴(kuò)展名,支持的擴(kuò)展名是“json”,“toml”,“yaml”,“yml”,“properties”,“props”,“prop”
          err?:=?viper.ReadRemoteConfig()

          遠(yuǎn)程key/value存儲示例 - 加密

          viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
          viper.SetConfigType("json")?//因為字節(jié)流中沒有文件擴(kuò)展名,支持的擴(kuò)展名是“json”,“toml”,“yaml”,“yml”,“properties”,“props”,“prop”
          err?:=?viper.ReadRemoteConfig()

          監(jiān)聽etcd中的變化 - 未加密

          //或者,您可以創(chuàng)建一個新的viper實例
          var?runtime_viper?=?viper.New()
          runtime_viper.AddRemoteProvider("etcd",?"http://127.0.0.1:4001",?"/config/hugo.yml")
          runtime_viper.SetConfigType("yaml")//?第一次從遠(yuǎn)程配置中讀取
          err?:=?runtime_viper.ReadRemoteConfig()//解密配置
          runtime_viper.Unmarshal(&runtime_conf)//?打開一個goroutine來永遠(yuǎn)監(jiān)聽遠(yuǎn)程變化

          go?func(){
          ??for?{
          ????time.Sleep(time.Second?*?5)?//?每次請求后延遲
          ????err?:=?runtime_viper.WatchRemoteConfig()
          ????if?err?!=?nil?{
          ??????log.Errorf("unable?to?read?remote?config:?%v",?err)
          ??????continue
          ????}
          ????//將新配置解組到我們的運行時配置結(jié)構(gòu)中。您還可以使用通道
          ????//實現(xiàn)信號以通知系統(tǒng)更改
          ????runtime_viper.Unmarshal(&runtime_conf)
          ??}
          }()

          Viper獲取值

          在Viper中,有幾種方法可以根據(jù)值的類型獲取值。存在以下功能和方法:

          Get(key?string)?:?interface{}
          GetBool(key?string)?:?bool
          GetFloat64(key?string)?:?float64
          GetInt(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{}

          如果找不到,每個Get函數(shù)都將返回零值。IsSet()方法檢查給定密鑰是否存在。

          實例:

          viper.GetString("logfile")?//?case-insensitive?Setting?&?Gettingif
          viper.GetBool("verbose")?{
          ?fmt.Println("verbose?enabled")
          }

          訪問嵌套

          訪問器方法也接受深層嵌套鍵的格式化路徑。例如,如果加載了以下JSON文件:

          {
          ??"host":?{
          ????"address":?"localhost",
          ????"port":?5799
          ??},
          ??"datastore":?{
          ????"metric":?{"host":?"127.0.0.1","port":?3099},
          ????"warehouse":?{"host":?"198.0.0.1","port":?2112}
          ??}
          }

          Viper可以通過傳遞.分隔的鍵路徑來訪問嵌套字段:

          GetString("datastore.metric.host")?//?(returns?"127.0.0.1")

          這符合上面建立的優(yōu)先規(guī)則; 搜索路徑將在剩余的配置注冊表中級聯(lián),直到找到。

          例如,給定此配置文件,都datastore.metric.hostdatastore.metric.port已經(jīng)定義(并且可以被覆蓋)。如果另外datastore.metric.protocol在默認(rèn)值中定義,Viper也會找到它。

          但是,如果使用立即值datastore.metric覆蓋(通過標(biāo)志,環(huán)境變量,Set()方法,...),則所有子鍵 datastore.metric變?yōu)槲炊x,它們將被更高優(yōu)先級的配置級別“遮蔽”。

          最后,如果存在與分隔的鍵路徑匹配的鍵,則將返回其值。例如

          {
          ??"datastore.metric.host":?"0.0.0.0",
          ??"host":?{"address":?"localhost","port":?5799},
          ??"datastore":?{
          ????"metric":?{"host":?"127.0.0.1","port":?3099},
          ????"warehouse":?{"host":?"198.0.0.1","port":?2112}
          ??}
          }
          GetString("datastore.metric.host")?//?returns?"0.0.0.0"

          提取sub-tree

          例如

          app:
          ??cache1:
          ????max-items:?100
          ????item-size:?64
          ??cache2:
          ????max-items:?200
          ????item-size:?80

          #執(zhí)行后
          subv?:=?viper.Sub("app.cache1")
          #subv為
          max-items:100
          item-size:64

          假設(shè)

          func?NewCache(cfg?*Viper)?*Cache?{...}

          它根據(jù)格式化為的配置信息創(chuàng)建緩存subv。現(xiàn)在可以輕松地分別創(chuàng)建這兩個緩存:

          cfg1?:=?viper.Sub("app.cache1")
          cache1?:=?NewCache(cfg1)
          cfg2?:=?viper.Sub("app.cache2")
          cache2?:=?NewCache(cfg2)

          遍歷

          您還可以選擇Unmarshaling all或特定值到struct,map等。

          有兩種方法可以做到這一點:

          Unmarshal(rawVal?interface{})?:?error
          UnmarshalKey(key?string,?rawVal?interface{})?:?error

          例如:

          type?config?struct?{
          ??Port?int
          ??Name?string
          ??PathMap?string?`mapstructure:"path_map"`
          }

          var?C?configerr?:=?Unmarshal(&C)
          if?err?!=?nil?{
          ??t.Fatalf("unable?to?decode?into?struct,?%v",?err)
          }

          轉(zhuǎn)為字符串

          您可能需要將viper中保存的所有設(shè)置變?yōu)樽址皇菍⑺鼈儗懭胛募D梢允褂媚矚g的格式的marshaller和返回的配置AllSettings()

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

          func?yamlStringSettings()?string?{
          ?c?:=?viper.AllSettings()
          ?bs,?err?:=?yaml.Marshal(c)
          ?if?err?!=?nil?{
          ??t.Fatalf("unable?to?marshal?config?to?YAML:?%v",?err)
          ?}
          ?return?string(bs)
          }

          Viper or Vipers?

          Viper隨時可以使用。開始使用Viper無需配置或初始化。由于大多數(shù)應(yīng)用程序都希望使用單個中央存儲庫進(jìn)行配置,因此viper軟件包提供了此功能。它類似于單身人士。

          在上面的所有示例中,他們演示了使用viper的單例式方法。

          使用多個Viper

          您還可以創(chuàng)建許多不同的viper,以便在您的應(yīng)用程序中使用。每個都有自己獨特的配置和價值觀。每個都可以從不同的配置文件,鍵值存儲等中讀取.viper包支持的所有功能都被鏡像為viper上的方法。

          例如

          x?:=?viper.New()
          y?:=?viper.New()
          x.SetDefault("ContentDir",?"content")
          y.SetDefault("ContentDir",?"foobar")

          使用多viper時,用戶可以跟蹤不同的viper。

          viper 的使用非常簡單,它需要很少的設(shè)置。設(shè)置文件名(SetConfigName)、配置類型(SetConfigType)和搜索路徑(AddConfigPath),然后調(diào)用ReadInConfig。viper會自動根據(jù)類型來讀取配置。使用時調(diào)用viper.Get方法獲取鍵值。

          有幾點需要注意:

          • 設(shè)置文件名時不要帶后綴;
          • 搜索路徑可以設(shè)置多個,viper 會根據(jù)設(shè)置順序依次查找;
          • viper 獲取值時使用section.key的形式,即傳入嵌套的鍵名;
          • 默認(rèn)值可以調(diào)用viper.SetDefault設(shè)置。

          讀取鍵

          viper 提供了多種形式的讀取方法。在上面的例子中,我們看到了Get方法的用法。Get方法返回一個interface{}的值,使用有所不便。

          GetType系列方法可以返回指定類型的值。 其中,Type 可以為Bool/Float64/Int/String/Time/Duration/IntSlice/StringSlice。 但是請注意,如果指定的鍵不存在或類型不正確,GetType方法返回對應(yīng)類型的零值

          如果要判斷某個鍵是否存在,使用IsSet方法。

          另外,GetStringMapGetStringMapString直接以 map 返回某個鍵下面所有的鍵值對,前者返回map[string]interface{},后者返回map[string]stringAllSettingsmap[string]interface{}返回所有設(shè)置。

          Unmarshal

          viper 支持將配置Unmarshal到一個結(jié)構(gòu)體中,為結(jié)構(gòu)體中的對應(yīng)字段賦值。

          保存配置

          有時候,我們想要將程序中生成的配置,或者所做的修改保存下來。viper 提供了接口!

          • WriteConfig:將當(dāng)前的 viper 配置寫到預(yù)定義路徑,如果沒有預(yù)定義路徑,返回錯誤。將會覆蓋當(dāng)前配置;
          • SafeWriteConfig:與上面功能一樣,但是如果配置文件存在,則不覆蓋;
          • WriteConfigAs:保存配置到指定路徑,如果文件存在,則覆蓋;
          • SafeWriteConfig:與上面功能一樣,但是入股配置文件存在,則不覆蓋。

          viper的代碼很簡潔,配置讀取的思路是

          根據(jù)用戶設(shè)置的目錄加載文件,查找用戶設(shè)置的文件類型,如果沒有設(shè)置類型,則根據(jù)配置文件名的擴(kuò)展來確定類型,然后將配置文件unmarshal到一個map[string]interface{}中,unmarshal 方法根據(jù)不同的文件類型,使用不同的解析方法。


          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號 「polarisxu」,回復(fù)?ebook?獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 101
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品国产AⅤ一区二区三区东京热 | 亚洲无码 一道本 | 一本大道av | 国产成人无码久久久天美传媒 | 久久无人区 |