您好,登錄后才能下訂單哦!
小編給大家分享一下Golang配置庫viper的用法,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
正文
viper 的功能
? viper 支持以下功能:
? 1. 支持Yaml、Json、 TOML、HCL 等格式的配置
? 2. 可以從文件、io、環(huán)境變量、command line中提取配置
? 3. 支持自動轉(zhuǎn)換的類型解析
? 4. 可以遠程從etcd中讀取配置
示例代碼
定義一個類型:
type config struct { v *viper.Viper; }
用于測試的Yaml配置文件 config.yaml
TimeStamp: "2018-07-16 10:23:19" Author: "WZP" PassWd: "Hello" Information: Name: "Harry" Age: "37" Alise: - "Lion" - "NK" - "KaQS" Image: "/path/header.rpg" Public: false Favorite: Sport: - "swimming" - "football" Music: - "zui xuan min zu feng" LuckyNumber: 99
讀取yaml配置文件
func LoadConfigFromYaml (c *config) error { c.v = viper.New(); //設(shè)置配置文件的名字 c.v.SetConfigName("config") //添加配置文件所在的路徑,注意在Linux環(huán)境下%GOPATH要替換為$GOPATH c.v.AddConfigPath("%GOPATH/src/") c.v.AddConfigPath("./") //設(shè)置配置文件類型 c.v.SetConfigType("yaml"); if err := c.v.ReadInConfig(); err != nil{ return err; } log.Printf("age: %s, name: %s \n", c.v.Get("information.age"), c.v.Get("information.name")); return nil; }
? 注意:如果不用AddConfigPath去指定路徑,它會在程序執(zhí)行的目錄去尋找config.yaml
從IO中讀取配置
//由IO讀取配置 func ReadConfigFormIo(c *config) error { c.v = viper.New() if f, err := os.Open("config.yaml"); err != nil{ log.Printf("filure: %s", err.Error()); return err; }else { confLength, _ :=f.Seek(0,2); //注意,通常寫c++的習(xí)慣害怕讀取字符串的時候越界,都會多留出一個NULL在末尾,但是在這里不行,會報出如下錯誤: //While parsing config: yaml: control characters are not allowed //錯誤參考網(wǎng)址:https://stackoverflow.com/questions/33717799/go-yaml-control-characters-are-not-allowed-error configData := make([]byte, confLength); f.Seek(0, 0); f.Read(configData); log.Printf("%s\n", string(configData)) c.v.SetConfigType("yaml"); if err := c.v.ReadConfig(bytes.NewBuffer(configData)); err != nil{ log.Fatalf(err.Error()); } } log.Printf("age: %s, name: %s \n", c.v.Get("information.age"), c.v.Get("information.name")); return nil; }
? 上面的代碼是把配置文件中的數(shù)據(jù)導(dǎo)入IO,然后再從IO中讀取
從環(huán)境變量中讀取配置
//讀取本地的環(huán)境變量 func EnvConfigPrefix(c *config) error { c.v = viper.New(); //BindEnv($1,$2) // 如果只傳入一個參數(shù),則會提取指定的環(huán)境變量$1,如果設(shè)置了前綴,則會自動補全 前綴_$1 //如果傳入兩個參數(shù)則不會補全前綴,直接獲取第二參數(shù)中傳入的環(huán)境變量$2 os.Setenv("LOG_LEVEL", "INFO"); if nil == c.v.Get("LOG_LEVEL ") { log.Printf("LOG_LEVEL is nil"); }else { return ErrorNotMacth; } //必須要綁定后才能獲取 c.v.BindEnv("LOG_LEVEL"); log.Printf("LOG_LEVEL is %s", os.Getenv("log_level")); //會獲取所有的環(huán)境變量,同時如果過設(shè)置了前綴則會自動補全前綴名 c.v.AutomaticEnv(); //環(huán)境變量前綴大小寫不區(qū)分 os.Setenv("DEV_ADDONES","none"); log.Printf("DEV_ADDONES: %s", c.v.Get("dev_addones")); //SetEnvPrefix會設(shè)置一個環(huán)境變量的前綴名 c.v.SetEnvPrefix("DEV"); os.Setenv("DEV_MODE", "true"); //此時會自動補全前綴,實際去獲取的是DEV_DEV_MODE if nil == c.v.Get("dev_mode"){ log.Printf("DEV_MODE is nil") ; }else { return ErrorNotMacth; } //此時我們直接指定了loglevel所對應(yīng)的環(huán)境變量,則不會去補全前綴 c.v.BindEnv("loglevel", "LOG_LEVEL"); log.Printf("LOG_LEVEL: %s", c.v.Get("loglevel")) ; return nil }
? SetEnvPrefix 和 AutomaticEnv、BindEnv搭配使用很方便,比如說我們把當前程序的環(huán)境變量都設(shè)置為xx_ ,這樣方便我們管理,也避免和其他環(huán)境變量沖突,而在讀取的時候又很方便的就可以讀取。
方便的替換符
func EnvCongiReplacer(c *config, setPerfix bool) error { c.v = viper.New(); c.v.AutomaticEnv(); c.v.SetEnvKeyReplacer(strings.NewReplacer(".","_")); os.Setenv("API_VERSION","v0.1.0"); //Replacer和prefix一起使用可能會沖突,比如我下面的例子 //因為會自動補全前綴最終由獲取API_VERSION變成API_API_VERSION if setPerfix{ c.v.SetEnvPrefix("api");} if s := c.v.Get("api.version"); s==nil{ return ErrorNoxExistKey }else { log.Printf("%s", c.v.Get("api.version")); } return nil; }
? 我們有時候需要去替換key中的某些字符,來轉(zhuǎn)化為對應(yīng)的環(huán)境變臉,比如說例子中將' . '替換為'_' ,由獲取api.version變成了api_version,但是有一點需要注意的,SetEnvPrefix和SetEnvKeyReplacer一起用的時候可能會混淆。
別名功能
//設(shè)置重載 和別名 func SetAndAliases(c *config) error { c.v = viper.New(); c.v.Set("Name","wzp"); c.v.RegisterAlias("id","Name"); c.v.Set("id","Mr.Wang"); //我們可以發(fā)現(xiàn)當別名對應(yīng)的值修改之后,原本的key也發(fā)生變化 log.Printf("id %s, name %s",c.v.Get("id"),c.v.Get("name") ); return nil; }
? 我們可以為key設(shè)置別名,當別名的值被重置后,原key對應(yīng)的值也會發(fā)生變化。
序列化和反序列化
type favorite struct { Sports []string; Music []string; LuckyNumber int; } type information struct { Name string; Age int; Alise []string; Image string; Public bool } type YamlConfig struct { TimeStamp string Author string PassWd string Information information Favorite favorite; } //將配置解析為Struct對象 func UmshalStruct(c *config) error { LoadConfigFromYaml(c); var cf YamlConfig if err := c.v.Unmarshal(&cf); err != nil{ return err; } return nil; } func YamlStringSettings(c *config) string { c.v = viper.New(); c.v.Set("name", "wzp"); c.v.Set("age", 18); c.v.Set("aliase",[]string{"one","two","three"}) cf := c.v.AllSettings() bs, err := yaml.Marshal(cf) if err != nil { log.Fatalf("unable to marshal config to YAML: %v", err) } return string(bs) } func JsonStringSettings(c *config) string { c.v = viper.New(); c.v.Set("name", "wzp"); c.v.Set("age", 18); c.v.Set("aliase",[]string{"one","two","three"}) cf := c.v.AllSettings() bs, err := json.Marshal(cf) if err != nil { log.Fatalf("unable to marshal config to YAML: %v", err) } return string(bs) }
? 超級實惠的一個功能,直接把配置反序列化到一個結(jié)構(gòu)體,爽歪歪有木有?也可以把設(shè)置直接序列化為我們想要的類型:yaml、json等等
從command Line中讀取配置
func main() { flag.String("mode","RUN","please input the mode: RUN or DEBUG"); pflag.Int("port",1080,"please input the listen port"); pflag.String("ip","127.0.0.1","please input the bind ip"); //獲取標準包的flag pflag.CommandLine.AddGoFlagSet(flag.CommandLine); pflag.Parse(); //BindFlag //在pflag.Init key后面使用 viper.BindPFlag("port", pflag.Lookup("port")); log.Printf("set port: %d", viper.GetInt("port")); viper.BindPFlags(pflag.CommandLine); log.Printf("set ip: %s", viper.GetString("ip")); }
? 可以使用標準的flag也可以使用viper包中自帶的pflag,作者建議使用pflag。
監(jiān)聽配置文件
//監(jiān)聽配置文件的修改和變動 func WatchConfig(c *config) error { if err := LoadConfigFromYaml(c); err !=nil{ return err; } ctx, cancel := context.WithCancel(context.Background()); c.v.WatchConfig() //監(jiān)聽回調(diào)函數(shù) watch := func(e fsnotify.Event) { log.Printf("Config file is changed: %s \n", e.String()) cancel(); } c.v.OnConfigChange(watch); <-ctx.Done(); return nil; }
? 重點來了啊,這個可以說是非常非常實用的一個功能,以往我們修改配置文件要么重啟服務(wù),要么搞一個api去修改,Viper把這個功能幫我們實現(xiàn)了。只要配置文件被修改保存后,我們事先注冊的watch函數(shù)就回被觸發(fā),只要我們在這里面添加更新操作就ok了。不過美中不足的是,它目前只監(jiān)聽配置文件。
拷貝子分支
func TestSubConfig(t *testing.T) { c := config{}; LoadConfigFromYaml(&c); sc := c.v.Sub("information"); sc.Set("age", 80); scs,_:=yaml.Marshal(sc.AllSettings()) t.Log(string(scs)); t.Logf("age: %d", c.v.GetInt("information.age")); }
? 拷貝一個子分支最大的用途就是我們可以復(fù)制一份配置,這樣在修改拷貝的時候原配置不會被修改,如果修改的配置出現(xiàn)了問題,我們可以方便的回滾。
獲取配置項的方法
//測試各種get類型 func TestGetValues(t *testing.T) { c := &config{} if err := LoadConfigFromYaml(c); err != nil{ t.Fatalf("%s: %s",t.Name(), err.Error()); } if info := c.v.GetStringMap("information"); info != nil{ t.Logf("%T", info); } if aliases := c.v.GetStringSlice("information.aliases"); aliases != nil{ for _, a := range aliases{ t.Logf("%s",a); } } timeStamp := c.v.GetTime("timestamp"); t.Logf("%s", timeStamp.String()); if public := c.v.GetBool("information.public"); public{ t.Logf("the information is public"); } age := c.v.GetInt("information.age"); t.Logf("%s age is %d", c.v.GetString("information.name"), age); }
?如果我們直接用Get獲取的返回值都是interface{}類型,這樣我們還要手動轉(zhuǎn)化一下,可以直接指定類型去獲取,方便快捷。
?除了以上所說的功能外,viper還有從etcd提取配置以及自定義flage的功能,這些大家感興趣可以自己去了解一下。
有趣的應(yīng)用
? 雖然Unmarshal Struct已經(jīng)足夠好用了,但有作者還是想開發(fā)一下新的玩法,比如說這個配置文件和當前的新版本不是很匹配,當然實際生產(chǎn)中我們是要講究向下兼容的。
var yamlConfig = YamlConfig{}; ycType := reflect.TypeOf(yamlConfig); for i := 0 ; i < ycType.NumField();i++{ name := ycType.Field(i).Name; element := reflect.ValueOf(yamlConfig).Field(i).Interface(); if err = config.UnmarshalKey(name, element); err != nil{ logger.Errorf("Error reading configuration:", err); } }
? 如上代碼所示,我們從最外圍的結(jié)構(gòu)體中找出子元素的名稱和interface,然后分別解析,這樣及時某一項缺失了我們也可以及時提醒用戶,或者設(shè)置缺省配置,還有很多好玩的方法,大家可以互相參考哦。
以上是“Golang配置庫viper的用法”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。