簡介

上一篇文章介紹 cobra 的時候提到了 viper,今天我們就來介紹一下這個庫。viper 是一個配置解決方案,擁有豐富的特性:

支援 JSON/TOML/YAML/HCL/envfile/Java properties 等多種格式的配置檔案;

可以設定監聽配置檔案的修改,修改時自動載入新的配置;

從環境變數、命令列選項和

io。Reader

中讀取配置;

從遠端配置系統中讀取和監聽修改,如 etcd/Consul;

程式碼邏輯中顯示設定鍵值。

快速使用

安裝:

$ go get github。com/spf13/viper

使用:

package main

import (

“fmt”

“log”

“github。com/spf13/viper”

func main() {

viper。SetConfigName(“config”)

viper。SetConfigType(“toml”)

viper。AddConfigPath(“。”)

viper。SetDefault(“redis。port”, 6381)

err := viper。ReadInConfig()

if err != nil {

log。Fatal(“read config failed: %v”, err)

}

fmt。Println(viper。Get(“app_name”))

fmt。Println(viper。Get(“log_level”))

fmt。Println(“mysql ip: ”, viper。Get(“mysql。ip”))

fmt。Println(“mysql port: ”, viper。Get(“mysql。port”))

fmt。Println(“mysql user: ”, viper。Get(“mysql。user”))

fmt。Println(“mysql password: ”, viper。Get(“mysql。password”))

fmt。Println(“mysql database: ”, viper。Get(“mysql。database”))

fmt。Println(“redis ip: ”, viper。Get(“redis。ip”))

fmt。Println(“redis port: ”, viper。Get(“redis。port”))

}

我們使用之前Go 每日一庫之 go-ini一文中使用的配置,不過改為 toml 格式。toml 的語法很簡單,快速入門請看learn X in Y minutes。

app_name = “awesome web”

# possible values: DEBUG, INFO, WARNING, ERROR, FATAL

log_level = “DEBUG”

[mysql]

ip = “127。0。0。1”

port = 3306

user = “dj”

password = 123456

database = “awesome”

[redis]

ip = “127。0。0。1”

port = 7381

viper 的使用非常簡單,它需要很少的設定。設定檔名(

SetConfigName

)、配置型別(

SetConfigType

)和搜尋路徑(

AddConfigPath

),然後呼叫

ReadInConfig

。viper會自動根據型別來讀取配置。使用時呼叫

viper。Get

方法獲取鍵值。

編譯、執行程式:

awesome web

DEBUG

mysql ip: 127。0。0。1

mysql port: 3306

mysql user: dj

mysql password: 123456

mysql database: awesome

redis ip: 127。0。0。1

redis port: 7381

有幾點需要注意:

設定檔名時不要帶字尾;

搜尋路徑可以設定多個,viper 會根據設定順序依次查詢;

viper 獲取值時使用

section。key

的形式,即傳入巢狀的鍵名;

預設值可以呼叫

viper。SetDefault

設定。

讀取鍵

viper 提供了多種形式的讀取方法。在上面的例子中,我們看到了

Get

方法的用法。

Get

方法返回一個

interface{}

的值,使用有所不便。

GetType

系列方法可以返回指定型別的值。其中,Type 可以為

Bool/Float64/Int/String/Time/Duration/IntSlice/StringSlice

。但是請注意,

如果指定的鍵不存在或型別不正確,GetType方法返回對應型別的零值

如果要判斷某個鍵是否存在,使用

IsSet

方法。另外,

GetStringMap

GetStringMapString

直接以 map 返回某個鍵下面所有的鍵值對,前者返回

map[string]interface{}

,後者返回

map[string]string

AllSettings

map[string]interface{}

返回所有設定。

// 省略包名和 import 部分

func main() {

viper。SetConfigName(“config”)

viper。SetConfigType(“toml”)

viper。AddConfigPath(“。”)

err := viper。ReadInConfig()

if err != nil {

log。Fatal(“read config failed: %v”, err)

}

fmt。Println(“protocols: ”, viper。GetStringSlice(“server。protocols”))

fmt。Println(“ports: ”, viper。GetIntSlice(“server。ports”))

fmt。Println(“timeout: ”, viper。GetDuration(“server。timeout”))

fmt。Println(“mysql ip: ”, viper。GetString(“mysql。ip”))

fmt。Println(“mysql port: ”, viper。GetInt(“mysql。port”))

if viper。IsSet(“redis。port”) {

fmt。Println(“redis。port is set”)

} else {

fmt。Println(“redis。port is not set”)

}

fmt。Println(“mysql settings: ”, viper。GetStringMap(“mysql”))

fmt。Println(“redis settings: ”, viper。GetStringMap(“redis”))

fmt。Println(“all settings: ”, viper。AllSettings())

}

我們在配置檔案 config。toml 中新增

protocols

ports

配置:

[server]

protocols = [“http”, “https”, “port”]

ports = [10000, 10001, 10002]

timeout = 3s

編譯、執行程式,輸出:

protocols: [http https port]

ports: [10000 10001 10002]

timeout: 3s

mysql ip: 127。0。0。1

mysql port: 3306

redis。port is set

mysql settings: map[database:awesome ip:127。0。0。1 password:123456 port:3306 user:dj]

redis settings: map[ip:127。0。0。1 port:7381]

all settings: map[app_name:awesome web log_level:DEBUG mysql:map[database:awesome ip:127。0。0。1 password:123456 port:3306 user:dj] redis:map[ip:127。0。0。1 port:7381] server:map[ports:[10000 10001 10002] protocols:[http https port]]]

如果將配置中的

redis。port

註釋掉,將輸出

redis。port is not set

上面的示例中還演示瞭如何使用

time。Duration

型別,只要是

time。ParseDuration

接受的格式都可以,例如

3s

2min

1min30s

等。

設定鍵值

viper 支援在多個地方設定,使用下面的順序依次讀取:

呼叫

Set

顯示設定的;

命令列選項;

環境變數;

配置檔案;

預設值。

viper。Set

如果某個鍵透過

viper。Set

設定了值,那麼這個值的優先順序最高。

viper。Set(“redis。port”, 5381)

如果將上面這行程式碼放到程式中,執行程式,輸出的

redis。port

將是 5381。

命令列選項

如果一個鍵沒有透過

viper。Set

顯示設定值,那麼獲取時將嘗試從命令列選項中讀取。如果有,優先使用。viper 使用 pflag 庫來解析選項。我們首先在

init

方法中定義選項,並且呼叫

viper。BindPFlags

繫結選項到配置中:

func init() {

pflag。Int(“redis。port”, 8381, “Redis port to connect”)

// 繫結命令列

viper。BindPFlags(pflag。CommandLine)

}

然後,在

main

方法開頭處呼叫

pflag。Parse

解析選項。

編譯、執行程式:

$ 。/main。exe ——redis。port 9381

awesome web

DEBUG

mysql ip: 127。0。0。1

mysql port: 3306

mysql user: dj

mysql password: 123456

mysql database: awesome

redis ip: 127。0。0。1

redis port: 9381

如何不傳入選項:

$ 。/main。exe

awesome web

DEBUG

mysql ip: 127。0。0。1

mysql port: 3306

mysql user: dj

mysql password: 123456

mysql database: awesome

redis ip: 127。0。0。1

redis port: 7381

注意,這裡並不會使用選項

redis。port

的預設值。

但是,如果透過下面的方法都無法獲得鍵值,那麼返回選項預設值(如果有)。試試註釋掉配置檔案中

redis。port

看看效果。

環境變數

如果前面都沒有獲取到鍵值,將嘗試從環境變數中讀取。我們既可以一個個繫結,也可以自動全部繫結。

init

方法中呼叫

AutomaticEnv

方法繫結全部環境變數:

func init() {

// 繫結環境變數

viper。AutomaticEnv()

}

為了驗證是否繫結成功,我們在

main

方法中將環境變數 GOPATH 打印出來:

func main() {

// 省略部分程式碼

fmt。Println(“GOPATH: ”, viper。Get(“GOPATH”))

}

透過 系統 -> 高階設定 -> 新建 建立一個名為

redis。port

的環境變數,值為 10381。執行程式,輸出的

redis。port

值為 10381,並且輸出中有 GOPATH 資訊。

也可以單獨繫結環境變數:

func init() {

// 繫結環境變數

viper。BindEnv(“redis。port”)

viper。BindEnv(“go。path”, “GOPATH”)

}

func main() {

// 省略部分程式碼

fmt。Println(“go path: ”, viper。Get(“go。path”))

}

呼叫

BindEnv

方法,如果只傳入一個引數,則這個引數既表示鍵名,又表示環境變數名。如果傳入兩個引數,則第一個引數表示鍵名,第二個引數表示環境變數名。

還可以透過

viper。SetEnvPrefix

方法設定環境變數字首,這樣一來,透過

AutomaticEnv

和一個引數的

BindEnv

繫結的環境變數,在使用

Get

的時候,viper 會自動加上這個字首再從環境變數中查詢。

如果對應的環境變數不存在,viper 會自動將鍵名全部轉為大寫再查詢一次。所以,使用鍵名

gopath

也能讀取環境變數

GOPATH

的值。

配置檔案

如果經過前面的途徑都沒能找到該鍵,viper 接下來會嘗試從配置檔案中查詢。為了避免環境變數的影響,需要刪除

redis。port

這個環境變數。

看快速使用中的示例。

預設值

在上面的快速使用一節,我們已經看到了如何設定預設值,這裡就不贅述了。

讀取配置

io。Reader

中讀取

viper 支援從

io。Reader

中讀取配置。這種形式很靈活,來源可以是檔案,也可以是程式中生成的字串,甚至可以從網路連線中讀取的位元組流。

package main

import (

“bytes”

“fmt”

“log”

“github。com/spf13/viper”

func main() {

viper。SetConfigType(“toml”)

tomlConfig := []byte(`

app_name = “awesome web”

# possible values: DEBUG, INFO, WARNING, ERROR, FATAL

log_level = “DEBUG”

[mysql]

ip = “127。0。0。1”

port = 3306

user = “dj”

password = 123456

database = “awesome”

[redis]

ip = “127。0。0。1”

port = 7381

`)

err := viper。ReadConfig(bytes。NewBuffer(tomlConfig))

if err != nil {

log。Fatal(“read config failed: %v”, err)

}

fmt。Println(“redis port: ”, viper。GetInt(“redis。port”))

}

Unmarshal

viper 支援將配置

Unmarshal

到一個結構體中,為結構體中的對應欄位賦值。

package main

import (

“fmt”

“log”

“github。com/spf13/viper”

type Config struct {

AppName string

LogLevel string

MySQL MySQLConfig

Redis RedisConfig

}

type MySQLConfig struct {

IP string

Port int

User string

Password string

Database string

}

type RedisConfig struct {

IP string

Port int

}

func main() {

viper。SetConfigName(“config”)

viper。SetConfigType(“toml”)

viper。AddConfigPath(“。”)

err := viper。ReadInConfig()

if err != nil {

log。Fatal(“read config failed: %v”, err)

}

var c Config

viper。Unmarshal(&c)

fmt。Println(c。MySQL)

}

編譯,執行程式,輸出:

{127。0。0。1 3306 dj 123456 awesome}

儲存配置

有時候,我們想要將程式中生成的配置,或者所做的修改儲存下來。viper 提供了介面!

WriteConfig

:將當前的 viper 配置寫到預定義路徑,如果沒有預定義路徑,返回錯誤。將會覆蓋當前配置;

SafeWriteConfig

:與上面功能一樣,但是如果配置檔案存在,則不覆蓋;

WriteConfigAs

:儲存配置到指定路徑,如果檔案存在,則覆蓋;

SafeWriteConfig

:與上面功能一樣,但是入股配置檔案存在,則不覆蓋。

下面我們透過程式生成一個

config。toml

配置:

package main

import (

“log”

“github。com/spf13/viper”

func main() {

viper。SetConfigName(“config”)

viper。SetConfigType(“toml”)

viper。AddConfigPath(“。”)

viper。Set(“app_name”, “awesome web”)

viper。Set(“log_level”, “DEBUG”)

viper。Set(“mysql。ip”, “127。0。0。1”)

viper。Set(“mysql。port”, 3306)

viper。Set(“mysql。user”, “root”)

viper。Set(“mysql。password”, “123456”)

viper。Set(“mysql。database”, “awesome”)

viper。Set(“redis。ip”, “127。0。0。1”)

viper。Set(“redis。port”, 6381)

err := viper。SafeWriteConfig()

if err != nil {

log。Fatal(“write config failed: ”, err)

}

}

編譯、執行程式,生成的檔案如下:

app_name = “awesome web”

log_level = “DEBUG”

[mysql]

database = “awesome”

ip = “127。0。0。1”

password = “123456”

port = 3306

user = “root”

[redis]

ip = “127。0。0。1”

port = 6381

監聽檔案修改

viper 可以監聽檔案修改,熱載入配置。因此不需要重啟伺服器,就能讓配置生效。

package main

import (

“fmt”

“log”

“time”

“github。com/spf13/viper”

func main() {

viper。SetConfigName(“config”)

viper。SetConfigType(“toml”)

viper。AddConfigPath(“。”)

err := viper。ReadInConfig()

if err != nil {

log。Fatal(“read config failed: %v”, err)

}

viper。WatchConfig()

fmt。Println(“redis port before sleep: ”, viper。Get(“redis。port”))

time。Sleep(time。Second * 10)

fmt。Println(“redis port after sleep: ”, viper。Get(“redis。port”))

}

只需要呼叫

viper。WatchConfig

,viper 會自動監聽配置修改。如果有修改,重新載入的配置。

上面程式中,我們先列印

redis。port

的值,然後

Sleep

10s。在這期間修改配置中

redis。port

的值,

Sleep

結束後再次列印。發現打印出修改後的值:

redis port before sleep: 7381

redis port after sleep: 73810

另外,還可以為配置修改增加一個回撥:

viper。OnConfigChange(func(e fsnotify。Event) {

fmt。Printf(“Config file:%s Op:%s\n”, e。Name, e。Op)

})

這樣檔案修改時會執行這個回撥。

viper 使用fsnotify這個庫來實現監聽檔案修改的功能。

完整示例程式碼見 GitHub。

參考

viper GitHub 倉庫

我的部落格

歡迎關注我的微信公眾號【GoUpUp】,共同學習,一起進步~

http://

weixin。qq。com/r/9y-s9Of

EYboeraOX93rl

(二維碼自動識別)