Go 語言之 Viper 的使用
Viper 介紹
Viper:https://github.com/spf13/viper
安裝
go get github.com/spf13/viper
Viper 是什么?
Viper 是一個針對 Go 應用程式的完整配置解決方案,包括12-Factor 應用程式,它可以在應用程式中作業,并且可以處理所有型別的配置需求和格式,它支持:
Viper is a complete configuration solution for Go applications including 12-Factor apps. It is designed to work within an application, and can handle all types of configuration needs and formats. It supports:
- setting defaults
- reading from JSON, TOML, YAML, HCL, envfile and Java properties config files
- live watching and re-reading of config files (optional)
- reading from environment variables
- reading from remote config systems (etcd or Consul), and watching changes
- reading from command line flags
- reading from buffer
- setting explicit values
Viper can be thought of as a registry for all of your applications configuration needs.
Viper 可以被認為是滿足所有應用程式配置需求的注冊表,
為什么使用 Viper?
在構建現代應用程式時,您不需要擔心組態檔格式; 您需要專注于構建令人滿意的軟體,Viper 就是為此而生的,
Viper 可以為你做以下事情:
- Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, INI, envfile or Java properties formats.
- Provide a mechanism to set default values for your different configuration options.
- Provide a mechanism to set override values for options specified through command line flags.
- Provide an alias system to easily rename parameters without breaking existing code.
- Make it easy to tell the difference between when a user has provided a command line or config file which is the same as the default.
Viper uses the following precedence order. Each item takes precedence over the item below it:
- explicit call to
Set - flag
- env
- config
- key/value store
- default
Important: Viper configuration keys are case insensitive. There are ongoing discussions about making that optional.
重要提示: Viper 配置鍵是不區分大小寫的,目前正在討論是否將其設定為可選的,
Viper 實操 Putting Values into Viper
建立默認值
一個好的配置系統將支持默認值,密鑰不需要默認值,但如果沒有通過組態檔、環境變數、遠程配置或標志設定密鑰,則默認值非常有用,
Examples:
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
讀取組態檔
Viper 需要最小的配置,這樣它就知道在哪里查找組態檔,Viper 支持 JSON、 TOML、 YAML、 HCL、 INI、 envfile 和 JavaProperties 檔案,Viper 可以搜索多個路徑,但目前單個 Viper 實體只支持單個組態檔,Viper 不默認任何配置搜索路徑,將默認決策留給應用程式,
下面是如何使用 Viper 搜索和讀取組態檔的示例,不需要任何特定的路徑,但至少應該在需要組態檔的地方提供一個路徑,
viper.SetConfigName("config") // name of config file (without extension)
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
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: %w", err))
}
您可以處理沒有如下組態檔的特定情況:
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found; ignore error if desired
} else {
// Config file was found but another error was produced
}
}
// Config file found and successfully parsed
寫入組態檔
從組態檔中讀取是有用的,但有時您希望存盤在運行時所做的所有修改,為此,提供了一系列命令,每個命令都有自己的用途:
- WriteConfig-將當前 viper 配置寫入預定義的路徑(如果存在),如果沒有預定義的路徑就會出錯,將覆寫當前組態檔(如果存在),
- SafeWriteConfig-將當前 viper 配置寫入預定義的路徑,如果沒有預定義的路徑就會出錯,不會覆寫當前組態檔(如果存在),
- WriteConfigAs-將當前 viper 配置寫入給定的檔案路徑,將覆寫給定的檔案(如果存在),
- SafeWriteConfigAs-將當前 viper 配置寫入給定的檔案路徑,不會覆寫給定的檔案(如果存在),
As a rule of the thumb, everything marked with safe won't overwrite any file, but just create if not existent, whilst the default behavior is to create or truncate.
根據經驗,所有標記為 safe 的檔案都不會覆寫任何檔案,只是創建(如果不存在的話) ,而默認行為是創建或截斷,
A small examples section:
viper.WriteConfig() // writes current config to predefined path set by 'viper.AddConfigPath()' and 'viper.SetConfigName'
viper.SafeWriteConfig()
viper.WriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.config") // will error since it has already been written
viper.SafeWriteConfigAs("/path/to/my/.other_config")
監視和重新讀取組態檔
Viper 支持讓應用程式在運行時實時讀取組態檔的能力,
需要重新啟動服務器才能使配置生效的日子已經一去不復返了,使用 viper 的應用程式可以在運行時讀取組態檔的更新,而且不會錯過任何一次更新,
只需告訴 viper 實體監視 Config,您還可以為 Viper 提供一個函式,以便在每次發生更改時運行該函式,
確保在呼叫 WatchConfig ()之前添加了所有的 configPath
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})
viper.WatchConfig()
組態檔實時加載實操
package main
import (
"fmt"
"net/http"
"github.com/fsnotify/fsnotify"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
func main() {
// 設定默認值
viper.SetDefault("fileDir", "./")
// 讀取組態檔
viper.SetConfigFile("./config.yaml") // 指定組態檔路徑
viper.SetConfigName("config") // 組態檔名稱(無擴展名)
viper.SetConfigType("yaml") // 如果組態檔的名稱中沒有擴展名,則需要配置此項
viper.AddConfigPath("/etc/appname/") // 查找組態檔所在的路徑
viper.AddConfigPath("$HOME/.appname") // 多次呼叫以添加多個搜索路徑
viper.AddConfigPath(".") // 還可以在作業目錄中查找配置
err := viper.ReadInConfig() // 查找并讀取組態檔
if err != nil { // 處理讀取組態檔的錯誤
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
// 實時監控組態檔的變化 WatchConfig 開始監視組態檔的更改,
viper.WatchConfig()
// OnConfigChange設定組態檔更改時呼叫的事件處理程式,
// 當組態檔變化之后呼叫的一個回呼函式
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})
r := gin.Default()
r.GET("/version", func(c *gin.Context) {
// GetString以字串的形式回傳與鍵相關的值,
c.String(http.StatusOK, viper.GetString("version"))
})
r.Run()
}
運行并訪問:http://127.0.0.1:8080/version
Code/go/viper_demo via ?? v1.20.3 via ?? base
? go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /version --> main.main.func2 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
[GIN] 2023/06/18 - 11:42:10 | 200 | 39.5μs | 127.0.0.1 | GET "/version"
[GIN] 2023/06/18 - 11:42:10 | 404 | 750ns | 127.0.0.1 | GET "/favicon.ico"
Config file changed: /Users/qiaopengjun/Code/go/viper_demo/config.yaml
[GIN] 2023/06/18 - 11:43:41 | 200 | 17.459μs | 127.0.0.1 | GET "/version"
config.yaml
host: "127.0.0.1"
port: 8080
version: "v0.0.2"
mysql:
host: "127.0.0.1"
port: 3306
dbname: "sql_demo"
運行之后,訪問:http://127.0.0.1:8080/version,此時結果回傳 v0.0.1

