日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Golang配置解析神器go?viper使用詳解_Golang

作者:程序員讀書 ? 更新時間: 2022-07-15 編程語言

前言

對于現代應用程序,尤其大中型的項目來說,在程序啟動和運行時,往往需要傳入很多參數來控制程序的行為,這些參數可以通過以下幾種方式傳遞給程序:

  • 命令行參數
  • 環境變量
  • 配置文件

顯然,對于Go項目而言,單個去讀取命令行、環境變量、配置文件并不難,但一個個讀取卻是很麻煩,有沒有一個第三方庫可以幫我們一次性讀取上面幾種數據源的配置呢?

有的,這里推薦使用viper庫,viper支持讀取不同數據源和不同格式的配置文件,是Go項目讀取配置的神器,今天跟著這篇文章,一起來探究一下吧!~

viper簡介

viper是一個很完善的Go項目配置解決方案,很多著名的開源項目都在使用,比如Hugo,Docker都使用了該庫,使用viper可以讓我們專注于自己的項目代碼,而不用自己寫那些配置解析代碼。

功能

  • 支持配置key默認值設置
  • 支持讀取JSON,TOML,YAML,HCL,envfile和java properties等多種不同類型配置文件
  • 可以監聽配置文件的變化,并重新加載配置文件
  • 讀取系統環境變量的值
  • 讀取存儲在遠程配置中心的配置數據,如ectd,Consul,firestore等系統,并監聽配置的變化
  • 從命令行讀取配置
  • 從buffer讀取配置
  • 可以顯示設置配置的值

viper配置優先級

viper支持從多個數據源讀取配置值,因此當同一個配置key在多個數據源有值時,viper讀取的優先級如下:

  • 顯示使用Set函數設置值
  • flag:命令行參數
  • env:環境變量
  • config:配置文件
  • key/value store:key/value存儲系統,如(etcd)
  • default:默認值

優先級示意圖

安裝viper

viper的安裝非常簡單,如同其他Go第三方包一樣,只需要go get命令即可安裝,如:

安裝

go get github.com/spf13/viper

使用

import "github.com/spf13/viper"

支持哪些文件格式

我們一直在說,viper支持多種不同格式的配置文件,到底是哪些格式呢?如下:

  • json
  • toml
  • yaml
  • yml
  • properties
  • props
  • prop
  • hcl
  • tfvars
  • dotenv
  • env
  • ini

key大小寫問題

viper的配置的key值是不區分大小寫,如:

# 小寫的key
viper.set("test","this is a test value")
# 大寫的key,也可以讀到值
fmt.Println(viper.get("TEST"))//輸出"this is a test value"

使用指南

在了解了viper是什么之后,下面我們來看看要怎么使用viper去幫我們讀取配置。

如何訪問viper的功能

使用包名viper,如:

viper.SetDefault("key1","value")//調用包級別下的函數

使用viper.New()函數創建一個Viper Struct,如:

viper := viper.New()
viper.SetDefault("key2","value2")

其實,這就是Go包的編程慣例,對實現功能對象再進行封裝,并通過包名來調用。

因此,下面所有示例中調用函數使用viper,可以是指包名viper,或者通過viper.New()返回的對象。

配置默認值

viper.SetDefault("key1","value1")
viper.SetDefault("key2","value2")

讀取配置文件

直接指定文件路徑

viper.SetConfigFile("./config.yaml")
viper.ReadInConfig()
fmt.Println(viper.Get("test"))

多路徑查找

viper.SetConfigName("config")     // 配置文件名,不需要后綴名
viper.SetConfigType("yml")            // 配置文件格式
viper.AddConfigPath("/etc/appname/")  // 查找配置文件的路徑
viper.AddConfigPath("$HOME/.appname") // 查找配置文件的路徑
viper.AddConfigPath(".")              // 查找配置文件的路徑
err := viper.ReadInConfig()           // 查找并讀取配置文件
if err != nil {                       // 處理錯誤
    panic(fmt.Errorf("Fatal error config file: %w \n", err))
}

讀取配置文件時,可能會出現錯誤,如果我們想判斷是否是因為找不到文件而報錯的,可以判斷err是否為ConfigFileNotFoundError

if err := viper.ReadInConfig(); err != nil {
	if _, ok := err.(viper.ConfigFileNotFoundError); ok {
		
	} else {
		
	}
}

寫配置文件

除了讀取配置文件外,viper也支持將配置值寫入配置文件,viper提供了四個函數,用于將配置寫回文件。

WriteConfig

WriteConfig函數會將配置寫入預先設置好路徑的配置文件中,如果配置文件存在,則覆蓋,如果沒有,則創建。

SafeWriteConfig

SafeWriterConfig與WriteConfig函數唯一的不同是如果配置文件存在,則會返回一個錯誤。

WriteConfigAs

WriteConfigAs與WriteConfig函數的不同是需要傳入配置文件保存路徑,viper會根據文件后綴判斷寫入格式。

