網站首頁 編程語言 正文
前言
對于現代應用程序,尤其大中型的項目來說,在程序啟動和運行時,往往需要傳入很多參數來控制程序的行為,這些參數可以通過以下幾種方式傳遞給程序:
- 命令行參數
- 環境變量
- 配置文件
顯然,對于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可以為所有環境變量設置一個前綴,這個前綴會影響AutomaticEnv
和BindEnv
函數
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
相關推薦
- 2023-06-20 React?DOM-diff?節點源碼解析_React
- 2022-03-27 C++命名空間和缺省參數介紹_C 語言
- 2022-07-16 Electron項目打包
- 2022-07-03 kali安裝docker及搭建漏洞環境的詳細教程_docker
- 2022-04-25 ASP.NET?Core中Razor頁面的Handlers處理方法詳解_基礎應用
- 2023-03-22 React使用Context與router實現權限路由詳細介紹_React
- 2022-09-04 Python中函數的參數類型詳解_python
- 2022-12-12 python語法之通過value找key問題_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支