golang_Viper配置管理器

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")
相关推荐
java_cj1 小时前
Elasticsearch索引管理完全指南:从基础API到ILM生命周期管理
大数据·后端·elasticsearch·性能优化
geovindu1 小时前
go: Broadcast Pattern
开发语言·后端·设计模式·golang·广播模式
Alson_Code2 小时前
Spring AI-1.1.0
java·人工智能·后端·spring·ai编程
~|Bernard|2 小时前
关于go语言中二维切片的append操作陷阱
开发语言·后端·golang
李昊哲小课2 小时前
Spring Boot 4.0.6 全栈教程案例
spring boot·后端
千云2 小时前
100w大表0停机回滚:我们为什么放弃Undo Log,选择表名切换?
数据库·后端·mysql
云恒要逆袭3 小时前
Hello World背后的秘密:Java程序是这样运行的
java·后端·程序员
蝎子莱莱爱打怪3 小时前
XZLL-IM干货系列 01|万字拆解分布式 IM 架构:7 个微服务 + 自研 Flutter SDK
java·后端·面试
Elaine3363 小时前
基于Django框架的静态个人名片网站设计
后端·python·django·mvt