参考资料
- https://studygolang.com/articles/22483
- https://www.liwenzhou.com/posts/Go/viper_tutorial/ -李文周博客
介绍
在开发过程中,像数据库信息、邮件配置和其他的第三方服务密钥等这些固定的信息都会写在配置文件中,而配置文件又有多种表现形式和格式,有 JSON, TOML, YAML各种格式,而且测试环境,开发环境和生产环境用的配置文件也不是同一份,使用Viper将帮助我们更好的对配置文件进行管理。
什么是viper?
Viper是适用于Go应用程序(包括Twelve-Factor App)的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式。它支持以下特性:
- 设置默认值
- 从JSON、TOML、YAML、HCL、envfile和Java properties格式的配置文件读取配置信息
- 实时监控和重新读取配置文件(可选)
- 从环境变量中读取
- 从远程配置系统(etcd或Consul)读取并监控配置变化
- 从命令行参数读取配置
- 从buffer读取配置
- 显式配置值
viper支持的操作:
在构建现代应用程序时,你无需担心配置文件格式;你想要专注于构建出色的软件。Viper的出现就是为了在这方面帮助你的。 Viper能够为你执行下列操作:
- 查找、加载和反序列化JSON、TOML、YAML、HCL、INI、envfile和Java properties格式的配置文件。
- 提供一种机制为你的不同配置选项设置默认值。
- 提供一种机制来通过命令行参数覆盖指定选项的值。
- 提供别名系统,以便在不破坏现有代码的情况下轻松重命名参数。
- 当用户提供了与默认值相同的命令行或配置文件时,可以很容易地分辨出它们之间的区别。
安装
优先级
Viper会按照下面的优先级。每个项目的优先级都高于它下面的项目:
- 显示调用Set设置值
- 命令行参数(flag)
- 环境变量
- 配置文件
- key/value存储
- 默认值
重要: 目前Viper配置的键(Key)是大小写不敏感的。目前正在讨论是否将这一选项设为可选。
向viper存入值
设置默认值
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
读取配置文件
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 {
// 配置文件被找到,但产生了另外的错误
}
}
// 配置文件找到并成功解析
写入配置文件
// 将当前配置写入“viper.AddConfigPath()”和“viper.SetConfigName”设置的预定义路径
viper.WriteConfig()
// 如果没有预定义的路径,则报错。如果存在,将不会覆盖当前的配置文件。
viper.SafeWriteConfig()
// 将当前的viper配置写入给定的文件路径。将覆盖给定的文件(如果它存在的话)
viper.WriteConfigAs("/path/to/my/.config")
// 因为该配置文件写入过,所以会报错
viper.SafeWriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.other_config")
监控配置文件
func main() {
// 设置配置文件读取路径
viper.SetConfigFile("./config.yaml")
// 监控配置文件
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
// 配置文件发生变更之后会调用的回调函数
fmt.Println("Config file changed:", e.Name)
})
r := gin.Default()
r.GET("/version", func(c *gin.Context) {
c.String(200, viper.GetString("version"))
})
r.Run()
}
从viper获取值
在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()方法。
访问嵌套的键
如:
{
"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") // (返回 "127.0.0.1")
提取子树
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
反序列化
type config struct {
Port int
Name string
PathMap string `mapstructure:"path_map"`
}
var C config
err := viper.Unmarshal(&C)
if err != nil {
t.Fatalf("unable to decode into struct, %v", err)
}
序列化为字符串
import (
yaml "gopkg.in/yaml.v2"
// ...
)
func yamlStringSettings() string {
c := viper.AllSettings()
bs, err := yaml.Marshal(c)
if err != nil {
log.Fatalf("unable to marshal config to YAML: %v", err)
}
return string(bs)
}
viper实例
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
func main() {
// 指定配置文件
viper.SetConfigFile("./config.yaml")
// 读取(检测)配置信息
if err := viper.ReadInConfig(); 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)
}
}