Viper配置管理器
1、什么是viper?
yaml
https://www.liwenzhou.com/posts/Go/viper/#autoid-1-4-13
Viper是Go生态中最流行的配置管理框架之一,由:spf13维护。
Viper主要解决:
配置文件读取
环境变量读取
配置热更新
配置统一管理
多环境配置管理
远程配置中心
2、主要能力
yaml
- 配置文件读取
- 环境变量读取
- 默认值设置
- 配置热更新
- 命令行参数读取
- 远程配置中心(Etcd、Consul)
- 配置结构体映射
3、支持的格式
yaml
yaml
json
toml
ini
env
properties
4、viper优先级
yaml
Set() 显示调用Set设置值
↓
Flag 命令行参数flag
↓
Env 环境变量
↓
Config File 配置文件
↓
Remote Config 远程配置
↓
Default 默认值
5、安装
go
go get github.com/spf13/viper
go get github.com/fsnotify/fsnotify
6、目录结构示例
yaml
project
├── cmd
├── configs
│ ├── dev.yaml
│ ├── test.yaml
│ └── prod.yaml
├── internal
│ ├── config
│ │ ├── config.go
│ │ ├── model.go
│ │ └── watcher.go
│ └── server
└── main.go
7、YAML配置示例
yaml
app:
name: ops-platform
version: v1.0.0
server:
host: 0.0.0.0
port: 8080
mysql:
host: 127.0.0.1
port: 3306
user: root
password: 123456
dbname: ops
redis:
host: 127.0.0.1
port: 6379
jwt:
secret: abc123
kubernetes:
kubeconfig: ./configs/kubeconfig
casbin:
model: ./configs/model.conf
policy: ./configs/policy.csv
8、配置结构体映射
go
type Config struct {
App AppConfig `mapstructure:"app"`
Server ServerConfig `mapstructure:"server"`
MySQL MysqlConfig `mapstructure:"mysql"`
Redis RedisConfig `mapstructure:"redis"`
}
9、配置加载器封装
go
func InitConfig() error {
viper.SetConfigName("dev")
viper.SetConfigType("yaml")
viper.AddConfigPath("./configs")
if err := viper.ReadInConfig(); err != nil {
return err
}
return viper.Unmarshal(&Conf)
}
10、.env配置示例
go
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
REDIS_HOST=127.0.0.1
go
viper.SetConfigFile(".env")
viper.SetConfigType("env")
viper.ReadInConfig()
11、环境变量覆盖
go
viper.AutomaticEnv()
viper.BindEnv("mysql.host", "MYSQL_HOST")
12、多环境配置示例
go
go run main.go -env=dev
go run main.go -env=test
go run main.go -env=prod
go
env := flag.String("env","dev","运行环境")
flag.Parse()
viper.SetConfigName(*env)
12、配置热更新
go
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
if err := viper.Unmarshal(&Conf); err != nil {
log.Println(err)
}
})
13、viper示例
go
package main
import (
"fmt"
"github.com/spf13/viper"
)
/*
把值存进viper
建立默认值: 一个好的配置系统应该支持默认值。键不需要默认值,但如果没有通过配置文件、环境变量、远程配置或命令行编制设置键,则默认值非常有用
*/
func main() {
// 建立默认值示例
viper.SetDefault("fileDir", "./")
/*
读取配置文件
*/
//viper.SetConfigName("./config.yaml") //配置文件名称(有扩展名)
viper.SetConfigName("config") //配置文件名称(无扩展名)
viper.SetConfigType("yaml") //如果配置的名称中没有扩展名,需要配置此项
viper.AddConfigPath("/etc/appname/") //查找配置文件所在的路径
viper.AddConfigPath("$HOME/.appname") //多次调用以添加多个搜索路径
viper.AddConfigPath(".") //还可以在工作目录中查找配置
//从上述AddConfigPath中指定的路径依次去查找设定的config.yaml文件并读取该文件
err := viper.ReadInConfig()
if err != nil {
panic(fmt.Errorf("fatal error config file: %s\n", err))
}
//从上述AddConfigPath中指定的路径依次去查找设定的config.yaml文件并读取该文件,当找不到配置文件的特定情况可以做以下配置
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件未找到 的错误
} else {
//配置文件找到,但产生了另外的错误
}
}
// 配置文件找到并成功解析
}
14、Viper写入配置文件示例
Viper不仅可以读取配置文件,还可以将程序运行过程中修改的配置重新写回配置文件。
例如:
app:
name: ops-platform
version: v1.0.0
程序运行后修改:
viper.Set("app.version", "v2.0.0")
然后写回配置文件。
WriteConfig()
示例
config.yaml
app:
name: ops-platform
version: v1.0.0
代码:
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigFile("./config.yaml")
err := viper.ReadInConfig()
if err != nil {
panic(err)
}
// 修改配置
viper.Set("app.version", "v2.0.0")
// 写回原文件
err = viper.WriteConfig()
if err != nil {
panic(err)
}
fmt.Println("写入成功")
}
执行后:
config.yaml
app:
name: ops-platform
version: v2.0.0
特点
必须先指定配置文件
会覆盖原文件
文件不存在会报错
SafeWriteConfig()
示例
viper.SetConfigFile("./config.yaml")
err := viper.SafeWriteConfig()
if err != nil {
fmt.Println(err)
}
行为
如果文件不存在:
创建成功
如果文件已经存在:
Config File "config.yaml" Already Exists
特点
不会覆盖已有文件
适合初始化配置文件
WriteConfigAs()
将配置写入指定文件。
示例
package main
import (
"github.com/spf13/viper"
)
func main() {
viper.Set("name", "ops-platform")
viper.Set("version", "v1.0.0")
err := viper.WriteConfigAs("./new-config.yaml")
if err != nil {
panic(err)
}
}
生成:
name: ops-platform
version: v1.0.0
特点
指定路径
存在则覆盖
不存在则创建
SafeWriteConfigAs()
与 WriteConfigAs 类似。
区别:
不会覆盖已有文件
示例
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.Set("name", "ops-platform")
err := viper.SafeWriteConfigAs("./config.yaml")
if err != nil {
fmt.Println(err)
}
}
如果文件已存在:
Config File "./config.yaml" Already Exists
四个方法对比
| 方法 | 文件不存在 | 文件存在 |
|---|---|---|
| WriteConfig | 报错 | 覆盖 |
| SafeWriteConfig | 创建 | 报错 |
| WriteConfigAs | 创建 | 覆盖 |
| SafeWriteConfigAs | 创建 | 报错 |
15、覆盖设置
GO
覆盖设置
viper.Set("Verbose",true)
viper.Set("LogFile",LogFile)
16、注册别名
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
// 注册别名
viper.RegisterAlias("loud", "Verbose")
// 设置原始Key
viper.Set("Verbose", true)
fmt.Println(viper.GetBool("Verbose"))
fmt.Println(viper.GetBool("loud"))
}
输出:
true
true
通过别名设置
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.RegisterAlias("loud", "Verbose")
// 设置别名
viper.Set("loud", true)
fmt.Println(viper.GetBool("Verbose"))
fmt.Println(viper.GetBool("loud"))
}
输出:
true
true
可以理解为:
viper.Set("loud", true)
等价于:
viper.Set("Verbose", true)
Alias原理图
+------------+
| Verbose |
+------------+
▲
│
│ Alias
│
+------------+
| loud |
+------------+
无论操作哪个 Key:
viper.Set("Verbose", true)
或者
viper.Set("loud", true)
最终修改的都是同一个值。
实际项目示例
假设历史项目配置:
verbose: true
后来统一规范改成:
debug: true
为了兼容老配置:
viper.RegisterAlias("verbose", "debug")
这样:
viper.GetBool("verbose")
和
viper.GetBool("debug")
都能正常获取。
配置迁移场景(推荐)
旧版配置:
mysql_host: 127.0.0.1
新版配置:
mysql:
host: 127.0.0.1
为了兼容老版本:
viper.RegisterAlias(
"mysql_host",
"mysql.host",
)
读取:
fmt.Println(
viper.GetString("mysql_host"),
)
fmt.Println(
viper.GetString("mysql.host"),
)
输出:
127.0.0.1
127.0.0.1
17、远程key/value存储示例-未加密
go
etcd
viper.AddRemoteProvider("etcd","http://127.0.0.1:2379","/config/peizhi.json")//指定从etcd中读取配置,指定etcd的连接方法和存储在etcd中配置的路径
viper.SetConfigType("json") //告诉存的格式是json
err:=viper.ReadRemoteConfig()//加载配置
consul
在consul中有一个名为MY_CONSUL_KEY的key,它存储的是
{
"port": 8080,
"hostname": "test"
}
viper.AddRemoteProvider("consul","localhost:8500","MY_CONSUL_KEY")
viper.SetConfigType("json") //需要显示设置成json
err:=viper.ReadRemoteConfig()
fmt.Println(viper.Get("port")) //8080
fmt.Println(viper.Get("hostname")) //test
18、从viper取值示例
go
app:
name: ops-platform
version: v1.0.0
debug: true
server:
host: 0.0.0.0
port: 8080
timeout: 30s
admins:
- admin
- root
ids:
- 1
- 2
- 3
database:
metric:
host: 127.0.0.1
port: 3306
extra:
author: zhangsan
email: test@example.com
go
package main
import (
"fmt"
"time"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
panic(err)
}
// 1. Get
fmt.Println(viper.Get("app"))
// 2. GetBool
fmt.Println(viper.GetBool("app.debug"))
// 3. GetString
fmt.Println(viper.GetString("app.name"))
// 4. GetInt
fmt.Println(viper.GetInt("server.port"))
// 5. GetStringSlice
fmt.Println(viper.GetStringSlice("admins"))
// 6. GetIntSlice
fmt.Println(viper.GetIntSlice("ids"))
// 7. GetStringMap
fmt.Println(viper.GetStringMap("extra"))
// 8. GetStringMapString
fmt.Println(viper.GetStringMapString("extra"))
// 9. GetDuration
fmt.Println(viper.GetDuration("server.timeout"))
// 10. IsSet
fmt.Println(viper.IsSet("server.port"))
// 11. AllSettings
fmt.Println(viper.AllSettings())
// 12. 嵌套配置读取
fmt.Println(viper.GetString("database.metric.host"))
fmt.Println(viper.GetInt("database.metric.port"))
// 13. 不存在的配置
fmt.Println(viper.GetString("not_exist"))
// 14. 时间演示
t := time.Now()
fmt.Println(t)
}
Get()
返回任意类型(interface{})
value := viper.Get("app")
fmt.Printf("%T\n", value)
fmt.Println(value)
输出:
map[string]interface {}
map[debug:true name:ops-platform version:v1.0.0]
GetBool()
debug := viper.GetBool("app.debug")
fmt.Println(debug)
输出:
true
GetString()
name := viper.GetString("app.name")
fmt.Println(name)
输出:
ops-platform
GetInt()
port := viper.GetInt("server.port")
fmt.Println(port)
输出:
8080
GetStringSlice()
配置:
admins:
- admin
- root
代码:
admins := viper.GetStringSlice("admins")
fmt.Println(admins)
输出:
[admin root]
GetIntSlice()
配置:
ids:
- 1
- 2
- 3
代码:
ids := viper.GetIntSlice("ids")
fmt.Println(ids)
输出:
[1 2 3]
GetStringMap()
配置:
extra:
author: zhangsan
email: test@example.com
代码:
m := viper.GetStringMap("extra")
fmt.Printf("%T\n", m)
fmt.Println(m)
输出:
map[string]interface {}
map[author:zhangsan email:test@example.com]
GetStringMapString()
代码:
m := viper.GetStringMapString("extra")
fmt.Printf("%T\n", m)
fmt.Println(m)
输出:
map[string]string
map[author:zhangsan email:test@example.com]
GetDuration()
配置:
server:
timeout: 30s
代码:
timeout := viper.GetDuration("server.timeout")
fmt.Println(timeout)
输出:
30s
常用于:
http.Client
redis.Client
mysql连接超时
grpc超时
IsSet()
判断配置是否存在
fmt.Println(viper.IsSet("server.port"))
fmt.Println(viper.IsSet("server.port2"))
输出:
true
false
AllSettings()
获取所有配置
settings := viper.AllSettings()
fmt.Println(settings)
输出:
map[
app:map[debug:true name:ops-platform version:v1.0.0]
server:map[host:0.0.0.0 port:8080 timeout:30s]
]
常用于:
调试
配置导出
配置中心同步
嵌套配置读取
配置:
database:
metric:
host: 127.0.0.1
port: 3306
读取:
host := viper.GetString("database.metric.host")
port := viper.GetInt("database.metric.port")
fmt.Println(host)
fmt.Println(port)
输出:
127.0.0.1
3306
Viper支持通过 . 访问多层嵌套配置:
viper.GetString("database.metric.host")
viper.GetString("database.metric.user")
viper.GetString("database.metric.password")
等价于:
database:
metric:
host: xxx
user: xxx
password: xxx
零值问题(重点)
如果配置不存在:
fmt.Println(viper.GetString("abc"))
fmt.Println(viper.GetInt("abc"))
fmt.Println(viper.GetBool("abc"))
输出:
0
false
即:
GetString() -> ""
GetInt() -> 0
GetBool() -> false
GetFloat64() -> 0
GetDuration() -> 0
因此在生产环境推荐:
if !viper.IsSet("mysql.host") {
panic("mysql.host 配置不存在")
}
或者:
viper.SetDefault("mysql.host", "127.0.0.1")