SafeWriteConfigAs

SafeWriteConfigAs與WriteConfigAs的唯一不同是如果配置文件存在,則返回一個錯誤。

監聽配置文件

viper支持監聽配置文件,并會在配置文件發生變化,重新讀取配置文件和回調函數,這樣可以避免每次配置變化時,都需要重啟啟動應用的麻煩。

viper.OnConfigChange(func(e fsnotify.Event) {
	fmt.Println("Config file changed:", e.Name)
})
viper.WatchConfig()

從io.Reader讀取配置

除了支持從配置文件讀取配置外,viper也支持從實現了io.Reader接口的實例中讀取配置(其實配置文件也實現了io.Reader),如:

viper.SetConfigType("json") //設置格式

var yamlExample = []byte(`
{
	"name":"小明"
}
`)
viper.ReadConfig(bytes.NewBuffer(yamlExample))
fmt.Println(viper.Get("name")) //輸出“小明”

顯示設置配置項

也可以使用Set函數顯示為某個key設置值,這種方式的優先級最高,會覆蓋該key在其他地方的值,如:

viper.SetConfigType("json") //設置格式
var yamlExample = []byte(`
{
	"name":"小明"
}
`)
viper.ReadConfig(bytes.NewBuffer(yamlExample))
fmt.Println(viper.Get("name")) //輸出:小明
viper.Set("name","test")
fmt.Println(viper.Get("name"))//輸出:test

注冊和使用別名

為某個配置key設置別名,這樣可以方便我們在不改變key的情況下,使用別的名稱訪問該配置。

viper.Set("name", "test")
//為name設置一個username的別名
viper.RegisterAlias("username", "name")
//通過username可以讀取到name的值
fmt.Println(viper.Get("username"))
//修改name的配置值,username的值也發生改變
viper.Set("name", "小明")
fmt.Println(viper.Get("username"))
//修改username的值,name的值也發生改變
viper.Set("username", "測試")
fmt.Println(viper.Get("name"))

讀取環境變量

對于讀取操作系統環境變量,viper提供了下面五個函數:

  • AutomaticEnv()
  • BindEnv(string...) : error
  • SetEnvPrefix(string)
  • SetEnvKeyReplacer(string...) *strings.Replacer
  • AllowEmptyEnv(bool)

要讓viper讀取環境變量,有兩種方式:

  • 調用AutomaticEnv函數,開啟環境變量讀取
fmt.Println(viper.Get("path"))
//開始讀取環境變量,如果沒有調用這個函數,則下面無法讀取到path的值
viper.AutomaticEnv()
//會從環境變量讀取到該值,注意不用區分大小寫
fmt.Println(viper.Get("path"))
  • 使用BindEnv綁定某個環境變量
//將p綁定到環境變量PATH,注意這里第二個參數是環境變量,這里是區分大小寫的
viper.BindEnv("p", "PATH")
//錯誤綁定方式,path為小寫,無法讀取到PATH的值
//viper.BindEnv("p","path")
fmt.Println(viper.Get("p"))//通過p可以讀取PATH的值

使用函數SetEnvPrefix可以為所有環境變量設置一個前綴,這個前綴會影響AutomaticEnvBindEnv函數

os.Setenv("TEST_PATH","test")
viper.SetEnvPrefix("test")
viper.AutomaticEnv()
//無法讀取path的值,因為此時加上前綴,viper會去讀取TEST_PATH這個環境變量的值
fmt.Println(viper.Get("path"))//輸出:nil
fmt.Println(viper.Get("test_path"))//輸出:test

環境變量大多是使用下劃號(_)作為分隔符的,如果想替換,可以使用SetEnvKeyReplacer函數,如:

//設置一個環境變量
os.Setenv("USER_NAME", "test")
//將下線號替換為-和.
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
//讀取環境變量
viper.AutomaticEnv()
fmt.Println(viper.Get("user.name"))//通過.訪問
fmt.Println(viper.Get("user-name"))//通過-訪問
fmt.Println(viper.Get("user_name"))//原來的下劃線也可以訪問

默認的情況下,如果讀取到的環境變量值為空(注意,不是環境變量不存在,而是其值為空),會繼續向優化級更低數據源去查找配置,如果想阻止這一行為,讓空的環境變量值有效,則可以調用AllowEmptyEnv函數:

viper.SetDefault("username", "admin")
viper.SetDefault("password", "123456")
//默認是AllowEmptyEnv(false),這里設置為true
viper.AllowEmptyEnv(true)
viper.BindEnv("username")
os.Setenv("USERNAME", "")
fmt.Println(viper.Get("username"))//輸出為空,因為環境變量USERNAME空
fmt.Println(viper.Get("password"))//輸出:123456

與命令行參數搭配使用

viper可以和解析命令行庫相關flag庫一起工作,從命令行讀取配置,其內置了對pflag庫的支持,同時也留有接口讓我們可以支持擴展其他的flag庫

pflag