修改 config.yaml 檔案中的version為 "v0.0.2"后,控制臺輸出 Config file changed: /Users/qiaopengjun/Code/go/viper_demo/config.yaml ,組態檔實時加載,訪問:http://127.0.0.1:8080/version,此時結果回傳 v0.0.2,

從 io.Reader 讀取配置
Viper 預定義了許多配置源,例如檔案、環境變數、標志和遠程 K/V 存盤,但是您并不系結到它們,您還可以實作自己所需的配置源,并將其提供給 viper,
viper.SetConfigType("yaml") // or viper.SetConfigType("YAML")
// any approach to require this configuration into your program.
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") // this would be "steve"
設定覆寫
它們可以來自命令列標志,也可以來自您自己的應用程式邏輯,
viper.Set("Verbose", true)
viper.Set("LogFile", LogFile)
注冊和使用別名
別名允許多個鍵參考單個值
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") // true
viper.GetBool("verbose") // true
使用環境變數
Viper 完全支持環境變數,這使12-Factor apps開箱即用,有五種方法可以幫助 ENV 的作業:
AutomaticEnv()BindEnv(string...) : errorSetEnvPrefix(string)SetEnvKeyReplacer(string...) *strings.ReplacerAllowEmptyEnv(bool)
當使用 ENV 變數時,重要的是要認識到 Viper 將 ENV 變數視為區分大小寫的,
Viper 提供了一種機制來嘗試確保 ENV 變數是唯一的,通過使用 SetEnvPrefix,可以告訴 Viper 在讀取環境變數時使用前綴,BindEnv 和 AutomaticEnv 都將使用此前綴,
BindEnv 接受一個或多個引數,第一個引數是鍵名,其余的是要系結到這個鍵的環境變數的名稱,如果提供了多個,它們將按照指定的順序優先,環境變數的名字是區分大小寫的,如果沒有提供 ENV 變數名,那么 Viper 將自動假定 ENV 變數匹配以下格式: 前綴 + “ _”+ ALL CAPS 中的鍵名,當顯式提供 ENV 變數名(第二個引數)時,它不會自動添加前綴,例如,如果第二個引數是“ ID”,Viper 將查找 ENV 變數“ ID”,
使用 ENV 變數時需要注意的一點是,每次訪問該值時都會讀取它,當呼叫 BindEnv 時,Viper 不會修復該值,
AutomaticEnv 是一個強大的助手,特別是當與 SetEnvPrefix 組合時,當召喚時,Viper會隨時檢查是否有環境變數,請求已經提出,它將適用以下規則,如果設定了大寫和前綴為 EnvPrefix 的鍵,它將檢查是否有名稱與之匹配的環境變數,
SetEnvKeyReplace 允許您使用字串,物件重寫一定范圍內的 Env 鍵,如果您希望在 Get ()呼叫中使用-或其他內容,但希望環境變數使用 _ 分隔符,那么這很有用,在viper_test.go 中可以找到使用它的示例,
或者,您可以使用帶有 NewWithOptions 工廠函式的 EnvKeyReplace,與 SetEnvKeyReplace 不同,它接受 StringReplace 介面,允許您撰寫自定義字串替換邏輯,
默認情況下,慷訓境變數被認為是未設定的,并將回退到下一個配置源,若要將慷訓境變數視為設定,請使用 AllowEmptyEnv 方法,
Env example
SetEnvPrefix("spf") // will be uppercased automatically
BindEnv("id")
os.Setenv("SPF_ID", "13") // typically done outside of the app
id := Get("id") // 13
Flags 的使用
Viper 具有系結到標志的能力,具體來說,Viper 支持在 Cobra 庫中使用的 Pflags,
與 BindEnv 一樣,該值不是在呼叫系結方法時設定的,而是在訪問該方法時設定的,這意味著您可以盡早進行系結,即使在 init ()函式中也是如此,
對于單個標志,BindPFlag ()方法提供了此功能,
Example:
serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
您還可以系結一組現有的 pFlag (pFlag. FlagSet) :
Example:
pflag.Int("flagname", 1234, "help message for flagname")
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)
i := viper.GetInt("flagname") // retrieve values from viper instead of pflag
在 Viper 中使用 pFlag 并不排除在其它包使用標準庫中 flag package, pflag 包可以通過匯入這些flag 來處理為flag 包定義的flags,這是通過呼叫一個名為 AddGoFlagSet ()的 pFlag 包提供的便利函式來實作的,
Example:
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 interfaces
Viper 提供了兩個 Go 介面來系結其他標志系統,如果你不使用標志的話,
FlagValue 表示一個標志,這是一個關于如何實作這個介面的非常簡單的例子:
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" }
一旦你的標志實作了這個介面,你可以簡單地告訴 Viper 系結它:
viper.BindFlagValue("my-flag-name", myFlag{})
FlagValueSet represents a group of flags. 這是一個關于如何實作這個介面的非常簡單的例子:
type myFlagSet struct {
flags []myFlag
}
func (f myFlagSet) VisitAll(fn func(FlagValue)) {
for _, flag := range flags {
fn(flag)
}
}
Once your flag set implements this interface, you can simply tell Viper to bind it:
fSet := myFlagSet{
flags: []myFlag{myFlag{}, myFlag{}},
}
viper.BindFlagValues("my-flags", fSet)
Remote Key/Value Store Support 遠程 Key/Value 存盤支持
要在 Viper 中啟用遠程支持,請對 Viper/remote 包執行匿名匯入:
import _ "github.com/spf13/viper/remote"
Viper 將讀取從 Key/Value 存盤(如 etcd 或 Consule)中的路徑檢索到的配置字串(如 JSON、 TOML、 YAML、 HCL 或 envfile),這些值優先于默認值,但是被從磁盤、標志或環境變數檢索到的配置值覆寫,
Viper 使用 crypt 從 K/V 存盤中檢索配置,這意味著您可以存盤加密的配置值,如果您有正確的 gpg 密鑰環,則可以自動解密它們,加密是可選的,
您可以將遠程配置與本地配置結合使用,也可以獨立于本地配置使用,
crypt 有一個命令列助手,您可以使用它在 K/V 存盤中放置配置,在 http://127.0.0.1:4001上,crypt 默認為 etcd,
$ go get github.com/bketelsen/crypt/bin/crypt
$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json
Confirm that your value was set:
$ crypt get -plaintext /config/hugo.json
有關如何設定加密值或如何使用 Consul 的示例,請參見 crypt 檔案,
Remote Key/Value Store Example - Unencrypted 遠程Key/Value存盤示例-未加密
etcd
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()
etcd3
viper.AddRemoteProvider("etcd3", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()
Consul
You need to set a key to Consul key/value storage with JSON value containing your desired config. For example, create a Consul key/value store key MY_CONSUL_KEY with value:
您需要使用包含所需配置的 JSON 值將一個鍵設定為 Consul key/value 存盤,例如,創建一個 Consul key/value store key MY _ CONSUL _ KEY,其值為:
{
"port": 8080,
"hostname": "myhostname.com"
}
viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")
viper.SetConfigType("json") // Need to explicitly set this to json
err := viper.ReadRemoteConfig()
fmt.Println(viper.Get("port")) // 8080
fmt.Println(viper.Get("hostname")) // myhostname.com
Firestore
viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")
viper.SetConfigType("json") // Config's format: "json", "toml", "yaml", "yml"
err := viper.ReadRemoteConfig()
當然,您也可以使用 SecureRemoteProvider
Remote Key/Value Store Example - Encrypted 遠程Key/Value存盤示例-加密
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()
Watching Changes in etcd - Unencrypted 監聽etcd 中的更改-未加密
// alternatively, you can create a new viper instance.
var runtime_viper = viper.New()
runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
// read from remote config the first time.
err := runtime_viper.ReadRemoteConfig()
// unmarshal config 反序列化
runtime_viper.Unmarshal(&runtime_conf)
// open a goroutine to watch remote changes forever
go func(){
for {
time.Sleep(time.Second * 5) // delay after each request
// currently, only tested with etcd support
err := runtime_viper.WatchRemoteConfig()
if err != nil {
log.Errorf("unable to read remote config: %v", err)
continue
}
// unmarshal new config into our runtime config struct. you can also use channel
// to implement a signal to notify the system of the changes
runtime_viper.Unmarshal(&runtime_conf)
}
}()
從 Viper 獲取值
在 Viper 中,有幾種根據值的型別獲取值的方法,現有以下職能和方法:
Get(key string) : interface{}GetBool(key string) : boolGetFloat64(key string) : float64GetInt(key string) : intGetIntSlice(key string) : []intGetString(key string) : stringGetStringMap(key string) : map[string]interface{}GetStringMapString(key string) : map[string]stringGetStringSlice(key string) : []stringGetTime(key string) : time.TimeGetDuration(key string) : time.DurationIsSet(key string) : boolAllSettings() : map[string]interface{}
需要注意的一點是,如果沒有找到,每個 Get 函式將回傳一個零值,為了檢查給定的鍵是否存在,提供了 IsSet ()方法,
Example:
viper.GetString("logfile") // case-insensitive Setting & Getting
if viper.GetBool("verbose") {
fmt.Println("verbose enabled")
}
Accessing nested keys 訪問嵌套鍵
訪問器方法還接受深度嵌套鍵的格式化路徑,例如,如果加載了以下 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")
這符合上面建立的優先級規則; 對路徑的搜索將通過剩余的配置注冊中心級聯,直到找到,
例如,給定這個組態檔,datastore.metric.host 和 datastore.metric.port 都已經定義(并且可能被覆寫),如果在默認情況下另外定義了 datastore.metric.protocol,Viper 也會找到它,
但是,如果 datastore.metrics 被覆寫(通過一個標志、一個環境變數、 Set ()方法,...)并且有一個立即的值,那么 datastore.metrics 的所有子鍵都將變得未定義,它們將被更高優先級的配置級別“隱藏”,
Viper 可以通過路徑中的數字訪問陣列索引,例如:
{
"host": {
"address": "localhost",
"ports": [
5799,
6029
]
},
"datastore": {
"metric": {
"host": "127.0.0.1",
"port": 3099
},
"warehouse": {
"host": "198.0.0.1",
"port": 2112
}
}
}
GetInt("host.ports.1") // returns 6029
最后,如果存在與分隔的鍵路徑匹配的鍵,則將回傳其值,
{
"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"
提取子樹
在開發可重用模塊時,提取配置的子集并將其傳遞給模塊通常很有用,通過這種方式,可以使用不同的配置多次實體化模塊,
例如,應用程式可能為不同目的使用多個不同的快取存盤區:
cache:
cache1:
max-items: 100
item-size: 64
cache2:
max-items: 200
item-size: 80
我們可以將快取名傳遞給一個模塊(例如,NewCache (“ cache1”) ,但是訪問配置鍵需要奇怪的連接,并且與全域配置的分離程度較低,
因此,與其這樣做,不如將 Viper 實體傳遞給代表配置子集的建構式:
cache1Config := viper.Sub("cache.cache1")
if cache1Config == nil { // Sub returns nil if the key cannot be found
panic("cache configuration not found")
}
cache1 := NewCache(cache1Config)
注意: 始終檢查 Sub 的回傳值,如果找不到鍵,它會回傳 nil,
在內部,NewCache 函式可以直接處理 max-item 和 item-size 鍵:
func NewCache(v *Viper) *Cache {
return &Cache{
MaxItems: v.GetInt("max-items"),
ItemSize: v.GetInt("item-size"),
}
}
產生的代碼很容易測驗,因為它與主配置結構解耦,并且出于同樣的原因更容易重用,
Unmarshaling 反序列化
您還可以選擇將所有值或特定值反序列化到結構體、map等,
有兩種方法可以做到這一點:
Unmarshal(rawVal interface{}) : errorUnmarshalKey(key string, rawVal interface{}) : error
Example:
type config struct {
Port int
Name string
PathMap string `mapstructure:"path_map"`
}
var C config
err := viper.Unmarshal(&C)
if err != nil {
t.Fatalf("unable to decode into struct, %v", err)
}
如果要反序列化鍵本身包含點(默認的鍵分隔符)的配置,必須更改分隔符:
v := viper.NewWithOptions(viper.KeyDelimiter("::"))
v.SetDefault("chart::values", map[string]interface{}{
"ingress": map[string]interface{}{
"annotations": map[string]interface{}{
"traefik.frontend.rule.type": "PathPrefix",
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
},
},
})
type config struct {
Chart struct{
Values map[string]interface{}
}
}
var C config
v.Unmarshal(&C)
Viper 還支持將資料決議為嵌入式結構:
/*
Example config:
module:
enabled: true
token: 89h3f98hbwf987h3f98wenf89ehf
*/
type config struct {
Module struct {
Enabled bool
moduleConfig `mapstructure:",squash"`
}
}
// moduleConfig could be in a module specific package
type moduleConfig struct {
Token string
}
var C config
err := viper.Unmarshal(&C)
if err != nil {
t.Fatalf("unable to decode into struct, %v", err)
}
Viper 使用底層 github.com/mitchellh/mapstructure 來決議值,默認情況下使用 mapstructure,
實操
package main
import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
)
type Config struct {
Host string `mapstructure:"host"`
Version string `mapstructure:"version"`
Port int `mapstructure:"port"`
MySQLConfig `mapstructure:"mysql"`
}
type MySQLConfig struct {
Host string `mapstructure:"host"`
DbName string `mapstructure:"dbname"`
Port int `mapstructure:"port"`
}
func main() {
// 設定默認值
viper.SetDefault("fileDir", "./")
// 讀取組態檔
viper.SetConfigFile("./config.yaml") // 指定組態檔路徑
viper.SetConfigName("config") // 組態檔名稱(無擴展名)
viper.SetConfigType("yaml") // 如果組態檔的名稱中沒有擴展名,則需要配置此項
viper.AddConfigPath("/etc/appname/") // 查找組態檔所在的路徑
viper.AddConfigPath("$HOME/.appname") // 多次呼叫以添加多個搜索路徑
viper.AddConfigPath(".") // 還可以在作業目錄中查找配置
err := viper.ReadInConfig() // 查找并讀取組態檔
if err != nil { // 處理讀取組態檔的錯誤
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
// 實時監控組態檔的變化 WatchConfig 開始監視組態檔的更改,
viper.WatchConfig()
// OnConfigChange設定組態檔更改時呼叫的事件處理程式,
// 當組態檔變化之后呼叫的一個回呼函式
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})
//r := gin.Default()
//r.GET("/version", func(c *gin.Context) {
// // GetString以字串的形式回傳與鍵相關的值,
// c.String(http.StatusOK, viper.GetString("version"))
//})
//r.Run()
var c Config
if err := viper.Unmarshal(&c); err != nil {
fmt.Printf("viper Unmarshal failed, err: %v\n", err)
return
}
fmt.Printf("viper unmarshal success. c: %#v\n", c)
}
config.yaml
host: "127.0.0.1"
port: 8080
version: "v0.0.2"
mysql:
host: "127.0.0.1"
port: 3306
dbname: "sql_demo"
運行
Code/go/viper_demo via ?? v1.20.3 via ?? base
? go run main.go
viper unmarshal success. c: main.Config{Host:"127.0.0.1", Version:"v0.0.2", Port:8080, MySQLConfig:main.MySQLConfig{Host:"127.0.0.1", DbName:"sql_demo", Port:3306}}
Code/go/viper_demo via ?? v1.20.3 via ?? base
?
Decoding custom formats
Viper 經常需要的一個特性是添加更多的值格式和解碼器,例如,決議字符(點、逗號、分號等)將字串分隔成片,
這在使用映射結構解碼鉤子的 Viper 中已經可用,
閱讀更多關于 this blog post.,
Marshalling to string 序列化成字串
您可能需要將 viper 中保存的所有設定編組成一個字串,而不是將它們寫入檔案,您可以對 AllSettings ()回傳的配置使用您喜歡的格式的編組器,
import (
yaml "gopkg.in/yaml.v2"
// ...
)
func yamlStringSettings() string {
c := viper.AllSettings()
bs, err := yaml.Marshal(c)
if err != nil {
log.Fatalf("unable to marshal config to YAML: %v", err)
}
return string(bs)
}
Viper or Vipers? 單個和多個 Viper 的選擇
Viper已經準備好開箱使用了,開始使用 Viper 不需要配置或初始化,由于大多數應用程式都希望使用單個中央存盤庫進行配置,因此 viper 包提供了這一功能,它類似于單例模式,
在上面的所有示例中,它們都演示了如何在單例樣式方法中使用 viper
多個 Viper 的使用
您還可以創建許多不同的vipers在您的應用程式中使用,每一個都有自己獨特的一組配置和值,每個都可以從不同的組態檔、鍵值存盤等讀取,Viper 包支持的所有功能都反映為 viper 上的方法,
Example:
x := viper.New()
y := viper.New()
x.SetDefault("ContentDir", "content")
y.SetDefault("ContentDir", "foobar")
//...
在處理多條vipers時,由用戶來跟蹤不同的vipers,
更多詳情請閱讀:https://github.com/spf13/viper/blob/master/README.md
本文來自博客園,作者:尋月隱君,轉載請注明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17489207.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/555494.html
標籤:其他
下一篇:返回列表
