天天看點

golang常用庫:配置檔案解析庫viper

作者:幹飯人小羽

viper簡介#

viper 配置管了解析庫,是由大神 Steve Francia 開發,他在google上司着 golang 的産品開發,他也是 gohugo.io 的創始人之一,指令行解析庫 cobra 開發者。總之,他在golang領域是專家,很牛的一個人。

他的github位址:https://github.com/spf13

viper是一個配置管理的解決方案,它能夠從 json,toml,ini,yaml,hcl,env 等多種格式檔案中,讀取配置内容,它還能從一些遠端配置中心讀取配置檔案,如consul,etcd等;它還能夠監聽檔案的内容變化。

viper的 logo:

golang常用庫:配置檔案解析庫viper

二、viper功能介紹#

  • 讀取 json,toml,ini,yaml,hcl,env 等格式的檔案内容
  • 讀取遠端配置檔案,如 consul,etcd 等和監控配置檔案變化
  • 讀取指令行 flag 的值
  • 從 buffer 中讀取值

配置檔案又可以分為不同的環境,比如dev,test,prod等。

viper 可以幫助你專注配置檔案管理。

viper 讀取配置檔案的優先順序,從高到低,如下:

  • 顯式設定的Set函數
  • 指令行參數
  • 環境變量
  • 配置檔案
  • 遠端k-v 存儲系統,如consul,etcd等
  • 預設值
Viper 配置key是不區分大小寫的。

其實,上面的每一種檔案格式,都有一些比較有名的解析庫,如:

  • toml :https://github.com/BurntSushi/toml
  • json :json的解析庫比較多,下面列出幾個常用的 https://github.com/json-iterator/go https://github.com/mailru/easyjson https://github.com/bitly/go-simplejson https://github.com/tidwall/gjson
  • ini : https://github.com/go-ini/ini

    等等單獨檔案格式解析庫。

但是為啥子要用viper,因為它是一個綜合檔案解析庫,包含了上面所有的檔案格式解析,是一個集合體,少了配置多個庫的煩惱。

三、viper使用#

安裝viper指令:

go get github.com/spf13/viper

文檔: https://github.com/spf13/viper/blob/master/README.md#putting-values-into-viper

通過viper.Set設定值#

如果某個鍵通過viper.Set設定了值,那麼這個值讀取的優先級最高

Copyviper.Set("mysql.info", "this is mysql info")
           

設定預設值#

https://github.com/spf13/viper/blob/master/README.md#establishing-defaults

viper 支援預設值的設定。如果配置檔案、環境變量、遠端配置中沒有設定鍵值,就可以通過viper設定一些預設值。

Examples:

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

讀取配置檔案#

https://github.com/spf13/viper/blob/master/README.md#reading-config-files

讀取配置檔案說明#

讀取配置檔案要求:最少要知道從哪個位置查找配置檔案。使用者一定要設定這個路徑。

viper可以從多個路徑搜尋配置檔案,單個viper執行個體隻支援單個配置檔案。

viper本身沒有設定預設的搜尋路徑,需要使用者自己設定預設路徑。

viper搜尋和讀取配置檔案例子片段:

