Go语言配置管理—Viper

前言

Viper是适用于Go应用程序的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式。

它支持以下特性:

  • 设置默认值
  • 从JSON、TOML、YAML、YML、HCL、envfile和 properties格式的配置文件读取配置信息
  • 实时监控和重新读取配置文件(可选)
  • 从环境变量中读取
  • 从远程配置系统(etcd或Consul)读取并监控配置变化
  • 从命令行参数读取配置
  • 从buffer读取配置
  • 显式配置值

1. Viper使用

1.1 特点

Viper能够为你执行下列操作:

  1. 查找、加载和反序列化JSON、TOML、YAML、HCL、INI、envfile和properties格式的配置文件。
  2. 提供一种机制为你的不同配置选项设置默认值。
  3. 提供一种机制来通过命令行参数覆盖指定选项的值。
  4. 提供别名系统,以便在不破坏现有代码的情况下轻松重命名参数。
  5. 当用户提供了与默认值相同的命令行或配置文件时,可以很容易地分辨出它们之间的区别。

Viper会按照下面的优先级。每个项目的优先级都高于它下面的项目:

  • 显示调用Set设置值
  • 命令行参数(flag)
  • 环境变量
  • 配置文件
  • key/value存储
  • 默认值

重要: 目前Viper配置的键(Key)是大小写不敏感的。

1.2 安装

go get github.com/spf13/viper

1.3 设置默认值

如果没有通过配置文件、环境变量、远程配置或命令行标志(flag)设置键,则默认值非常有用。

viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})

1.4 读取配置文件

Viper需要最少知道在哪里查找配置文件的配置。Viper支持JSON、TOML、YAML、HCL、envfile和Java properties格式的配置文件。Viper可以搜索多个路径,但目前单个Viper实例只支持单个配置文件。Viper不默认任何配置搜索路径,将默认决策留给应用程序。

下面是一个如何使用Viper搜索和读取配置文件的示例。不需要任何特定的路径,但是至少应该提供一个配置文件预期出现的路径。

viper.SetConfigFile("./config.yaml") // 指定配置文件路径
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: %s \n", err))
}

在加载配置文件出错时,可以像下面这样处理找不到配置文件的特定情况:

if err := viper.ReadInConfig(); err != nil {
    if _, ok := err.(viper.ConfigFileNotFoundError); ok {
        // 配置文件未找到错误;如果需要可以忽略
    } else {
        // 配置文件被找到,但产生了另外的错误
    }
}

// 配置文件找到并成功解析

1.5 监控并重新读取配置文件

Viper支持在运行时实时读取配置文件的功能。只需告诉viper实例watchConfig。可选地,你可以为Viper提供一个回调函数,以便在每次发生更改时运行。
确保在调用WatchConfig()之前添加了所有的配置路径。

viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
  // 配置文件发生变更之后会调用的回调函数
	fmt.Println("Config file changed:", e.Name)
})

1.6 覆盖设置

这些可能来自命令行标志,也可能来自你自己的应用程序逻辑。

viper.Set("Verbose", true)
viper.Set("LogFile", LogFile)

1.7 使用环境变量

Viper完全支持环境变量。这使Twelve-Factor App开箱即用。有五种方法可以帮助与ENV协作:

  • AutomaticEnv()
  • BindEnv(string…) : error
  • SetEnvPrefix(string)
  • SetEnvKeyReplacer(string…) *strings.Replacer
  • AllowEmptyEnv(bool)
    使用ENV变量时,务必要意识到Viper将ENV变量视为区分大小写。

1.8 使用Flags

Viper 具有绑定到标志的能力。具体来说,Viper支持Cobra库中使用的Pflag。

pflag.Int("flagname", 1234, "help message for flagname")

pflag.Parse()
viper.BindPFlags(pflag.CommandLine)

i := viper.GetInt("flagname") // 从viper而不是从pflag检索值

