配置解析神器:Viper 使用介紹


學習地址:https://github.com/spf13/viper

幾乎所有的后端服務,都需要一些配置項來配置我們的服務,一些小型的項目,配置不是很多,可以選擇只通過命令行參數來傳遞配置。但是大型項目配置很多,通過命令行參數傳遞就變得很麻煩,不好維護。標准的解決方案是將這些配置信息保存在配置文件中,由程序啟動時加載和解析。Go 生態中有很多包可以加載並解析配置文件,目前最受歡迎的是 Viper 包。

​ Viper 是 Go 應用程序現代化的、完整的解決方案,能夠處理不同格式的配置文件,讓我們在構建現代應用程序時,不必擔心配置文件格式。Viper 也能夠滿足我們對應用配置的各種需求。

​ Viper 可以從不同的位置讀取配置,不同位置的配置具有不同的優先級,高優先級的配置會覆蓋低優先級相同的配置,按優先級從高到低排列如下:

1、通過 viper.Set 函數顯示設置的配置

2、命令行參數

3、環境變量

4、配置文件

5、Key/Value 存儲

6、默認值

這里需要注意,Viper 配置鍵不區分大小寫

​ Viper 有很多功能,最重要的兩類功能是讀入配置和讀取配置,Viper 提供不同的方式來實現這兩類功能。接下來,我們就來詳細介紹下 Viper 如何讀入配置和讀取配置。

1、讀入配置

讀入配置,就是將配置讀入到 Viper 中,有如下讀入方式:

  • 設置默認的配置文件名。

  • 讀取配置文件。

  • 監聽和重新讀取配置文件。

  • 從 io.Reader 讀取配置。

  • 從環境變量讀取。

  • 從命令行標志讀取。

  • 從遠程 Key/Value 存儲讀取

1.1、設置默認值。

​ 一個好的配置系統應該支持默認值。Viper 支持對 key 設置默認值,當沒有通過配置文件、環境變量、遠程配置或命令行標志設置 key 時,設置默認值通常是很有用的,可以讓程序在沒有明確指定配置時也能夠正常運行。例如:

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

1.2、讀取配置文件。

​ Viper 可以讀取配置文件來解析配置,支持 JSON、TOML、YAML、YML、Properties、Props、Prop、HCL、Dotenv、Env 格式的配置文件。Viper 支持搜索多個路徑,並且默認不配置任何搜索路徑,將默認決策留給應用程序。

以下是如何使用 Viper 搜索和讀取配置文件的示例:

package main
import ( "fmt" "github.com/spf13/pflag" "github.com/spf13/viper")

var ( 
    cfg = pflag.StringP("config", "c", "", "Configuration file.") 
    help = pflag.BoolP("help", "h", false, "Show this help message.")
)

func main() { 
    pflag.Parse() 
    if *help { 
        pflag.Usage() 
        return 
    } 
    // 從配置文件中讀取配置 
    if *cfg != "" { 
        viper.SetConfigFile(*cfg) // 指定配置文件名 
        viper.SetConfigType("yaml") // 如果配置文件名中沒有文件擴展名,則需要指定配置文件的格式,告訴viper以何種格式解析文件 
    } else { 
        viper.AddConfigPath(".") // 把當前目錄加入到配置文件的搜索路徑中 
        viper.AddConfigPath("$HOME/config") // 配置文件搜索路徑,可以設置多個配置文件搜索路徑 
        viper.SetConfigName("config") // 配置文件名稱(沒有文件擴展名) 
    } 
    if err := viper.ReadInConfig(); err != nil { 
        // 讀取配置文件。如果指定了配置文件名,則使用指定的配置文件,否則在注冊的搜索路徑中搜索 
        panic(fmt.Errorf("Fatal error config file: %s \n", err)) 
    } 
    fmt.Printf("Used configuration file is: %s\n", viper.ConfigFileUsed())
}

Viper 支持設置多個配置文件搜索路徑,需要注意添加搜索路徑的順序,Viper 會根據添加的路徑順序搜索配置文件,如果找到則停止搜索。如果調用 SetConfigFile 直接指定了配置文件名,並且配置文件名沒有文件擴展名時,需要顯式指定配置文件的格式,以使 Viper 能夠正確解析配置文件。

​ 如果通過搜索的方式查找配置文件,則需要注意,SetConfigName 設置的配置文件名是不帶擴展名的,在搜索時 Viper 會在文件名之后追加文件擴展名,並嘗試搜索所有支持的擴展類型。

1.3、監聽和重新讀取配置文件。

​ Viper 支持在運行時讓應用程序實時讀取配置文件,也就是熱加載配置。可以通過 WatchConfig 函數熱加載配置。在調用 WatchConfig 函數之前,需要確保已經添加了配置文件的搜索路徑。另外,還可以為 Viper 提供一個回調函數,以便在每次發生更改時運行。

viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) { 
    // 配置文件發生變更之后會調用的回調函數 
    fmt.Println("Config file changed:", e.Name)
})

不建議在實際開發中使用熱加載功能,因為即使配置熱加載了,程序中的代碼也不一定會熱加載。例如:修改了服務監聽端口,但是服務沒有重啟,這時候服務還是監聽在老的端口上,會造成不一致。
1.4、設置配置值。

