1. 程式人生 > 實用技巧 >Go語言配置管理神器——Viper中文教程

Go語言配置管理神器——Viper中文教程

轉自

Go語言配置管理神器——Viper中文教程

Viper是適用於Go應用程式的完整配置解決方案。它被設計用於在應用程式中工作,並且可以處理所有型別的配置需求和格式。

Viper

Viper是適用於Go應用程式的完整配置解決方案。它被設計用於在應用程式中工作,並且可以處理所有型別的配置需求和格式。

鑑於viper庫本身的README已經寫得十分詳細,這裡就將其翻譯成中文,並在最後附上兩個專案中使用viper的示例程式碼以供參考。

安裝

go get github.com/spf13/viper

  

什麼是Viper?

Viper是適用於Go應用程式(包括Twelve-Factor App

)的完整配置解決方案。它被設計用於在應用程式中工作,並且可以處理所有型別的配置需求和格式。它支援以下特性:

  • 設定預設值
  • JSONTOMLYAMLHCLenvfileJava properties格式的配置檔案讀取配置資訊
  • 實時監控和重新讀取配置檔案(可選)
  • 從環境變數中讀取
  • 從遠端配置系統(etcd或Consul)讀取並監控配置變化
  • 從命令列引數讀取配置
  • 從buffer讀取配置
  • 顯式配置值

為什麼選擇Viper?

在構建現代應用程式時,你無需擔心配置檔案格式;你想要專注於構建出色的軟體。Viper的出現就是為了在這方面幫助你的。

Viper能夠為你執行下列操作:

  1. 查詢、載入和反序列化JSON
    TOMLYAMLHCLINIenvfileJava properties格式的配置檔案。
  2. 提供一種機制為你的不同配置選項設定預設值。
  3. 提供一種機制來通過命令列引數覆蓋指定選項的值。
  4. 提供別名系統,以便在不破壞現有程式碼的情況下輕鬆重新命名引數。
  5. 當用戶提供了與預設值相同的命令列或配置檔案時,可以很容易地分辨出它們之間的區別。

Viper會按照下面的優先順序。每個專案的優先順序都高於它下面的專案:

  • 顯示呼叫Set設定值
  • 命令列引數(flag)
  • 環境變數
  • 配置檔案
  • key/value儲存
  • 預設值

重要: 目前Viper配置的鍵(Key)是大小寫不敏感的。目前正在討論是否將這一選項設為可選。

把值存入Viper

建立預設值

一個好的配置系統應該支援預設值。鍵不需要預設值,但如果沒有通過配置檔案、環境變數、遠端配置或命令列標誌(flag)設定鍵,則預設值非常有用。

例如:

viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})

  

讀取配置檔案

Viper需要最少知道在哪裡查詢配置檔案的配置。Viper支援JSONTOMLYAMLHCLenvfileJava properties格式的配置檔案。Viper可以搜尋多個路徑,但目前單個Viper例項只支援單個配置檔案。Viper不預設任何配置搜尋路徑,將預設決策留給應用程式。

下面是一個如何使用Viper搜尋和讀取配置檔案的示例。不需要任何特定的路徑,但是至少應該提供一個配置檔案預期出現的路徑。

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))
}

  

在載入配置檔案出錯時,你可以像下面這樣處理找不到配置檔案的特定情況:

if err := viper.ReadInConfig(); err != nil {
    if _, ok := err.(viper.ConfigFileNotFoundError); ok {
        // 配置檔案未找到錯誤;如果需要可以忽略
    } else {
        // 配置檔案被找到,但產生了另外的錯誤
    }
}

  

// 配置檔案找到併成功解析 

注意[自1.6起]: 你也可以有不帶副檔名的檔案,並以程式設計方式指定其格式。對於位於使用者$HOME目錄中的配置檔案沒有任何副檔名,如.bashrc

這裡補充兩個問題供讀者解答並自行驗證