在 Viper 中使用 pflag 并不阻碍其他包中使用标准库中的 flag 包。pflag 包可以通过导入这些 flags 来处理flag包定义的flags。
这是通过调用pflag包提供的便利函数AddGoFlagSet()来实现的。

package main

import (
	"flag"
	"github.com/spf13/pflag"
)

func main() {

	// 使用标准库 "flag" 包
	flag.Int("flagname", 1234, "help message for flagname")

	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
	pflag.Parse()
	viper.BindPFlags(pflag.CommandLine)

	i := viper.GetInt("flagname") // 从 viper 检索值

	
}

2. 从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{}

每一个Get方法在找不到值的时候都会返回零值。为了检查给定的键是否存在,提供了IsSet()方法。

2.1 获取嵌套的键的值

{
    "host": {
        "address": "localhost",
        "port": 5799
    },
    "datastore": {
        "mysql": {
            "host": "127.0.0.1",
            "port": 3306
        }
    }
}

Viper可以通过传入.分隔的路径来访问嵌套字段

GetString("datastore.mysql.host") // (返回 "127.0.0.1")

2.2 获取子树值

app:
  cache1:
    max-items: 100
    item-size: 64
  cache2:
    max-items: 200
    item-size: 80

执行如下:

subv := viper.Sub("app.cache1")

subv现在就代表:

max-items: 100
item-size: 64

3 使用Viper示例

假设我们的项目现在有一个./conf/config.yaml配置文件,内容如下:

port: 6060
version: "v1.0.0"

3.1 直接使用viper管理配置

这里用一个demo演示如何在gin框架搭建的web项目中使用viper,使用viper加载配置文件中的信息,并在代码中直接使用viper.GetXXX()方法获取对应的配置值。

package main

import (
	"fmt"
	"net/http"
	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
)

func main() {
	viper.SetConfigFile("config.yaml") // 指定配置文件
	viper.AddConfigPath("./conf/")     // 指定查找配置文件的路径
	err := viper.ReadInConfig()        // 读取配置信息
	if err != nil {                    // 读取配置信息失败
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
	}

	// 监控配置文件变化
	viper.WatchConfig()

	r := gin.Default()
	// 访问/version的返回值会随配置文件的变化而变化
	r.GET("/version", func(c *gin.Context) {
		c.String(http.StatusOK, viper.GetString("version"))
	})

	if err := r.Run(
		fmt.Sprintf(":%d", viper.GetInt("port"))); err != nil {
		panic(err)
	}
}

3.2 使用结构体变量保存配置信息

在项目中定义与配置文件对应的结构体,viper加载完配置信息后使用结构体变量保存配置信息。

package main

import (
	"fmt"
	"net/http"
	"github.com/fsnotify/fsnotify"
	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
)

type Config struct {
	Port    int    `mapstructure:"port"`
	Version string `mapstructure:"version"`
}

var Conf = new(Config)

func main() {
	viper.SetConfigFile("./conf/config.yaml") // 指定配置文件路径
	err := viper.ReadInConfig()               // 读取配置信息
	if err != nil {                           // 读取配置信息失败
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
	}
	// 将读取的配置信息保存至全局变量Conf
	if err := viper.Unmarshal(Conf); err != nil {
		panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err))
	}
	// 监控配置文件变化
	viper.WatchConfig()
	// 注意!!!配置文件发生变化后要同步到全局变量Conf
	viper.OnConfigChange(func(in fsnotify.Event) {
		fmt.Println("配置文件被修改了")
		if err := viper.Unmarshal(Conf); err != nil {
			panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err))
		}
	})

	r := gin.Default()
	// 访问/version的返回值会随配置文件的变化而变化
	r.GET("/version", func(c *gin.Context) {
		c.String(http.StatusOK, Conf.Version)
	})

	if err := r.Run(fmt.Sprintf(":%d", Conf.Port)); err != nil {
		panic(err)
	}
}

参考文章:
https://github.com/spf13/viper/blob/master/README.md
https://www.liwenzhou.com/posts/Go/viper_tutorial/#autoid-1-0-0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值