乐趣区

关于go:go-酷之viper-配置读取

引言

viper 是一个用于读取配置文件的库。如果你须要读取配置文件,那么 viper 足够好用。

我的项目地址

我的项目地址:https://github.com/spf13/viper [star:20.7k]

应用场景

  • 读取配置

装置

go get github.com/spf13/viper

罕用办法

  • SetConfigFile 定义配置文件
  • ReadInConfig 读取配置文件
  • GetString 获取某个 key 的配置
  • WatchConfig 监听配置
  • OnConfigChange 定义配置扭转对应的操作

例子

咱们能够减少一个文件

# oscome.yaml
name: oscome
mode: debug
log:
  level: debug

咱们能够应用 viper 读取这个配置文件,并且配合 fsnotify 监听配置,监听的益处就在于运行中配置简直实时失效,无需重启服务。

package day002

import (
    "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 外面的代码还是有很多值得参考的,我感觉感兴趣能够深刻看一下。


退出移动版