可以通過 viper.Set() 函數來顯式設置配置:

viper.Set("user.username", "colin")

1.5、使用環境變量。

Viper 還支持環境變量,通過如下 5 個函數來支持環境變量:

  • AutomaticEnv()

  • BindEnv(input …string)

  • errorSetEnvPrefix(in string)

  • SetEnvKeyReplacer(r *strings.Replacer)

  • AllowEmptyEnv(allowEmptyEnv bool)

​ 這里要注意:Viper 讀取環境變量是區分大小寫的。Viper 提供了一種機制來確保 Env 變量是唯一的。通過使用 SetEnvPrefix,可以告訴 Viper 在讀取環境變量時使用前綴。BindEnv 和 AutomaticEnv 都將使用此前綴。比如,我們設置了 viper.SetEnvPrefix(“VIPER”),當使用 viper.Get(“apiversion”) 時,實際讀取的環境變量是VIPER_APIVERSION。

​ BindEnv 需要一個或兩個參數。第一個參數是鍵名,第二個是環境變量的名稱,環境變量的名稱區分大小寫。如果未提供 Env 變量名,則 Viper 將假定 Env 變量名為:環境變量前綴_鍵名全大寫。例如:前綴為 VIPER,key 為 username,則 Env 變量名為VIPER_USERNAME。當顯示提供 Env 變量名(第二個參數)時,它不會自動添加前綴。例如,如果第二個參數是 ID,Viper 將查找環境變量 ID。

​ 在使用 Env 變量時,需要注意的一件重要事情是:每次訪問該值時都將讀取它。Viper 在調用 BindEnv 時不固定該值。

​ 還有一個魔法函數 SetEnvKeyReplacer,SetEnvKeyReplacer 允許你使用 strings.Replacer 對象來重寫 Env 鍵。如果你想在 Get() 調用中使用-或者.,但希望你的環境變量使用_分隔符,可以通過 SetEnvKeyReplacer 來實現。比如,我們設置了環境變量USER_SECRET_KEY=bVix2WBv0VPfrDrvlLWrhEdzjLpPCNYb,但我們想用viper.Get("user.secret-key"),那我們就調用函數:

viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))

​ 上面的代碼,在調用 viper.Get() 函數時,會用 _ 替換.和-。默認情況下,空環境變量被認為是未設置的,並將返回到下一個配置源。若要將空環境變量視為已設置,可以使用 AllowEmptyEnv 方法。使用環境變量示例如下:

// 使用環境變量
os.Setenv("VIPER_USER_SECRET_ID", "QLdywI2MrmDVjSSv6e95weNRvmteRjfKAuNV")
os.Setenv("VIPER_USER_SECRET_KEY", "bVix2WBv0VPfrDrvlLWrhEdzjLpPCNYb")
viper.AutomaticEnv() 
// 讀取環境變量
viper.SetEnvPrefix("VIPER") // 設置環境變量前綴:VIPER_,如果是viper,將自動轉變為大寫。
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) 
// 將viper.Get(key) key字符串中'.'和'-'替換為'_'
viper.BindEnv("user.secret-key")
viper.BindEnv("user.secret-id", "USER_SECRET_ID") // 綁定環境變量名到key

1.6、使用標志。
Viper 支持 Pflag 包,能夠綁定 key 到 Flag。與 BindEnv 類似,在調用綁定方法時,不會設置該值,但在訪問它時會設置。對於單個標志,可以調用 BindPFlag() 進行綁定:

viper.BindPFlag("token", pflag.Lookup("token")) // 綁定單個標志

還可以綁定一組現有的 pflags(pflag.FlagSet):

viper.BindPFlags(pflag.CommandLine) //綁定標志集

2、讀取配置

Viper 提供了如下方法來讀取配置:

  • Get(key string) interface{}

  • Get (key string)

  • AllSettings() map[string]interface{}

  • IsSet(key string) : bool

每一個 Get 方法在找不到值的時候都會返回零值。為了檢查給定的鍵是否存在,可以使用 IsSet() 方法。可以是 Viper 支持的類型,首字母大寫:Bool、Float64、Int、IntSlice、String、StringMap、StringMapString、StringSlice、Time、Duration。例如:GetInt()。

常見的讀取配置方法有以下幾種。

2.1、訪問嵌套的鍵。

例如,加載下面的 JSON 文件:

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

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

viper.GetString("datastore.metric.host") // (返回 "127.0.0.1")

如果 datastore.metric 被直接賦值覆蓋(被 Flag、環境變量、set() 方法等等),那么datastore.metric的所有子鍵都將變為未定義狀態,它們被高優先級配置級別覆蓋了。

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

{
    "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
        }
    }
}

通過 viper.GetString 獲取值:

viper.GetString("datastore.metric.host") // 返回 "0.0.0.0"

2.2、反序列化。

Viper 可以支持將所有或特定的值解析到結構體、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 在后台使用 github.com/mitchellh/mapstructure 來解析值,其默認情況下使用mapstructure tags。當我們需要將 Viper 讀取的配置反序列到我們定義的結構體變量中時,一定要使用 mapstructure tags。

2.3、序列化成字符串

有時候我們需要將 Viper 中保存的所有設置序列化到一個字符串中,而不是將它們寫入到一個文件中,示例如下:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM