主頁 > 後端開發 > Go 語言之 Viper 的使用

Go 語言之 Viper 的使用

2023-06-19 07:37:46 後端開發

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 可以為你做以下事情:

  1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, INI, envfile or Java properties formats.
  2. Provide a mechanism to set default values for your different configuration options.
  3. Provide a mechanism to set override values for options specified through command line flags.
  4. Provide an alias system to easily rename parameters without breaking existing code.
  5. 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...) : error
  • SetEnvPrefix(string)
  • SetEnvKeyReplacer(string...) *strings.Replacer
  • AllowEmptyEnv(bool)

當使用 ENV 變數時,重要的是要認識到 Viper 將 ENV 變數視為區分大小寫的,

Viper 提供了一種機制來嘗試確保 ENV 變數是唯一的,通過使用 SetEnvPrefix,可以告訴 Viper 在讀取環境變數時使用前綴,BindEnvAutomaticEnv 都將使用此前綴,

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) : 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
  • IsSet(key string) : bool
  • AllSettings() : 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{}) : error
  • UnmarshalKey(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

標籤:其他

上一篇:SpringBoot自動配置的原理

下一篇:返回列表

標籤雲
其他(161229) Python(38240) JavaScript(25505) Java(18246) C(15237) 區塊鏈(8271) C#(7972) AI(7469) 爪哇(7425) MySQL(7256) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5875) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4603) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2436) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) HtmlCss(1968) 功能(1967) Web開發(1951) C++(1941) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Go 語言之 Viper 的使用

    # Go 語言之 Viper 的使用 ## Viper 介紹 [Viper](https://github.com/spf13/viper): ### 安裝 ```bash go get github.com/spf13/viper ``` ### Viper 是什么? Viper 是一個針對 Go ......

    uj5u.com 2023-06-19 07:37:46 more
  • SpringBoot自動配置的原理

    以WebMvcAutoConfiguration自動配置的原理為例,SpringBoot內部對大量的第三方庫或Spring內部庫進行了默認配置,這些配置是否生效,取決于我們是否引入了對應庫所需的依賴,如果有那么默認配置就會生效。如果引入springboot-starter-web那么對應的web配置 ......

    uj5u.com 2023-06-19 07:37:28 more
  • 【numpy基礎】--陣列簡介

    `NumPy`(Numerical Python)是一個`Python`庫,主要用于高效地處理多維陣列和矩陣計算。它是科學計算領域中使用最廣泛的一個庫。 在`NumPy`中,**陣列**是最核心的概念,用于存盤和操作資料。 `NumPy`陣列是一種多維陣列物件,可以存盤相同型別的元素,它支持高效的數 ......

    uj5u.com 2023-06-19 07:37:17 more
  • 【Java學習】 Spring的基礎理解 IOC、AOP以及事務

    一、簡介 官網: https://spring.io/projects/spring-framework#overview 官方下載工具: https://repo.spring.io/release/org/springframework/spring/ github下載: https://git ......

    uj5u.com 2023-06-19 07:37:09 more
  • 尚醫通day13【預約掛號】(內附原始碼)

    # 頁面預覽 ## 預約掛號 - 根據預約周期,展示可預約日期,根據有號、無號、約滿等狀態展示不同顏色,以示區分 - 可預約最后一個日期為即將放號日期 - 選擇一個日期展示當天可預約串列 ![image-20230227202834422](https://s2.loli.net/2023/06/1 ......

    uj5u.com 2023-06-19 07:36:36 more
  • 并行計算——緒論

    # 一、緒論 ## 1.1 基本概念 1. 加速比:表示加速效果。單個處理器運行花費時間 / P個處理器運行花費時間;$S=\frac{T(1)}{T(p)}$ 2. 效率:$E = \frac{S}{p} = \frac{T(1)}{T(p)\times p}$ 3. 開銷:$C=T(p)\tim ......

    uj5u.com 2023-06-19 07:31:09 more
  • 集合體系結構

    集合體系結構 List系列集合:添加的元素有序,可重復,有索引 Collection:是單列集合的祖宗介面,它的功能是全部單列集合都可以繼承使用的 set系列集合:添加的元素無序,不重復,無索引 方法名說明 public boolean add(E e) 把給定的物件添加到當前集合中 public ......

    uj5u.com 2023-06-18 08:13:56 more
  • 集合體系結構

    集合體系結構 List系列集合:添加的元素有序,可重復,有索引 Collection:是單列集合的祖宗介面,它的功能是全部單列集合都可以繼承使用的 set系列集合:添加的元素無序,不重復,無索引 方法名說明 public boolean add(E e) 把給定的物件添加到當前集合中 public ......

    uj5u.com 2023-06-18 08:12:55 more
  • rust 使用第三方庫構建mini命令列工具

    這是上一篇 [rust 學習 - 構建 mini 命令列工具](https://www.cnblogs.com/dreamHot/p/17467837.html)的續作,擴展增加一些 crate 庫。這些基礎庫在以后的編程作業中會常用到,他們作為基架存在于專案中,解決專案中的某個問題。 專案示例還是 ......

    uj5u.com 2023-06-18 07:49:48 more
  • [ARM 匯編]進階篇—例外處理與中斷—2.4.2 ARM處理器的例外向量

    #### 例外向量表簡介 在ARM架構中,例外向量表是一組固定位置的記憶體地址,它們包含了處理器在遇到例外時需要跳轉到的處理程式的入口地址。每個例外型別都有一個對應的向量地址。當例外發生時,處理器會自動跳轉到對應的向量地址,并開始執行例外處理程式。 #### 例外向量表的位置 ARM處理器的例外向量表 ......

    uj5u.com 2023-06-18 07:49:41 more