pflag.Int("port", 8080, "server http port")
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)
fmt.Println(viper.GetInt("port"))//輸出8080

擴展其他flag

如果我們沒有使用pflag庫,但又想讓viper幫我們讀取命令行參數呢?

package main
import (
	"flag"
	"fmt"
	"github.com/spf13/viper"
)
type myFlag struct {
	f *flag.Flag
}
func (m *myFlag) HasChanged() bool {
	return false
}
func (m *myFlag) Name() string {
	return m.f.Name
}
func (m *myFlag) ValueString() string {
	return m.f.Value.String()
}
func (m *myFlag) ValueType() string {
	return "string"
}
func NewMyFlag(f *flag.Flag) *myFlag {
	return &myFlag{f: f}
}
func main() {
	flag.String("username", "defaultValue", "usage")
	m := NewMyFlag(flag.CommandLine.Lookup("username"))
	viper.BindFlagValue("myFlagValue", m)
	flag.Parse()
	fmt.Println(viper.Get("myFlagValue"))
}

遠程key/value存儲支持

viper支持存儲或者讀取遠程配置存儲中心和NoSQL(目前支持etcd,Consul,firestore)的配置,并可以實時監聽配置的變化,不過需要在代碼中引入下面的包:

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

現在遠程配置中心存儲著以下JSON的配置信息

{
	"hostname":"localhost",
	"port":"8080"
}

那么我們可以通過下面的方面連接到系統,并讀取配置,也可以單獨開啟一個Goroutine實時監聽配置的變化。

連接Consul

viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")

連接etcd

viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")

連接firestore

viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")

連接上配置中心后,就可以像讀取配置文件讀取其中的配置了,如:

viper.SetConfigType("json")
err := viper.ReadRemoteConfig()
fmt.Println(viper.Get("port")) // 輸出:8080
fmt.Println(viper.Get("hostname")) // 輸出:localhost

監聽配置系統,如:

go func(){
	for {
		time.Sleep(time.Second * 5) 
		err := viper.WatchRemoteConfig()
		if err != nil {
			log.Errorf("unable to read remote config: %v", err)
			continue
		}
	}
}()

另外,viper連接etcd,Consul,firestore進行配置傳輸時,也支持加解密,這樣可以更加安全,如果想要實現加密傳輸可以把AddRemoteProvider函數換為SecureRemoteProvider

viper.SecureRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")

訪問配置

viper可以幫我們讀取各個地方的配置,那讀到配置之后,要怎么用呢?

直接訪問

{
  "mysql":{
    "db":"test"
  },
  "host":{
	  "address":"localhost"
	  "ports":[
		  "8080",
		  "8081"
	  ]
  }
}

對于多層級配置key,可以用逗號隔號,如:

viper.Get("mysql.db")
viper.GetString("user.db")
viper.Get("host.address")//輸出:localhost

數組,可以用序列號訪問,如:

viper.Get("host.posts.1")//輸出: 8081

也可以使用sub函數解析某個key的下級配置,如:

hostViper := viper.Sub("host")
fmt.Println(hostViper.Get("address"))
fmt.Println(hostViper.Get("posts.1"))

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

序列化

讀取了配置之后,除了使用上面列舉出來的函數訪問配置,還可以將配置序列化到struct或map之中,這樣可以更加方便訪問配置。

示例代碼

配置文件:config.yaml

host: localhost
username: test
password: test
port: 3306
charset: utf8
dbName: test

解析代碼:

type MySQL struct {
	Host     string
	DbName   string
	Port     string
	Username string
	Password string
	Charset  string
}
func main() {

	viper.SetConfigName("config")
	viper.SetConfigType("yaml")
	viper.AddConfigPath(".")
	viper.ReadInConfig()
	var mysql MySQL
	viper.Unmarshal(&mysql)//序列化

	fmt.Println(mysql.Username)
	fmt.Println(mysql.Host)
}

對于多層級的配置,viper也支持序列化到一個復雜的struct中,如:

我們將config.yaml改為如下結構:

mysql: 
  host: localhost
  username: test
  password: test
  port: 3306
  charset: utf8
  dbName: test
redis: 
  host: localhost
  port: 6379

示例程序

type MySQL struct {
	Host     string
	DbName   string
	Username string
	Password string
	Charset  string
}
type Redis struct {
	Host string
	Port string
}
type Config struct {
	MySQL MySQL
	Redis Redis
}
func main() {
	viper.SetConfigName("config")
	viper.SetConfigType("yaml")
	viper.AddConfigPath(".")
	viper.ReadInConfig()
	var config Config
	viper.Unmarshal(&config)
	fmt.Println(config.MySQL.Username)
	fmt.Println(config.Redis.Host)
}

判斷配置key是否存在

if viper.IsSet("user"){
	fmt.Println("key user is not exists")
}

打印所有配置

m := viper.AllSettings()
fmt.Println(m)

小結

原文鏈接:https://juejin.cn/post/7096416508054044685

欄目分類
最近更新