Bootstrap

[Golang] Viper原理以及详细使用案例

在这里插入图片描述

什么是 Viper?

介绍:用于处理配置文件中解析和读取配置文件
优点:支持多种配置格式,json,yaml 等等
作用:配置文件解析和读取 默认值支持 环境变量支持 命令行标志支持 配置监听和热加载

基础配置

引入依赖:

go get github.com/spf13/viper

读取文件方法:

    viper.SetConfigFile("haha")      //配置文件名,不需要后缀
 	viper.SetConfigType("yaml")      //设置配置文件格式
	viper.AddConfigPath("../config") //查找路径
 	err := viper.ReadInConfig()      //读取配置文件

读取文件方法二:

 	viper.SetConfigFile("./config/config.yaml")
    err := viper.ReadInConfig()

动态监听原理分析:

//监听配置文件
viper.WatchConfig()
//监听是否更改配置文件
viper.OnConfigChange(func(e fsnotify.Event) {
   fmt.Println("配置文件被人修改了...")
   err := viper.Unmarshal(&Conf)
   if err != nil {
      panic(fmt.Errorf("配置文件修改以后,报错啦,err:%v", err))
   }
})


监听原理分析

分析WatchConfig()方法
// 监听文件变化
func (v *Viper) WatchConfig() {
    //开启一个协程  相当于开启一个任务
   initWG := sync.WaitGroup{}
   initWG.Add(1)
   go func() {
       //创建一个文件监听器
      watcher, err := newWatcher()
      if err != nil {
         v.logger.Error(fmt.Sprintf("failed to create watcher: %s", err))
         os.Exit(1)
      }
      defer watcher.Close()
      // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way
         //获得配置文件名
       filename, err := v.getConfigFile()
      if err != nil {
         v.logger.Error(fmt.Sprintf("get config file: %s", 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:
               if !ok { // 'Events' channel is closed
                  eventsWG.Done()
                  return
               }
               currentConfigFile, _ := filepath.EvalSymlinks(filename)
               // we only care about the config file with the following cases:
               // 1 - if the config file was modified or created
               // 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement)
               //判断文件是否修改,创建 配置文件的真实路径是否发生变化
               if (filepath.Clean(event.Name) == configFile &&
                  (event.Has(fsnotify.Write) || event.Has(fsnotify.Create))) ||
                  (currentConfigFile != "" && currentConfigFile != realConfigFile) {
                  realConfigFile = currentConfigFile
                  //变化之后 重新进行文件读取
                  err := v.ReadInConfig()
                  if err != nil {
                     v.logger.Error(fmt.Sprintf("read config file: %s", err))
                  }
                  //调用回调函数去重新将内容写进结构体中
                  if v.onConfigChange != nil {
                     v.onConfigChange(event)
                  }
               } else if filepath.Clean(event.Name) == configFile && event.Has(fsnotify.Remove) {
                  eventsWG.Done()
                  return
               }

            case err, ok := <-watcher.Errors:
               if ok { // 'Errors' channel is not closed
                  v.logger.Error(fmt.Sprintf("watcher error: %s", err))
               }
               eventsWG.Done()
               return
            }
         }
      }()
      //如果发生文件找不到或者监听过程中出错就会退出内外两层协程,然后监听停止
      watcher.Add(configDir)
      initWG.Done()   // done initializing the watch in this go routine, so the parent routine can move on...
      eventsWG.Wait() // now, wait for event loop to end in this go-routine...
   }()
   initWG.Wait() // make sure that the go routine above fully ended before returning
}

同时通过mapstructure将配置文件中的信息(键值对)映射到结构体中,实现随时拿取

Config.yaml文件配置

mode: "dev"
port: 8080

log:
  level: "debug"
  filename: "./log/logfile.log"
  max_size: 1000
  max_age: 3600
  max_backups: 5

mysql:
  host: localhost
  port: 3306
  user: root
  password: root
  db: library
  max_open_conns: 100
  max_idle_conns: 20

redis:
  host: 127.0.0.1
  port: 6379
  db: 0

Viper文件配置

package config

import (
	"fmt"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

var Conf = new(LibraryConfig)

type MysqlConfig struct {
	Host         string `mapstructure:"host"`
	User         string `mapstructure:"user"`
	Password     string `mapstructure:"password"`
	DB           string `mapstructure:"db"`
	Port         int    `mapstructure:"port"`
	MaxOpenConns int    `mapstructure:"max_open_conns"`
	MaxIdleConns int    `mapstructure:"max_idle_conns"`
}
type RedisConfig struct {
	Host         string `mapstructure:"host"`
	Port         int    `mapstructure:"port"`
	DB           int    `mapstructure:"db"`
	Password     string `mapstructure:"password"`
	PollSize     int    `mapstructure:"PollSize"`
	MinIdleConns int    `mapstructure:"min_idle_cons"`
}
type LogConfig struct {
	Level      string `mapstructure:"level"`
	FileName   string `mapstructure:"filename"`
	MaxSize    int    `mapstructure:"max_size"`
	MaxAge     int    `mapstructure:"max_age"`
	MaxBackUps int    `mapstructure:"max_backups"`
}

type LibraryConfig struct {
	Mode         string `mapstructure:"mode"`
	Port         int    `mapstructure:"port"`
	*LogConfig   `mapstructure:"log"`
	*MysqlConfig `mapstructure:"mysql"`
	*RedisConfig `mapstructure:"redis"`
}

func Init() error {

	//加载配置文件位置
	viper.SetConfigFile("./config/config.yaml")
	//监听配置文件
	viper.WatchConfig()
	//监听是否更改配置文件
	viper.OnConfigChange(func(e fsnotify.Event) {
		fmt.Println("配置文件被人修改了...")
		err := viper.Unmarshal(&Conf)
		if err != nil {
			panic(fmt.Errorf("配置文件修改以后,报错啦,err:%v", err))
		}
	})
	// 读取配置文件内容
	err := viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("ReadInConfig failed,err:%v", err))
	}
	//将配置文件内容写入到Conf结构体
	if err1 := viper.Unmarshal(&Conf); err1 != nil {
		panic(fmt.Errorf("unmarshal data to Conf failed,err:%v", err))
	}
	return nil
}

;