Copyviper.SetConfigName("config") // 配置檔案的檔案名,沒有擴充名,如 .yaml, .toml 這樣的擴充名
viper.SetConfigType("yaml")  // 設定擴充名。在這裡設定檔案的擴充名。另外,如果配置檔案的名稱沒有擴充名,則需要配置這個選項
viper.AddConfigPath("/etc/appname/") // 查找配置檔案所在路徑
viper.AddConfigPath("$HOME/.appname") // 多次調用AddConfigPath,可以添加多個搜尋路徑
viper.AddConfigPath(".")             // 還可以在工作目錄中搜尋配置檔案
err := viper.ReadInConfig()       // 搜尋并讀取配置檔案
if err != nil { // 處理錯誤
  panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
           

說明:

這裡執行viper.ReadInConfig()之後,viper才能确定到底用哪個檔案,viper按照上面的AddConfigPath() 進行搜尋,找到第一個名為 config.ext (這裡的ext代表擴充名: 如 json,toml,yaml,yml,ini,prop 等擴充名) 的檔案後即停止搜尋。

如果有多個名稱為config的配置檔案,viper怎麼搜尋呢?它會按照如下順序搜尋

config.json config.toml config.yaml config.yml config.properties (這種一般是java中的配置檔案名) config.props (這種一般是java中的配置檔案名)

你還可以處理一些特殊情況:

Copyif err := viper.ReadInConfig(); err != nil {
    if _, ok := err.(viper.ConfigFileNotFoundError); ok {        
        // 配置檔案沒有找到; 如果需要可以忽略
    } else {        
        // 查找到了配置檔案但是産生了其它的錯誤
    }
}

// 查找到配置檔案并解析成功
           
注意[自1.6起]: 你也可以有不帶擴充名的檔案,并以程式設計方式指定其格式。對于位于使用者$HOME目錄中的配置檔案沒有任何擴充名,如.bashrc。

例子1. 讀取配置檔案#

config.toml 配置檔案:

Copy# this is a toml 

title = "toml exaples"
redis = "127.0.0.1:3300"  # redis

[mysql]
host = "192.168.1.1"
ports = 3306
username = "root"
password = "root123456"
           

viper_toml.go:

Copypackage main

import(
    "fmt"
    "github.com/spf13/viper"
)

// 讀取配置檔案config
type Config struct {
    Redis string
    MySQL MySQLConfig
}

type MySQLConfig struct {
    Port int
    Host string
    Username string
    Password string
}

func main() {
    // 把配置檔案讀取到結構體上
    var config Config
    
    viper.SetConfigName("config")
    viper.AddConfigPath(".")
    err := viper.ReadInConfig()
    if err != nil {
        fmt.Println(err)
        return
    }
     
    viper.Unmarshal(&config) //将配置檔案綁定到config上
    fmt.Println("config: ", config, "redis: ", config.Redis)
}
           

例子2. 讀取多個配置檔案#

在例子1基礎上多增加一個json的配置檔案,config3.json 配置檔案:

Copy{
  "redis": "127.0.0.1:33000",
  "mysql": {
    "port": 3306,
    "host": "127.0.0.1",
    "username": "root",
    "password": "123456"
  }
}
           

viper_multi.go

Copypackage main

import (
	"fmt"

	"github.com/spf13/viper"
)

type Config struct {
	Redis string
	MySQL MySQLConfig
}

type MySQLConfig struct {
	Port     int
	Host     string
	Username string
	Password string
}

func main() {
	// 讀取 toml 配置檔案
	var config1 Config

	vtoml := viper.New()
	vtoml.SetConfigName("config")
	vtoml.SetConfigType("toml")
	vtoml.AddConfigPath(".")

	if err := vtoml.ReadInConfig(); err != nil {
		fmt.Println(err)
		return
	}

	vtoml.Unmarshal(&config1)
	fmt.Println("read config.toml")
	fmt.Println("config: ", config1, "redis: ", config1.Redis)

	// 讀取 json 配置檔案
	var config2 Config
	vjson := viper.New()
	vjson.SetConfigName("config3")
	vjson.SetConfigType("json")
	vjson.AddConfigPath(".")

	if err := vjson.ReadInConfig(); err != nil {
		fmt.Println(err)
		return
	}

	vjson.Unmarshal(&config2)
	fmt.Println("read config3.json")
	fmt.Println("config: ", config1, "redis: ", config1.Redis)
}
           

運作:

$ go run viper_multi.go

read config.toml

config: {127.0.0.1:33000 {0 192.168.1.1 root 123456}} redis: 127.0.0.1:33000

read config3.json

config: {127.0.0.1:33000 {0 192.168.1.1 root 123456}} redis: 127.0.0.1:33000

例子3. 讀取配置項的值#

建立檔案夾 item, 在裡面建立檔案 config.json,内容如下:

Copy{
  "redis": "127.0.0.1:33000",
  "mysql": {
    "port": 3306,
    "host": "127.0.0.1",
    "username": "root",
    "password": "123456",
    "ports": [
        5799,
        6029
    ],
    "metric": {
        "host": "127.0.0.1",
        "port": 2112
    }
  }
}
           

item/viper_get_item.go 讀取配置項的值

Copypackage main

import (
	"fmt"

	"github.com/spf13/viper"
)

func main() {
	viper.SetConfigName("config")
	viper.SetConfigType("json")
	viper.AddConfigPath(".")
	err := viper.ReadInConfig() //根據上面配置加載檔案
	if err != nil {
		fmt.Println(err)
		return
	}

	host := viper.Get("mysql.host")
	username := viper.GetString("mysql.username")
	port := viper.GetInt("mysql.port")
	portsSlice := viper.GetIntSlice("mysql.ports")

	metricPort := viper.GetInt("mysql.metric.port")
	redis := viper.Get("redis")

	mysqlMap := viper.GetStringMapString("mysql")

	if viper.IsSet("mysql.host") {
		fmt.Println("[IsSet()]mysql.host is set")
	} else {
		fmt.Println("[IsSet()]mysql.host is not set")
	}
	fmt.Println("mysql - host: ", host, ", username: ", username, ", port: ", port)
	fmt.Println("mysql ports :", portsSlice)
	fmt.Println("metric port: ", metricPort)
	fmt.Println("redis - ", redis)

	fmt.Println("mysqlmap - ", mysqlMap, ", username: ", mysqlMap["username"])
}
           

運作:

$ go run viper_get_item.go

[IsSet()]mysql.host is set

mysql - host: 127.0.0.1 , username: root , port: 3306

mysql ports : [5799 6029]

metric port: 2112

redis - 127.0.0.1:33000

mysqlmap - map[host:127.0.0.1 metric: password:123456 port:3306 ports: username:root] , username: root

如果把上面的檔案config.json寫成toml格式,怎麼解析? 改成config1.toml:

Copy# toml
toml = "toml example"

redis = "127.0.0.1:33000"

[mysql]
port = 3306
host = "127.0.0.1"
username = "root"
password = "123456"
ports = [5799,6029]
[mysql.metric]
host = "127.0.0.1"
port = 2112
           

其實解析代碼差不多,隻需修改2處,

viper.SetConfigName("config") 裡的 config 改成 config1 ,

viper.SetConfigType("json")裡的 json 改成 toml,其餘代碼都一樣。解析的效果也一樣。

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

例子4. 讀取指令行的值#

建立檔案夾 cmd,然後cmd檔案夾裡建立config.json檔案:

Copy{
  "redis":{
    "port": 3301,
    "host": "127.0.0.1"
  },
  "mysql": {
    "port": 3306,
    "host": "127.0.0.1",
    "username": "root",
    "password": "123456"
  }
}
           

go解析檔案,cmd/viper_pflag.go:

Copypackage main

import (
	"fmt"

	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

func main() {
	pflag.Int("redis.port", 3302, "redis port")

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

	viper.SetConfigName("config")
	viper.SetConfigType("json")
	viper.AddConfigPath(".")
	err := viper.ReadInConfig() //根據上面配置加載檔案
	if err != nil {
		fmt.Println(err)
		return
	}

	host := viper.Get("mysql.host")
	username := viper.GetString("mysql.username")
	port := viper.GetInt("mysql.port")
	redisHost := viper.GetString("redis.host")
	redisPort := viper.GetInt("redis.port")

	fmt.Println("mysql - host: ", host, ", username: ", username, ", port: ", port)
	fmt.Println("redis - host: ", redisHost, ", port: ", redisPort)
}
           

1.不加指令行參數運作:

$ go run viper_pflag.go

mysql - host: 127.0.0.1 , username: root , port: 3306

redis - host: 127.0.0.1 , port: 3301

說明:redis.port 的值是 3301,是 config.json 配置檔案裡的值。

2.加指令行參數運作

$ go run viper_pflag.go --redis.port 6666

mysql - host: 127.0.0.1 , username: root , port: 3306

redis - host: 127.0.0.1 , port: 6666

說明:加了指令行參數 --redis.port 6666,這時候redis.port輸出的值為 6666,讀取的是cmd指令行的值

例子5:io.Reader中讀取值#

https://github.com/spf13/viper#reading-config-from-ioreader

viper_ioreader.go

Copypackage main

import (
	"bytes"
	"fmt"

	"github.com/spf13/viper"
)

func main() {
	viper.SetConfigType("yaml")

	var yaml = []byte(`
Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:
  jacket: leather
  trousers: denim
age: 35
eyes : brown
beard: true
    `)

	err := viper.ReadConfig(bytes.NewBuffer(yaml))
	if err != nil {
		fmt.Println(err)
		return
	}
	hacker := viper.GetBool("Hacker")
	hobbies := viper.GetStringSlice("hobbies")
	jacket := viper.Get("clothing.jacket")
	age := viper.GetInt("age")
	fmt.Println("Hacker: ", hacker, ",hobbies: ", hobbies, ",jacket: ", jacket, ",age: ", age)

}
           

例子6:寫配置檔案#

https://github.com/spf13/viper#writing-config-files

建立檔案 writer/viper_write_config.go:

Copypackage main

import (
	"fmt"

	"github.com/spf13/viper"
)

func main() {
	viper.SetConfigName("config")
	viper.SetConfigType("yaml")
	viper.AddConfigPath(".")

	viper.Set("yaml", "this is a example of yaml")

	viper.Set("redis.port", 4405)
	viper.Set("redis.host", "127.0.0.1")

	viper.Set("mysql.port", 3306)
	viper.Set("mysql.host", "192.168.1.0")
	viper.Set("mysql.username", "root123")
	viper.Set("mysql.password", "root123")

	if err := viper.WriteConfig(); err != nil {
		fmt.Println(err)
	}
}
           

運作:

$ go run viper_write_config.go

沒有任何輸出表示生成配置檔案成功

Copymysql:
  host: 192.168.1.0
  password: root123
  port: 3306
  username: root123
redis:
  host: 127.0.0.1
  port: 4405
yaml: this is a example of yaml
           

WriteConfig() 和 SafeWriteConfig() 差別:#

如果待生成的檔案已經存在,那麼SafeWriteConfig()就會報錯,Config File "config.yaml" Already Exists, 而WriteConfig()則會直接覆寫同名檔案。