viper的用法

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. 总结:常用操作步骤

  1. 初始化:设置 ConfigName, ConfigType, AddConfigPath
  2. 设置默认值(可选):SetDefault
  3. 读取ReadInConfig()
  4. 集成其他来源(可选):
    • 环境变量:AutomaticEnv(), BindEnv
    • 命令行参数:BindPFlag
  5. 获取配置
    • 直接获取:Get, GetString, GetInt 等。
    • 推荐 反序列化到结构体:Unmarshal
  6. 监听变化(可选):WatchConfig, OnConfigChange

Viper 通过提供一个统一、强大的接口,极大地简化了 Go 应用程序的配置管理工作。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容