1. viper 特点

viperGo
JSONTOMLYAMLHCLenvfileJava
viper
viper.Set()flagetcd/consul
viper

2. 安装

go get github.com/spf13/viper
3. 将配置注册到 viper

3.1 创建默认值

viper.SetDefault("Name", "wohu")
viper.SetDefault("Gender", "male")
viper.SetDefault("City", map[string]string{"country": "China", "Province": "Beijing"})

3.2 从配置文件读取值

viper
viper.SetConfigName("config") // 配置文件名字,注意没有扩展名
viper.SetConfigType("yaml") // 如果配置文件的名称中没有包含扩展名,那么该字段是必需的
viper.AddConfigPath("/etc/appname/")   // 配置文件的路径
viper.AddConfigPath("$HOME/.appname")  // 多次调用添加多个配置文件的路径
viper.AddConfigPath(".")               // 在当前工作目录寻找配置文件
err := viper.ReadInConfig() // 查找并读取配置文件
if err != nil { 
	panic(fmt.Errorf("Fatal error config file: %w \n", err))
}

3.3 将 viper值保存到配置文件

viper.WriteConfig()
viper.AddConfigPath()viper.SetConfigName()
viper.SafeWriteConfig() // 与第一个区别是不会覆盖当前已经存在的文件
viper.WriteConfigAs("/path/to/my/.config") // 会覆盖当前已经存在的文件
viper.SafeWriteConfigAs("/path/to/my/.config")  // 不会覆盖当前已经存在的文件
viper.SafeWriteConfigAs("/path/to/my/.other_config")

3.4 监测并热加载配置文件

viperWatchConfig()configPaths
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
	fmt.Println("Config file changed:", e.Name)
})

3.5 从 io.Reader 读取配置

viper.SetConfigType("yaml") // or viper.SetConfigType("YAML")

// any approach to require this configuration into your program.
var yamlExample = []byte(`
Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:
  jacket: leather
  trousers: denim
age: 35
eyes : brown
beard: true
`)

viper.ReadConfig(bytes.NewBuffer(yamlExample))

viper.Get("name") // this would be "steve"
4. 从 viper 读取配置

4.1 读取单个值方法

viper
Get(key string) : interface{}
GetBool(key string) : bool
GetFloat64(key string) : float64
GetInt(key string) : int
GetIntSlice(key string) : []int
GetString(key string) : string
GetStringMap(key string) : map[string]interface{}
GetStringMapString(key string) : map[string]string
GetStringSlice(key string) : []string
GetTime(key string) : time.Time
GetDuration(key string) : time.Duration
IsSet(key string) : bool
AllSettings() : map[string]interface{}
Get0IsSet()

4.1 读取嵌套的配置

{
    "host": {
        "address": "localhost",
        "port": 5799
    },
    "datastore": {
        "metric": {
            "host": "127.0.0.1",
            "port": 3099
        },
        "warehouse": {
            "host": "198.0.0.1",
            "port": 2112
        }
    }
}
viper.
GetString("datastore.metric.host") // (returns "127.0.0.1")
viper
{
    "host": {
        "address": "localhost",
        "ports": [
            5799,
            6029
        ]
    },
    "datastore": {
        "metric": {
            "host": "127.0.0.1",
            "port": 3099
        },
        "warehouse": {
            "host": "198.0.0.1",
            "port": 2112
        }
    }
}

GetInt("host.ports.1") // returns 6029

如果存在一个与划定的键路径相匹配的键,其值将被返回。例如。

{
    "datastore.metric.host": "0.0.0.0",
    "host": {
        "address": "localhost",
        "port": 5799
    },
    "datastore": {
        "metric": {
            "host": "127.0.0.1",
            "port": 3099
        },
        "warehouse": {
            "host": "198.0.0.1",
            "port": 2112
        }
    }
}

GetString("datastore.metric.host") // returns "0.0.0.0"

5. 使用示例

代码结构如下:

.
├── conf
│   └── config.yaml
├── config
│   └── config.go
├── go.mod
├── go.sum
├── main.go
└── README.md

2 directories, 6 files
config.yaml
name: demo
host: 127.0.0.1:3306
username: root
password: root         
config.go
package config

import (
	"log"

	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

type Config struct {
	Name     string
	Host     string
	Username string
	Password string
}

func Init() (*Config, error) {
	viper.AddConfigPath("conf")   // 设置配置文件路径
	viper.SetConfigName("config") // 设置配置文件名
	viper.SetConfigType("yaml")   // 设置配置文件类型格式为YAML

	// 初始化配置文件
	if err := viper.ReadInConfig(); err != nil { // viper解析配置文件
		return &Config{}, err
	}
	// 监控配置文件变化并热加载程序,即不重启程序进程就可以加载最新的配置
	viper.WatchConfig()
	viper.OnConfigChange(func(e fsnotify.Event) {
		log.Printf("Config file changed: %s", e.Name)
	})

	c := &Config{
		Name:     viper.GetString("name"),
		Host:     viper.GetString("host"),
		Username: viper.GetString("username"),
		Password: viper.GetString("password"),
	}

	return c, nil
}

main.go
package main

import (
	"fmt"
	"time"
	"webserver/config"

	"github.com/spf13/viper"
)

func main() {

	// init config
	_, err := config.Init()
	if err != nil {
		fmt.Println(err)
	}
	// 注意:只能在 init 之后再次通过 viper.Get 方法读取配置,否则不生效
	for {
		cfg := &config.Config{
			Name:     viper.GetString("name"),
			Host:     viper.GetString("host"),
			Username: viper.GetString("username"),
			Password: viper.GetString("password"),
		}
		fmt.Println(cfg.Name)
		time.Sleep(4 * time.Second)
	}

}

main.go
$ go run main.go 
123
123
2021/12/03 14:05:45 Config file changed: /home/wohu/goProject/webserver/conf/config.yaml
2021/12/03 14:05:45 Config file changed: /home/wohu/goProject/webserver/conf/config.yaml
345
345
345
2021/12/03 14:05:56 Config file changed: /home/wohu/goProject/webserver/conf/config.yaml
2021/12/03 14:05:56 Config file changed: /home/wohu/goProject/webserver/conf/config.yaml
demo
demo