← GORM | Zap →

Viper - Go 配置管理神器

Viper 是 Go 最流行的配置管理库,支持多种配置格式、环境变量、命令行参数、远程配置中心等。掌握 Viper 是开发生产级 Go 应用的基础。

📌 核心概念

📄

多格式

JSON/YAML/TOML

.yaml
🔄

热重载

配置变更监听

WatchConfig
🌐

远程配置

etcd/Consul

Remote Config
⚙️

优先级

多来源合并

Priority

快速开始

📝 基础使用

package main

import (
    "fmt"
    "github.com/spf13/viper"
)

func main() {
    // 设置配置文件名
    viper.SetConfigName("config")
    // 设置文件类型
    viper.SetConfigType("yaml")
    // 设置搜索路径
    viper.AddConfigPath("./configs")
    viper.AddConfigPath("/etc/myapp")
    
    // 读取配置
    if err := viper.ReadInConfig(); err != nil {
        panic(fmt.Sprintf("Config error: %v", err))
    }
    
    // 读取配置值
    host := viper.GetString("server.host")
    port := viper.GetInt("server.port")
    debug := viper.GetBool("app.debug")
    
    fmt.Printf("Server: %s:%d, Debug: %v\n", host, port, debug)
}

💡 Viper 要点

  • 自动搜索: 在多个路径查找配置文件
  • 类型安全: GetString/GetInt/GetBool 等类型方法
  • 嵌套访问: 使用点号访问嵌套配置
  • 默认值: SetDefault 设置默认值

配置文件

📝 YAML 配置示例

# config.yaml
server:
  host: "0.0.0.0"
  port: 8080
  timeout: "30s"

database:
  driver: "mysql"
  dsn: "user:pass@tcp(localhost:3306)/db"
  max_idle: 10
  max_open: 100

redis:
  addr: "localhost:6379"
  password: ""
  db: 0

app:
  name: "MyApp"
  version: "1.0.0"
  debug: true
  log_level: "info"

features:
  - "feature1"
  - "feature2"
  - "feature3"

📝 读取配置到结构体

package main

import (
    "fmt"
    "github.com/spf13/viper"
)

// 配置结构体
type Config struct {
    Server   ServerConfig   `mapstructure:"server"`
    Database DatabaseConfig `mapstructure:"database"`
    Redis    RedisConfig    `mapstructure:"redis"`
    App      AppConfig      `mapstructure:"app"`
}

type ServerConfig struct {
    Host    string `mapstructure:"host"`
    Port    int    `mapstructure:"port"`
    Timeout string `mapstructure:"timeout"`
}

type DatabaseConfig struct {
    Driver  string `mapstructure:"driver"`
    DSN     string `mapstructure:"dsn"`
    MaxIdle int    `mapstructure:"max_idle"`
    MaxOpen int    `mapstructure:"max_open"`
}

type RedisConfig struct {
    Addr     string `mapstructure:"addr"`
    Password string `mapstructure:"password"`
    DB       int    `mapstructure:"db"`
}

type AppConfig struct {
    Name    string `mapstructure:"name"`
    Version string `mapstructure:"version"`
    Debug   bool   `mapstructure:"debug"`
    LogLevel string `mapstructure:"log_level"`
}

func main() {
    viper.SetConfigFile("./configs/config.yaml")
    viper.ReadInConfig()
    
    // 方式 1: 手动读取
    host := viper.GetString("server.host")
    port := viper.GetInt("server.port")
    
    // 方式 2: 解组到结构体
    var config Config
    viper.Unmarshal(&config)
    
    fmt.Printf("Server: %s:%d\n", config.Server.Host, config.Server.Port)
    fmt.Printf("Database: %s\n", config.Database.DSN)
    fmt.Printf("App: %s v%s\n", config.App.Name, config.App.Version)
}

配置优先级

📖 Viper 配置来源优先级

// Viper 配置优先级 (从高到低):
// 1. 显式调用 Set
// 2. 命令行参数 (flag)
// 3. 环境变量
// 4. 配置文件
// 5. 远程配置 (etcd/Consul)
// 6. 默认值

func main() {
    // 6. 设置默认值 (最低优先级)
    viper.SetDefault("server.host", "localhost")
    viper.SetDefault("server.port", 8080)
    
    // 4. 读取配置文件
    viper.SetConfigName("config")
    viper.ReadInConfig()
    
    // 3. 绑定环境变量
    viper.BindEnv("server.host", "SERVER_HOST")
    viper.BindEnv("server.port", "SERVER_PORT")
    viper.AutomaticEnv() // 自动绑定所有环境变量
    
    // 2. 绑定命令行参数
    var port int
    flag.IntVar(&port, "port", 0, "Server port")
    flag.Parse()
    viper.BindPFlag("server.port", flag.Lookup("port"))
    
    // 1. 显式设置 (最高优先级)
    viper.Set("server.host", "127.0.0.1")
    
    // 获取最终值
    host := viper.GetString("server.host")
}

配置热重载

📝 监听配置变更

package main

import (
    "fmt"
    "github.com/spf13/viper"
)

func main() {
    viper.SetConfigFile("./configs/config.yaml")
    viper.ReadInConfig()
    
    // 监听配置变更
    viper.WatchConfig()
    
    // 注册变更回调
    viper.OnConfigChange(func(e viper.ConfigEvent) {
        fmt.Printf("Config changed: %s\n", e.Key)
        
        // 重新加载配置到结构体
        var config Config
        viper.Unmarshal(&config)
        
        fmt.Printf("New config: %+v\n", config)
    })
    
    // 保持程序运行
    select {}
}

多环境配置

📝 环境分离配置

package main

import (
    "fmt"
    "os"
    "github.com/spf13/viper"
)

func LoadConfig(env string) (*Config, error) {
    // 根据环境加载不同配置
    configFile := fmt.Sprintf("./configs/config.%s.yaml", env)
    
    viper.SetConfigFile(configFile)
    
    if err := viper.ReadInConfig(); err != nil {
        return nil, err
    }
    
    var config Config
    viper.Unmarshal(&config)
    
    return &config, nil
}

func main() {
    // 从环境变量获取环境名
    env := os.Getenv("APP_ENV")
    if env == "" {
        env = "development"
    }
    
    config, err := LoadConfig(env)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Loaded config for %s environment\n", env)
}

远程配置中心

📝 从 etcd/Consul 读取配置

package main

import (
    "github.com/spf13/viper"
    "github.com/spf13/viper/remote"
)

func main() {
    // 从 etcd 读取配置
    err := viper.AddRemoteProvider(
        "etcd",
        "http://localhost:2379",
        "/config/app",
    )
    if err != nil {
        panic(err)
    }
    
    viper.SetConfigType("yaml")
    
    if err := viper.ReadRemoteConfig(); err != nil {
        panic(err)
    }
    
    // 从 Consul 读取配置
    // viper.AddRemoteProvider("consul", "http://localhost:8500", "/config/app")
    
    // 监听远程配置变更
    go func() {
        for {
            viper.WatchRemoteConfig()
            // 配置已变更,重新加载
            viper.ReadRemoteConfig()
        }
    }()
}

最佳实践

✅ Viper 使用建议

  • 配置结构体: 使用结构体定义配置,类型安全
  • 多环境分离: dev/test/prod 使用不同配置文件
  • 环境变量: 敏感信息使用环境变量
  • 默认值: 为关键配置设置默认值
  • 配置验证: 启动时验证配置有效性
  • 热重载: 生产环境启用配置热重载

🚨 常见陷阱

  • 并发安全: Viper 读取是并发安全的,但 Write 不是
  • 类型转换: GetInt/GetString 等会进行类型转换
  • 嵌套访问: 点号访问只支持一层 mapstructure
  • 环境变量: 环境变量名默认大写
  • 远程配置: 需要额外安装 remote 包