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