當你使用如下方式讀取配置時,viper會從./conf目錄下查詢任何以config為檔名的配置檔案,如果同時存在./conf/config.json./conf/config.yaml兩個配置檔案的話,viper會從哪個配置檔案載入配置呢?

viper.SetConfigName("config")
viper.AddConfigPath("./conf")

  

在上面兩個語句下搭配使用viper.SetConfigType("yaml")指定配置檔案型別可以實現預期的效果嗎?

寫入配置檔案

從配置檔案中讀取配置檔案是有用的,但是有時你想要儲存在執行時所做的所有修改。為此,可以使用下面一組命令,每個命令都有自己的用途:

  • WriteConfig - 將當前的viper配置寫入預定義的路徑並覆蓋(如果存在的話)。如果沒有預定義的路徑,則報錯。
  • SafeWriteConfig - 將當前的viper配置寫入預定義的路徑。如果沒有預定義的路徑,則報錯。如果存在,將不會覆蓋當前的配置檔案。
  • WriteConfigAs - 將當前的viper配置寫入給定的檔案路徑。將覆蓋給定的檔案(如果它存在的話)。
  • SafeWriteConfigAs - 將當前的viper配置寫入給定的檔案路徑。不會覆蓋給定的檔案(如果它存在的話)。

根據經驗,標記為safe的所有方法都不會覆蓋任何檔案,而是直接建立(如果不存在),而預設行為是建立或截斷。

一個小示例:

viper.WriteConfig() // 將當前配置寫入“viper.AddConfigPath()”和“viper.SetConfigName”設定的預定義路徑
viper.SafeWriteConfig()
viper.WriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.config") // 因為該配置檔案寫入過,所以會報錯
viper.SafeWriteConfigAs("/path/to/my/.other_config")

  

監控並重新讀取配置檔案

Viper支援在執行時實時讀取配置檔案的功能。

需要重新啟動伺服器以使配置生效的日子已經一去不復返了,viper驅動的應用程式可以在執行時讀取配置檔案的更新,而不會錯過任何訊息。

只需告訴viper例項watchConfig。可選地,你可以為Viper提供一個回撥函式,以便在每次發生更改時執行。

確保在呼叫WatchConfig()之前添加了所有的配置路徑。

viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
  // 配置檔案發生變更之後會呼叫的回撥函式
	fmt.Println("Config file changed:", e.Name)
})

  

從io.Reader讀取配置

Viper預先定義了許多配置源,如檔案、環境變數、標誌和遠端K/V儲存,但你不受其約束。你還可以實現自己所需的配置源並將其提供給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") // 這裡會得到 "steve"

  

覆蓋設定

這些可能來自命令列標誌,也可能來自你自己的應用程式邏輯。

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

  

註冊和使用別名

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

viper.RegisterAlias("loud", "Verbose")  // 註冊別名(此處loud和Verbose建立了別名)

viper.Set("verbose", true) // 結果與下一行相同
viper.Set("loud", true)   // 結果與前一行相同

viper.GetBool("loud") // true
viper.GetBool("verbose") // true

  

使用環境變數

Viper完全支援環境變數。這使Twelve-Factor App開箱即用。有五種方法可以幫助與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變數與以下格式匹配:字首+ “_” +鍵名全部大寫。當你顯式提供ENV變數名(第二個引數)時,它 不會 自動新增字首。例如,如果第二個引數是“id”,Viper將查詢環境變數“ID”。

在使用ENV變數時,需要注意的一件重要事情是,每次訪問該值時都將讀取它。Viper在呼叫BindEnv時不固定該值。

AutomaticEnv是一個強大的助手,尤其是與SetEnvPrefix結合使用時。呼叫時,Viper會在發出viper.Get請求時隨時檢查環境變數。它將應用以下規則。它將檢查環境變數的名稱是否與鍵匹配(如果設定了EnvPrefix)。

SetEnvKeyReplacer允許你使用strings.Replacer物件在一定程度上重寫 Env 鍵。如果你希望在Get()呼叫中使用-或者其他什麼符號,但是環境變數裡使用_分隔符,那麼這個功能是非常有用的。可以在viper_test.go中找到它的使用示例。

或者,你可以使用帶有NewWithOptions工廠函式的EnvKeyReplacer。與SetEnvKeyReplacer不同,它接受StringReplacer介面,允許你編寫自定義字串替換邏輯。

預設情況下,空環境變數被認為是未設定的,並將返回到下一個配置源。若要將空環境變數視為已設定,請使用AllowEmptyEnv方法。

Env 示例:

SetEnvPrefix("spf") // 將自動轉為大寫
BindEnv("id")

os.Setenv("SPF_ID", "13") // 通常是在應用程式之外完成的

id := Get("id") // 13

  

使用Flags

Viper 具有繫結到標誌的能力。具體來說,Viper支援Cobra庫中使用的Pflag

BindEnv類似,該值不是在呼叫繫結方法時設定的,而是在訪問該方法時設定的。這意味著你可以根據需要儘早進行繫結,即使在init()函式中也是如此。

對於單個標誌,BindPFlag()方法提供此功能。

例如:

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

  

你還可以繫結一組現有的pflags (pflag.FlagSet):

舉個例子:

pflag.Int("flagname", 1234, "help message for flagname")

pflag.Parse()
viper.BindPFlags(pflag.CommandLine)

i := viper.GetInt("flagname") // 從viper而不是從pflag檢索值

  

在 Viper 中使用 pflag 並不阻礙其他包中使用標準庫中的 flag 包。pflag 包可以通過匯入這些 flags 來處理flag包定義的flags。這是通過呼叫pflag包提供的便利函式AddGoFlagSet()來實現的。

例如:

package main

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

func main() {

	// 使用標準庫 "flag" 包
	flag.Int("flagname", 1234, "help message for flagname")

	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
	pflag.Parse()
	viper.BindPFlags(pflag.CommandLine)

	i := viper.GetInt("flagname") // 從 viper 檢索值

	...
}

  

flag介面

如果你不使用Pflag,Viper 提供了兩個Go介面來繫結其他 flag 系統。

FlagValue表示單個flag。這是一個關於如何實現這個介面的非常簡單的例子:

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 實現了這個介面,你可以很方便地告訴Viper繫結它:

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

  

FlagValueSet代表一組 flags 。這是一個關於如何實現這個介面的非常簡單的例子:

type myFlagSet struct {
	flags []myFlag
}

func (f myFlagSet) VisitAll(fn func(FlagValue)) {
	for _, flag := range flags {
		fn(flag)
	}
}

  

一旦你的flag set實現了這個介面,你就可以很方便地告訴Viper繫結它:

fSet := myFlagSet{
	flags: []myFlag{myFlag{}, myFlag{}},
}
viper.BindFlagValues("my-flags", fSet)

  

遠端Key/Value儲存支援

在Viper中啟用遠端支援,需要在程式碼中匿名匯入viper/remote這個包。

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

Viper將讀取從Key/Value儲存(例如etcd或Consul)中的路徑檢索到的配置字串(如JSONTOMLYAMLHCLenvfileJava properties格式)。這些值的優先順序高於預設值,但是會被從磁碟、flag或環境變數檢索到的配置值覆蓋。(譯註:也就是說Viper載入配置值的優先順序為:磁碟上的配置檔案>命令列標誌位>環境變數>遠端Key/Value儲存>預設值。)

Viper使用crypt從K/V儲存中檢索配置,這意味著如果你有正確的gpg密匙,你可以將配置值加密儲存並自動解密。加密是可選的。

你可以將遠端配置與本地配置結合使用,也可以獨立使用。

