引言
viper 是一个用于读取配置文件的库。如果你须要读取配置文件,那么 viper 足够好用。
我的项目地址
我的项目地址: https://github.com/spf13/viper [star:20.7k]
应用场景
- 读取配置
装置
go get github.com/spf13/viper
罕用办法
- SetConfigFile 定义配置文件
- ReadInConfig 读取配置文件
- GetString 获取某个key的配置
- WatchConfig 监听配置
- OnConfigChange 定义配置扭转对应的操作
例子
咱们能够减少一个文件
# oscome.yamlname: oscomemode: debuglog: level: debug
咱们能够应用 viper 读取这个配置文件,并且配合 fsnotify 监听配置,监听的益处就在于运行中配置简直实时失效,无需重启服务。
package day002import ( "fmt" "testing" "time" "github.com/fsnotify/fsnotify" "github.com/spf13/viper")func read() { viper.AddConfigPath(".") // 还能够在工作目录中查找配置 viper.SetConfigFile("oscome.yaml") // 指定配置文件门路(这一句跟上面两行合起来表白的是一个意思) // viper.SetConfigName("oscome") // 配置文件名称(无扩展名) // viper.SetConfigType("yaml") // 如果配置文件的名称中没有扩展名,则须要配置此项 err := viper.ReadInConfig() // 配置文件 if err != nil { panic(fmt.Errorf("Fatal error config file: %s \n", err)) }}func TestViper(t *testing.T) { read() t.Log(viper.GetString("name")) t.Log(viper.GetString("log.level"))}func TestWatch(t *testing.T) { read() t.Log(viper.GetString("name")) viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println("Config file changed:", e.Name) read() t.Log(viper.GetString("name")) t.Log(viper.GetString("log.level")) }) time.Sleep(100 * time.Second)}
成果如图:
实例代码
https://github.com/oscome/god...
tips
- viper 读取优先级是 Set 办法、flag、env、config、k/v、默认值
- viper 配置键不辨别大小写
- 除了yaml,还反对 json、toml、ini等,新版本还反对 etcd,如果感兴趣,能够尝试一下。
源码解读
绝对 cast 而言,viper 的代码要略微简单一点,咱们重点看 ReadInConfig 和 WatchConfig。
ReadInConfig
func (v *Viper) ReadInConfig() error { v.logger.Info("attempting to read in config file") // 读取配置文件 filename, err := v.getConfigFile() if err != nil { return err } // 文件类型判断,反对 "json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini" if !stringInSlice(v.getConfigType(), SupportedExts) { return UnsupportedConfigError(v.getConfigType()) } v.logger.Debug("reading file", "file", filename) // 波及另一个库 afero,这里能够简略看成读取文件,返回 []byte 和 error file, err := afero.ReadFile(v.fs, filename) if err != nil { return err } config := make(map[string]interface{}) // 解析文件内容 err = v.unmarshalReader(bytes.NewReader(file), config) if err != nil { return err } v.config = config return nil}
WatchConfig
func (v *Viper) WatchConfig() { // 这里应用了 sync 包 initWG := sync.WaitGroup{} initWG.Add(1) go func() { // fsnotify.NewWatcher() watcher, err := newWatcher() if err != nil { log.Fatal(err) } defer watcher.Close() filename, err := v.getConfigFile() if err != nil { log.Printf("error: %v\n", err) initWG.Done() return } configFile := filepath.Clean(filename) // 获取配置文件所在目录,以便于后续监听 configDir, _ := filepath.Split(configFile) realConfigFile, _ := filepath.EvalSymlinks(filename) eventsWG := sync.WaitGroup{} eventsWG.Add(1) go func() { for { select { case event, ok := <-watcher.Events: // watcher.Events 这个通道敞开,留神 channel 两个返回值的写法哦 if !ok { eventsWG.Done() return } currentConfigFile, _ := filepath.EvalSymlinks(filename) // 这里关怀两种状况 // 1. 配置文件创立或批改 // 2. 配置文件的实在门路产生了变动(例如:k8s ConfigMap replacement) const writeOrCreateMask = fsnotify.Write | fsnotify.Create if (filepath.Clean(event.Name) == configFile && event.Op&writeOrCreateMask != 0) || (currentConfigFile != "" && currentConfigFile != realConfigFile) { realConfigFile = currentConfigFile err := v.ReadInConfig() if err != nil { log.Printf("error reading config file: %v\n", err) } // 调用自定义的 OnConfigChange if v.onConfigChange != nil { v.onConfigChange(event) } } else if filepath.Clean(event.Name) == configFile && event.Op&fsnotify.Remove != 0 { eventsWG.Done() return } case err, ok := <-watcher.Errors: if ok { // 'Errors' channel is not closed log.Printf("watcher error: %v\n", err) } eventsWG.Done() return } } }() // 监听整个目录 watcher.Add(configDir) initWG.Done() // 期待上面一个 go routine 实现 eventsWG.Wait() }() // 确保下面的 go routine 在返回之前齐全完结 initWG.Wait()}
PS: viper 外面的代码还是有很多值得参考的,我感觉感兴趣能够深刻看一下。