Go項(xiàng)目開發(fā)中如何讀取應(yīng)用配置詳解
幾乎所有的后端服務(wù)都需要一些配置項(xiàng)來配置我們的服務(wù),一些小型的項(xiàng)目,配置不是很多,可以選擇只通過命令行參數(shù)來傳遞配置,但是大型項(xiàng)目配置很多,通過命令行參數(shù)傳遞就變的很麻煩,不好維護(hù),標(biāo)準(zhǔn)的解決方案是將這些配置信息保存在配置文件中,由程序啟動(dòng)時(shí)加載和解析。所以對(duì)于一個(gè)稍微大型點(diǎn)的系統(tǒng),配置文件加載和解析就是一個(gè)剛需。Go 生態(tài)中有很多包可以加載并解析配置,目前最受歡迎的是 Viper 包。
Viper 包介紹
Viper 是 Go 應(yīng)用程序現(xiàn)代化、完整的解決方案,能夠處理不同格式的配置文件,讓我們?cè)跇?gòu)建現(xiàn)代應(yīng)用程序時(shí),不必?fù)?dān)心配置文件格式。Viper 也能夠滿足我們對(duì)應(yīng)用配置的各種需求。Viper有 很多特性,其中一些比較重要的特性如下:
- 支持默認(rèn)配置。
- 支持從
json
、toml
、yaml
、yml
、properties
、props
、prop
、hcl
、dotenv
、env
格式的文件中讀取數(shù)據(jù)。 - 實(shí)時(shí)監(jiān)控和重新讀取配置文件(可選)。
- 支持從環(huán)境變量中讀取配置。
- 支持從遠(yuǎn)程配置系統(tǒng)(etcd 或 Consul)讀取并監(jiān)控配置變化。
- 從命令行參數(shù)讀取配置。
- 支持從 buffer 中讀取配置。
- 可以顯式的給配置項(xiàng)設(shè)置值。
Viper 可以從不同的位置讀取配置,不同位置的配置具有不同的優(yōu)先級(jí),高優(yōu)先的配置會(huì)覆蓋低優(yōu)先級(jí)相同的配置,按優(yōu)先級(jí)從高到低排列如下:
- 通過
viper.Set
函數(shù)式設(shè)置的配置; - 命令行參數(shù);
- 環(huán)境變量;
- 配置文件;
- key/value 存儲(chǔ);
- 默認(rèn)值。
Viper 因?yàn)槠鋸?qiáng)大的特性,越來越多的優(yōu)秀項(xiàng)目開始使用 Viper 作為其配置解析工具,例如:Hugo、Docker Notary、Clairctl、Mercure 等。
這里需要注意,Viper 配置鍵不區(qū)分大小寫。
Viper 使用方法
Viper 有很多功能,這里選擇一些常用的、重要的功能來講解。在使用 viper 的過程中,最重要的2類功能就是讀入配置和讀取配置,Viper 提供不同的方式來讀入配置和讀取配置。
讀入配置,將配置讀入到 viper 中,有如下讀入方式:
- 可以設(shè)置默認(rèn)的配置文件名。
- 讀取配置文件。
- 監(jiān)聽和重新讀取配置文件。
- 從
io.Reader
讀取配置。 - 從環(huán)境變量讀取。
- 從命令行標(biāo)志讀取。
- 從遠(yuǎn)程 Key/Value 存儲(chǔ)讀取。
讀取配置,從 viper 中讀取配置到應(yīng)用程序中,viper 提供如下函數(shù),來讀取配置: Get(key string) interface{}
Get<Type>(key string) <Type>
AllSettings() map[string]interface{}
讀入配置
設(shè)置默認(rèn)值
一個(gè)好的配置系統(tǒng)應(yīng)該支持默認(rèn)值。viper 支持對(duì) key 設(shè)置默認(rèn)值,當(dāng)沒有通過配置文件,環(huán)境變量,遠(yuǎn)程配置或命令行標(biāo)志設(shè)置 key 時(shí),設(shè)置默認(rèn)值通常是很有用的,可以使程序即使在沒有明確指定配置時(shí),也能夠正常運(yùn)行。例如:
viper.SetDefault("ContentDir", "content") viper.SetDefault("LayoutDir", "layouts") viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
讀取配置文件
viper 可以讀取配置文件來解析配置,支持 json
、toml
、yaml
、yml
、properties
、props
、prop
、hcl
、dotenv
、env
格式的配置文件。Viper 可以搜索多個(gè)路徑,但目前單個(gè) Viper 實(shí)例僅支持單個(gè)配置文件。Viper 不默認(rèn)任何配置搜索路徑,將默認(rèn)決策留給應(yīng)用程序。
以下是如何使用 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") // 如果配置文件名中沒有文件擴(kuò)展名,則需要指定配置文件的格式,告訴 viper 以何種格式解析文件 } else { viper.AddConfigPath(".") // 把當(dāng)前目錄加入到配置文件的搜索路徑中 viper.AddConfigPath("$HOME/.iam") // 配置文件搜索路徑,可以設(shè)置多個(gè)配置文件搜索路徑 viper.SetConfigName("config") // 配置文件名稱(沒有文件擴(kuò)展名) } if err := viper.ReadInConfig(); err != nil { // 讀取配置文件。如果指定了配置文件名,則使用指定的配置文件,否則在注冊(cè)的搜索路徑中搜索 panic(fmt.Errorf("Fatal error config file: %s \n", err)) } fmt.Printf("Used configuration file is: %s\n", viper.ConfigFileUsed()) }
在加載配置文件出錯(cuò)時(shí),你可以像下面這樣處理找不到配置文件的特定情況:
if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { // 配置文件未找到錯(cuò)誤;如果需要可以忽略 } else { // 配置文件被找到,但產(chǎn)生了另外的錯(cuò)誤 } } // 配置文件找到并成功解析
viper 支持設(shè)置多個(gè)配置文件搜索路徑,需要注意添加搜索路徑的順序,viper 會(huì)根據(jù)添加的路徑順序搜索配置文件,如果找到則停止搜索。如果調(diào)用 SetConfigFile
直接指定了配置文件名,并且配置文件名沒有文件擴(kuò)展名時(shí),需要顯試指定配置文件的格式,以使 viper 能夠正確解析配置文件。
如果通過搜索的方式查找配置文件,則需要注意 SetConfigName
設(shè)置的配置文件名是不帶擴(kuò)展名的,在搜索時(shí) viper 會(huì)在文件名之后追加文件擴(kuò)展名,并嘗試搜索所有支持的擴(kuò)展類型。比如,如果我們通過 SetConfigName
設(shè)置了配置文件名為 config
,則 viper 會(huì)在注冊(cè)的搜索路徑中,依次搜索:config.json
、config.toml
、config.yaml
、config.yml
、config.properties
、config.props
、config.prop
、config.hcl
、config.dotenv
、config.env
。
寫入配置文件
讀取配置文件很有用,但有時(shí)候我們可能需要將程序中當(dāng)前的配置保存起來,方便后續(xù)使用或者 debug,viper 提供了一系列的函數(shù),可以讓我們把當(dāng)前的配置保存到文件中,viper 提供了如下函數(shù)來保存配置:
WriteConfig
:保存當(dāng)前的配置到 viper 當(dāng)前使用的配置文件中,如果配置文件不存在會(huì)報(bào)錯(cuò),如果配置文件存在則覆蓋當(dāng)前的配置文件。SafeWriteConfig
:保存當(dāng)前的配置到 viper 當(dāng)前使用的配置文件中,如果配置文件不存在會(huì)報(bào)錯(cuò),如果配置文件存在則返回file exists
錯(cuò)誤。WriteConfigAs
:保存當(dāng)前的配置到指定的文件中,如果文件不存在則新建,如果文件存在則會(huì)覆蓋文件。SafeWriteConfigAs
:保存當(dāng)前的配置到指定的文件中,如果文件不存在則新建,如果文件存在則返回file exists
錯(cuò)誤。
根據(jù)經(jīng)驗(yàn),標(biāo)記為 Safe
的所有方法都不會(huì)覆蓋任何文件,而是直接創(chuàng)建(如果不存在),而默認(rèn)行為是創(chuàng)建或截?cái)唷?/p>
一個(gè)小示例:
viper.WriteConfig() viper.SafeWriteConfig() viper.WriteConfigAs("config.running.yaml") viper.SafeWriteConfigAs("config.running.yaml")
監(jiān)聽和重新讀取配置文件
Viper 支持在運(yùn)行時(shí)讓應(yīng)用程序?qū)崟r(shí)讀取配置文件,也就是熱加載配置。可以通過 WatchConfig
函數(shù)熱加載配置。在調(diào)用 WatchConfig
函數(shù)之前,請(qǐng)確保已經(jīng)添加了配置文件的搜索路徑??蛇x地,可以為 Viper 提供一個(gè)回調(diào)函數(shù),以便在每次發(fā)生更改時(shí)運(yùn)行。
示例:
viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { // 配置文件發(fā)生變更之后會(huì)調(diào)用的回調(diào)函數(shù) fmt.Println("Config file changed:", e.Name) })
設(shè)置配置值
我們可以通過 viper.Set()
函數(shù)來顯式設(shè)置配置:
viper.Set("user.username", "colin")
注冊(cè)和使用別名
別名允許多個(gè)鍵引用單個(gè)值。示例:
viper.RegisterAlias("loud", "Verbose") viper.Set("verbose", true) // 效果等同于下面一行代碼 viper.Set("loud", true) // 效果等同于上面一行代碼 viper.GetBool("loud") // true viper.GetBool("verbose") // true
使用環(huán)境變量
viper 還支持環(huán)境變量,通過如下 5 個(gè)函數(shù)來支持環(huán)境變量:
AutomaticEnv()
BindEnv(input ...string) error
SetEnvPrefix(in string)
SetEnvKeyReplacer(r *strings.Replacer)
AllowEmptyEnv(allowEmptyEnv bool)
這里要注意:viper 讀取環(huán)境變量是區(qū)分大小寫的。viper 提供了一種機(jī)制來確保 ENV
變量是唯一的。通過使用 SetEnvPrefix
,可以告訴 Viper 在讀取環(huán)境變量時(shí)使用前綴。BindEnv
和 AutomaticEnv
都將使用此前綴。比如,我們?cè)O(shè)置了 viper.SetEnvPrefix("VIPER")
,當(dāng)使用 viper.Get("apiversion")
時(shí),實(shí)際讀取的環(huán)境變量是 VIPER_APIVERSION
。
BindEnv
需要一個(gè)或兩個(gè)參數(shù)。第一個(gè)參數(shù)是鍵名,第二個(gè)是環(huán)境變量的名稱,環(huán)境變量的名稱區(qū)分大小寫。如果未提供 ENV
變量名,則viper將假定ENV變量名為: 環(huán)境變量前綴_鍵名全大寫
,例如:前綴為VIPER,key為username,則ENV變量名為: VIPER_USERNAME
。當(dāng)顯式提供 ENV
變量名(第二個(gè)參數(shù))時(shí),它不會(huì)自動(dòng)添加前綴。例如,如果第二個(gè)參數(shù)是 id
,Viper 將查找環(huán)境變量 ID
。
在使用 ENV
變量時(shí),需要注意的一件重要事情是,每次訪問該值時(shí)都將讀取它。Viper在調(diào)用 BindEnv
時(shí)不固定該值。
還有一個(gè)魔法函數(shù) SetEnvKeyReplacer
,SetEnvKeyReplacer
允許你使用 strings.Replacer
對(duì)象來重寫 Env
鍵。如果你想在 Get()
調(diào)用中使用 -
或者 .
,但希望你的環(huán)境變量使用 _
分隔符,可以通過 SetEnvKeyReplacer
來實(shí)現(xiàn)。比如,我們?cè)O(shè)置了環(huán)境變量 USER_SECRET_KEY=bVix2WBv0VPfrDrvlLWrhEdzjLpPCNYb
,但我們想用 viper.Get("user.secret-key")
,我們調(diào)用函數(shù):
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
上面的代碼,在調(diào)用 viper.Get()
函數(shù)時(shí),會(huì)用 _
替換 .
和 -
。默認(rèn)情況下,空環(huán)境變量被認(rèn)為是未設(shè)置的,并將返回到下一個(gè)配置源。若要將空環(huán)境變量視為已設(shè)置,可以使用 AllowEmptyEnv
方法。使用環(huán)境變量示例如下:
// 使用環(huán)境變量 os.Setenv("VIPER_USER_SECRET_ID", "QLdywI2MrmDVjSSv6e95weNRvmteRjfKAuNV") os.Setenv("VIPER_USER_SECRET_KEY", "bVix2WBv0VPfrDrvlLWrhEdzjLpPCNYb") viper.AutomaticEnv() // 讀取環(huán)境變量 viper.SetEnvPrefix("VIPER") // 設(shè)置環(huán)境變量前綴:VIPER_,如果是 viper,將自動(dòng)轉(zhuǎn)變?yōu)榇髮憽? viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) // 將 viper.Get(key) key 字符串中 '.' 和 '-' 替換為 '_' viper.BindEnv("user.secret-key") viper.BindEnv("user.secret-id", "USER_SECRET_ID") // 綁定環(huán)境變量名到 key
使用標(biāo)志
viper 支持 pflag 包,能夠綁定 key 到 flag。與 BindEnv
類似,在調(diào)用綁定方法時(shí),不會(huì)設(shè)置該值。但在訪問它時(shí)會(huì)設(shè)置。對(duì)于單個(gè)標(biāo)志,可以調(diào)用 BindPFlag()
進(jìn)行綁定:
viper.BindPFlag("token", pflag.Lookup("token")) // 綁定單個(gè)標(biāo)志
還可以綁定一組現(xiàn)有的pflags(pflag.FlagSet
):
viper.BindPFlags(pflag.CommandLine) //綁定標(biāo)志集
讀取配置
viper 提供了如下方法來讀取配置:
Get(key string) interface{}
;Get<Type>(key string) <Type>
;AllSettings() map[string]interface{}
;IsSet(key string) bool
。
每一個(gè) Get
方法在找不到值的時(shí)候都會(huì)返回零值。為了檢查給定的鍵是否存在,可以使用 IsSet()
方法。<Type>
可以是 viper 支持的類型首字母大寫:Bool
、Float64
、Int
、IntSlice
、String
、StringMap
、StringMapString
、StringSlice
、Time
、Duration
。例如:GetInt()
。
讀取配置具體使用方法如下:
訪問嵌套的鍵
例如:加載下面的 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,環(huán)境變量,set()
方法等等),那么 datastore.metric
的所有子鍵都將變?yōu)槲炊x狀態(tài),它們被高優(yōu)先級(jí)配置級(jí)別覆蓋了。
如果存在與分隔的鍵路徑匹配的鍵,則直接返回其值。例如:
{ "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"
提取子樹
例如:viper 加載了如下配置:
app: cache1: max-items: 100 item-size: 64 cache2: max-items: 200 item-size: 80
可以通過 viper.Sub
提取子樹:
subv := viper.Sub("app.cache1")
subv
現(xiàn)在就代表:
max-items: 100 item-size: 64
反序列化
viper 可以支持將所有或特定的值解析到結(jié)構(gòu)體、map 等??梢酝ㄟ^ 2 個(gè)函數(shù)來實(shí)現(xiàn):
Unmarshal(rawVal interface{}) error
UnmarshalKey(key string, rawVal interface{}) error
一個(gè)示例:
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) }
如果想要解析那些鍵本身就包含 .
(默認(rèn)的鍵分隔符)的配置,則需要修改分隔符:
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 在后臺(tái)使用 github.com/mitchellh/mapstructure
來解析值,其默認(rèn)情況下使用 mapstructure tags
。當(dāng)我們需要將 viper 讀取的配置反序列到我們定義的結(jié)構(gòu)體變量中時(shí),一定要使用 mapstructure tags。
序列化成字符串
有時(shí)候我們需要將 viper 中保存的所有設(shè)置序列化到一個(gè)字符串中,而不是將它們寫入到一個(gè)文件中,示例如下:
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) }
總結(jié)
本文討論了 Go 語言中用于加載和解析配置文件的 Viper 包。Viper 支持多種格式的配置文件,并提供了許多功能來幫助開發(fā)者管理配置。
關(guān)鍵要點(diǎn)包括:
- 配置文件格式:Viper 支持 JSON、TOML、YAML、HCL 和 INI 格式的配置文件。
- 默認(rèn)值設(shè)置:開發(fā)者可以為配置鍵設(shè)置默認(rèn)值,以便在配置文件中未找到該鍵時(shí)使用。
- 讀取配置文件:Viper 提供了多種方法來讀取配置文件,包括從文件系統(tǒng)、環(huán)境變量或命令行參數(shù)中讀取。
- 監(jiān)聽和重新讀取配置文件:Viper 可以監(jiān)聽配置文件的更改,并自動(dòng)重新加載配置。
- 示例代碼:文檔提供了一些示例代碼,幫助開發(fā)者更好地理解和使用 Viper 包。
到此這篇關(guān)于Go項(xiàng)目開發(fā)中如何讀取應(yīng)用配置詳解的文章就介紹到這了,更多相關(guān)Go 讀取應(yīng)用配置內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
gorm 結(jié)構(gòu)體中 binding 和 msg 結(jié)構(gòu)體標(biāo)簽示例詳解
文章介紹了Gin框架中binding和msg結(jié)構(gòu)體標(biāo)簽的使用,包括基本用法、常用驗(yàn)證規(guī)則、自定義驗(yàn)證器、錯(cuò)誤信息自定義、控制器使用示例、組合驗(yàn)證規(guī)則、跨字段驗(yàn)證和初始化驗(yàn)證器等,這些標(biāo)簽主要用于數(shù)據(jù)驗(yàn)證、自定義錯(cuò)誤信息、參數(shù)綁定和表單驗(yàn)證2024-11-11淺析Go語言如何避免數(shù)據(jù)競(jìng)爭(zhēng)Data?Race和競(jìng)態(tài)條件Race?Condition
在并發(fā)編程中,數(shù)據(jù)競(jìng)爭(zhēng)?(Data?Race)?和?競(jìng)態(tài)條件?(Race?Condition)?是兩個(gè)常見的問題,本文將簡(jiǎn)單介紹一下二者如何避免,有需要的可以了解下2025-01-01Golang實(shí)現(xiàn)Json分級(jí)解析及數(shù)字解析實(shí)踐詳解
你是否遇到過在無法準(zhǔn)確確定json層級(jí)關(guān)系的情況下對(duì)json進(jìn)行解析的需求呢?本文就來和大家介紹一次解析不確定的json對(duì)象的經(jīng)歷,以及遇到的問題和解決方法2023-02-02Go中g(shù)routine通信與context控制實(shí)例詳解
隨著context包的引入,標(biāo)準(zhǔn)庫中很多接口因此加上了context參數(shù),下面這篇文章主要給大家介紹了關(guān)于Go中g(shù)routine通信與context控制的相關(guān)資料,需要的朋友可以參考下2022-02-02使用Gin框架搭建一個(gè)Go Web應(yīng)用程序的方法詳解
在本文中,我們將要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 Web 應(yīng)用程序,通過 Gin 框架來搭建,主要支持用戶注冊(cè)和登錄,用戶可以通過注冊(cè)賬戶的方式創(chuàng)建自己的賬號(hào),并通過登錄功能進(jìn)行身份驗(yàn)證,感興趣的同學(xué)跟著小編一起來看看吧2023-08-08Go語言調(diào)用SiliconFlow實(shí)現(xiàn)文本轉(zhuǎn)換為MP3格式
這篇文章主要為大家詳細(xì)介紹了Go語言如何調(diào)用?SiliconFlow?語音生成?API?的腳本,用于將文本轉(zhuǎn)換為?MP3?格式的語音文件,感興趣的小伙伴可以了解下2025-02-02細(xì)細(xì)探究Go 泛型generic設(shè)計(jì)
這篇文章主要帶大家細(xì)細(xì)探究了Go 泛型generic設(shè)計(jì)及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04