crypt有一個命令列助手,你可以使用它將配置放入K/V儲存中。crypt預設使用在http://127.0.0.1:4001的etcd。

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

  

確認值已經設定:

$ crypt get -plaintext /config/hugo.json

  

有關如何設定加密值或如何使用Consul的示例,請參見crypt文件。

遠端Key/Value儲存示例-未加密

etcd

viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // 因為在位元組流中沒有副檔名,所以這裡需要設定下型別。支援的副檔名有 "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()

  

Consul

你需要 Consul Key/Value儲存中設定一個Key儲存包含所需配置的JSON值。例如,建立一個keyMY_CONSUL_KEY將下面的值存入Consul key/value 儲存:

{
    "port": 8080,
    "hostname": "liwenzhou.com"
}
viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")
viper.SetConfigType("json") // 需要顯示設定成json
err := viper.ReadRemoteConfig()

fmt.Println(viper.Get("port")) // 8080
fmt.Println(viper.Get("hostname")) // liwenzhou.com

  

Firestore

viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")
viper.SetConfigType("json") // 配置的格式: "json", "toml", "yaml", "yml"
err := viper.ReadRemoteConfig()

  

當然,你也可以使用SecureRemoteProvider

遠端Key/Value儲存示例-加密

viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
viper.SetConfigType("json") // 因為在位元組流中沒有副檔名,所以這裡需要設定下型別。支援的副檔名有 "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()

  

監控etcd中的更改-未加密

// 或者你可以建立一個新的viper例項
var runtime_viper = viper.New()

runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
runtime_viper.SetConfigType("yaml") // 因為在位元組流中沒有副檔名,所以這裡需要設定下型別。支援的副檔名有 "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"

// 第一次從遠端讀取配置
err := runtime_viper.ReadRemoteConfig()

// 反序列化
runtime_viper.Unmarshal(&runtime_conf)

