1. Viper 是什么?
Viper 是 Go 语言的一个完整的配置解决方案,它拥有以下特性:
- 支持多种配置格式:JSON, TOML, YAML, HCL, envfile 以及 Java properties 格式。
- 支持从多种来源读取配置:配置文件、环境变量、命令行标志、远程配置系统(如 etcd 或 Consul)。
- 实时监控和重新读取配置文件:配置文件修改后,无需重启应用即可生效。
-
易于读取值:提供
Get
,GetString
,GetInt
,GetBool
等方法,方便地获取不同类型配置值。 - 设置默认值:可以为配置项设置默认值,防止未配置时出现错误。
- 别名系统:可以为配置键设置别名,保持向后兼容。
2. 安装
使用 go get 命令安装 Viper:
go get github.com/spf13/viper
然后在你的代码中导入它:
import "github.com/spf13/viper"
3. 基本用法
3.1 读取配置文件
假设我们有一个 config.yaml
文件:
# config.yaml
server:
port: 8080
host: "localhost"
database:
name: "myapp"
user: "admin"
password: "secret"
max_connections: 100
读取这个配置文件的代码如下:
package main
import (
"fmt"
"log"
"github.com/spf13/viper"
)
func main() {
// 1. 设置配置文件名(不含扩展名)
viper.SetConfigName("config")
// 2. 设置配置文件类型
viper.SetConfigType("yaml")
// 3. 添加查找配置文件的路径(可以添加多个,按顺序查找)
viper.AddConfigPath(".") // 在当前目录查找
viper.AddConfigPath("/etc/myapp/") // 在 /etc/myapp/ 目录查找
// 4. 读取配置文件
err := viper.ReadInConfig()
if err != nil {
log.Fatalf("Fatal error config file: %s \n", err)
}
// 5. 获取配置值
port := viper.GetInt("server.port") // 使用 . 来访问嵌套键
host := viper.GetString("server.host")
dbName := viper.GetString("database.name")
fmt.Printf("Server is running on %s:%d\n", host, port)
fmt.Printf("Database name: %s\n", dbName)
// 你也可以直接使用 Get,然后进行类型断言
maxConn := viper.Get("database.max_connections")
fmt.Printf("Max connections: %v (type: %T)\n", maxConn, maxConn)
}
3.2 设置默认值
在 ReadInConfig
之前,可以为某些配置项设置默认值。如果配置文件中没有这些值,将使用默认值。
func main() {
// ... (设置 ConfigName, Type, Path 的代码同上)
// 设置默认值
viper.SetDefault("server.port", 3000)
viper.SetDefault("database.max_connections", 50)
err := viper.ReadInConfig()
// ... (错误处理)
// 如果 config.yaml 中没有 server.port,这里将返回 3000
port := viper.GetInt("server.port")
fmt.Println(port)
}
3.3 监听和重新读取配置文件
Viper 支持让应用程序在运行时监听配置文件的更改,并在文件被修改后自动重新加载配置。
func main() {
// ... (基本的读取配置代码)
// 开始监听配置变化
viper.WatchConfig()
// 注册一个回调函数,当配置变化时执行
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
// 重新获取配置
newPort := viper.GetInt("server.port")
fmt.Println("New port is:", newPort)
})
// 为了演示,让主协程不要退出(例如在 HTTP 服务器中)
select {}
}
3.4 读取环境变量
Viper 可以自动读取与环境变量匹配的配置。它会检查所有已经设置的键(包括嵌套键),将 .
和 -
替换为 _
并转换为大写,然后去环境变量中查找。
例如,键 database.name
会对应环境变量 DATABASE_NAME
。
func main() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
// 启用读取环境变量
viper.AutomaticEnv()
err := viper.ReadInConfig()
if err != nil {
log.Fatalf("Fatal error config file: %s \n", err)
}
// 获取值:优先从配置文件读取,如果没找到,则查找环境变量 DATABASE_USER
user := viper.GetString("database.user")
fmt.Println("Database User:", user)
// 你也可以显式地绑定一个环境变量到一个配置键(可以起别名)
viper.BindEnv("go_version", "GO_VERSION") // 将环境变量 GO_VERSION 绑定到配置键 ‘go_version‘
version := viper.GetString("go_version") // 从环境变量 GO_VERSION 获取
fmt.Println("Go Version:", version)
}
运行程序时,可以这样覆盖配置:
export DATABASE_USER=prod_admin
go run main.go
3.5 读取命令行参数
Viper 可以配合 pflag
或 Go 标准库的 flag
包来读取命令行参数。
package main
import (
"flag"
"fmt"
"log"
"github.com/spf13/viper"
)
func main() {
// 1. 定义命令行标志(也可以使用 pflag)
portFlag := flag.Int("port", 0, "server port") // 默认值 0 表示未设置
flag.Parse()
// 2. 将这些标志绑定到 Viper
viper.BindPFlag("server.port", flag.Lookup("port"))
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.SetDefault("server.port", 3000) // 设置默认值
err := viper.ReadInConfig()
if err != nil {
log.Fatal(err)
}
// 获取端口:优先级 命令行 > 配置文件 > 默认值
finalPort := viper.GetInt("server.port")
fmt.Printf("Final server port: %d\n", finalPort)
}
运行程序:
go run main.go -port 9090
4. 高级用法:反序列化到结构体
手动用 Get
获取每一个值很繁琐。Viper 提供了 Unmarshal
方法,可以将配置直接反序列化到一个结构体中,这是最推荐的方式。
package main
import (
"fmt"
"log"
"github.com/spf13/viper"
)
// 定义与配置对应的结构体
type Config struct {
Server ServerConfig
Database DatabaseConfig
}
type ServerConfig struct {
Port int `mapstructure:"port"`
Host string `mapstructure:"host"`
}
type DatabaseConfig struct {
Name string `mapstructure:"name"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
MaxConn int `mapstructure:"max_connections"`
}
func main() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
log.Fatal(err)
}
var config Config
// 将整个配置反序列化到 config 结构体
err = viper.Unmarshal(&config)
if err != nil {
log.Fatal("Unable to decode into struct, ", err)
}
fmt.Printf("Server: %s:%d\n", config.Server.Host, config.Server.Port)
fmt.Printf("DB: %s, User: %s\n", config.Database.Name, config.Database.User)
}
注意:这里使用的是 mapstructure
标签,而不是 json
标签。因为 Viper 在底层使用 mapstructure
库进行反序列化。
5. 总结:常用操作步骤
-
初始化:设置
ConfigName
,ConfigType
,AddConfigPath
。 -
设置默认值(可选):
SetDefault
。 -
读取:
ReadInConfig()
。 -
集成其他来源(可选):
- 环境变量:
AutomaticEnv()
,BindEnv
。 - 命令行参数:
BindPFlag
。
- 环境变量:
-
获取配置:
- 直接获取:
Get
,GetString
,GetInt
等。 -
推荐 反序列化到结构体:
Unmarshal
。
- 直接获取:
-
监听变化(可选):
WatchConfig
,OnConfigChange
。
Viper 通过提供一个统一、强大的接口,极大地简化了 Go 应用程序的配置管理工作。