什么是 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
}