引言

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

  1. viper 读取优先级是 Set 办法、flag、env、config、k/v、默认值
  2. viper 配置键不辨别大小写
  3. 除了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 外面的代码还是有很多值得参考的,我感觉感兴趣能够深刻看一下。