// 開啟一個單獨的goroutine一直監控遠端的變更
go func(){
	for {
	    time.Sleep(time.Second * 5) // 每次請求後延遲一下

	    // 目前只測試了etcd支援
	    err := runtime_viper.WatchRemoteConfig()
	    if err != nil {
	        log.Errorf("unable to read remote config: %v", err)
	        continue
	    }

	    // 將新配置反序列化到我們執行時的配置結構體中。你還可以藉助channel實現一個通知系統更改的訊號
	    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()方法。

例如:

viper.GetString("logfile") // 不區分大小寫的設定和獲取
if 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") // (返回 "127.0.0.1")

  

這遵守上面建立的優先規則;搜尋路徑將遍歷其餘配置登錄檔,直到找到為止。(譯註:因為Viper支援從多種配置來源,例如磁碟上的配置檔案>命令列標誌位>環境變數>遠端Key/Value儲存>預設值,我們在查詢一個配置的時候如果在當前配置源中沒找到,就會繼續從後續的配置源查詢,直到找到為止。)

例如,在給定此配置檔案的情況下,datastore.metric.hostdatastore.metric.port均已定義(並且可以被覆蓋)。如果另外在預設值中定義了datastore.metric.protocol,Viper也會找到它。

然而,如果datastore.metric被直接賦值覆蓋(被flag,環境變數,set()方法等等…),那麼datastore.metric的所有子鍵都將變為未定義狀態,它們被高優先順序配置級別“遮蔽”(shadowed)了。

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

{
    "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") // 返回 "0.0.0.0"

  

提取子樹

從Viper中提取子樹。

例如,viper例項現在代表了以下配置:

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

  

執行後:

subv := viper.Sub("app.cache1")

  

subv現在就代表:

max-items: 100
item-size: 64

  

假設我們現在有這麼一個函式:

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

  

它基於subv格式的配置資訊建立快取。現在,可以輕鬆地分別建立這兩個快取,如下所示:

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

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

  

反序列化

你還可以選擇將所有或特定的值解析到結構體、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 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來解析值,其預設情況下使用mapstructuretag。

注意 當我們需要將viper讀取的配置反序列到我們定義的結構體變數中時,一定要使用mapstructuretag哦!

序列化成字串

你可能需要將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例項?

Viper是開箱即用的。你不需要配置或初始化即可開始使用Viper。由於大多數應用程式都希望使用單箇中央儲存庫管理它們的配置資訊,所以viper包提供了這個功能。它類似於單例模式。

在上面的所有示例中,它們都以其單例風格的方法演示瞭如何使用viper。

使用多個viper例項

你還可以在應用程式中建立許多不同的viper例項。每個都有自己獨特的一組配置和值。每個人都可以從不同的配置檔案,key value儲存區等讀取資料。每個都可以從不同的配置檔案、鍵值儲存等中讀取。viper包支援的所有功能都被映象為viper例項的方法。

例如:

x := viper.New()
y := viper.New()

x.SetDefault("ContentDir", "content")
y.SetDefault("ContentDir", "foobar")

//...

  

當使用多個viper例項時,由使用者來管理不同的viper例項。

使用Viper示例

假設我們的專案現在有一個./conf/config.yaml配置檔案,內容如下:

port: 8123
version: "v1.2.3"

  

接下來通過示例程式碼演示兩種在專案中使用viper管理專案配置資訊的方式。

直接使用viper管理配置

這裡用一個demo演示如何在gin框架搭建的web專案中使用viper,使用viper載入配置檔案中的資訊,並在程式碼中直接使用viper.GetXXX()方法獲取對應的配置值。

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
)

func main() {
	viper.SetConfigFile("config.yaml") // 指定配置檔案
	viper.AddConfigPath("./conf/")     // 指定查詢配置檔案的路徑
	err := viper.ReadInConfig()        // 讀取配置資訊
	if err != nil {                    // 讀取配置資訊失敗
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
	}

	// 監控配置檔案變化
	viper.WatchConfig()

	r := gin.Default()
	// 訪問/version的返回值會隨配置檔案的變化而變化
	r.GET("/version", func(c *gin.Context) {
		c.String(http.StatusOK, viper.GetString("version"))
	})

	if err := r.Run(
		fmt.Sprintf(":%d", viper.GetInt("port"))); err != nil {
		panic(err)
	}
}

  

使用結構體變數儲存配置資訊

除了上面的用法外,我們還可以在專案中定義與配置檔案對應的結構體,viper載入完配置資訊後使用結構體變數儲存配置資訊。

package main

import (
	"fmt"
	"net/http"

	"github.com/fsnotify/fsnotify"

	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
)

type Config struct {
	Port    int    `mapstructure:"port"`
	Version string `mapstructure:"version"`
}

var Conf = new(Config)

func main() {
	viper.SetConfigFile("./conf/config.yaml") // 指定配置檔案路徑
	err := viper.ReadInConfig()               // 讀取配置資訊
	if err != nil {                           // 讀取配置資訊失敗
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
	}
	// 將讀取的配置資訊儲存至全域性變數Conf
	if err := viper.Unmarshal(Conf); err != nil {
		panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err))
	}
	// 監控配置檔案變化
	viper.WatchConfig()
	// 注意!!!配置檔案發生變化後要同步到全域性變數Conf
	viper.OnConfigChange(func(in fsnotify.Event) {
		fmt.Println("夭壽啦~配置檔案被人修改啦...")
		if err := viper.Unmarshal(Conf); err != nil {
			panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err))
		}
	})

	r := gin.Default()
	// 訪問/version的返回值會隨配置檔案的變化而變化
	r.GET("/version", func(c *gin.Context) {
		c.String(http.StatusOK, Conf.Version)
	})

	if err := r.Run(fmt.Sprintf(":%d", Conf.Port)); err != nil {
		panic(err)
	}
}

  

參考連結

https://github.com/spf13/viper/blob/master/README.md