6.1. Viper 配置管理
6.1.1. Viper 概述
Viper 是 Go 应用程序的完整配置解决方案,支持:
JSON、YAML、TOML、HCL 等配置文件
环境变量
命令行标志
远程配置系统(etcd、Consul)
配置热重载
6.1.2. 安装
go get github.com/spf13/viper
6.1.3. 基本用法
6.1.3.1. 读取配置文件
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigName("config") // 配置文件名(不带扩展名)
viper.SetConfigType("yaml") // 配置文件类型
viper.AddConfigPath(".") // 查找路径
viper.AddConfigPath("./config") // 可以添加多个路径
if err := viper.ReadInConfig(); err != nil {
panic(fmt.Errorf("fatal error config file: %w", err))
}
// 读取配置
fmt.Println(viper.GetString("database.host"))
fmt.Println(viper.GetInt("database.port"))
}
6.1.3.2. 配置文件示例
# config.yaml
app:
name: myapp
port: 8080
debug: true
database:
host: localhost
port: 3306
user: root
password: secret
dbname: mydb
redis:
host: localhost
port: 6379
6.1.4. 配置优先级
Viper 按以下顺序(从高到低)读取配置:
显式调用
Set命令行标志
环境变量
配置文件
远程配置
默认值
// 设置默认值
viper.SetDefault("database.port", 3306)
// 显式设置(最高优先级)
viper.Set("database.host", "production-db")
6.1.5. 环境变量
6.1.5.1. 自动绑定环境变量
// 自动绑定所有环境变量
viper.AutomaticEnv()
// 设置环境变量前缀
viper.SetEnvPrefix("MYAPP") // 将读取 MYAPP_* 环境变量
// 将 . 替换为 _
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// 现在可以通过 MYAPP_DATABASE_HOST 设置 database.host
6.1.5.2. 手动绑定
viper.BindEnv("database.host", "DB_HOST")
6.1.6. 与 Cobra 集成
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "My application",
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().String("config", "", "config file")
rootCmd.PersistentFlags().Int("port", 8080, "server port")
viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port"))
}
func initConfig() {
if cfgFile := viper.GetString("config"); cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
viper.SetConfigName("config")
viper.AddConfigPath(".")
}
viper.AutomaticEnv()
viper.ReadInConfig()
}
6.1.7. 反序列化到结构体
type Config struct {
App AppConfig `mapstructure:"app"`
Database DatabaseConfig `mapstructure:"database"`
}
type AppConfig struct {
Name string `mapstructure:"name"`
Port int `mapstructure:"port"`
Debug bool `mapstructure:"debug"`
}
type DatabaseConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
DBName string `mapstructure:"dbname"`
}
func loadConfig() (*Config, error) {
var config Config
if err := viper.Unmarshal(&config); err != nil {
return nil, err
}
return &config, nil
}
6.1.8. 配置热重载
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
// 重新加载配置
reloadConfig()
})
6.1.9. ⚠️ 常见陷阱
6.1.9.1. 陷阱 1:并发访问不安全
// ❌ 并发读写 viper 不安全
go func() {
for {
viper.Set("key", value)
}
}()
go func() {
for {
_ = viper.GetString("key")
}
}()
// ✅ 使用全局配置结构体
var (
config *Config
configLock sync.RWMutex
)
func GetConfig() *Config {
configLock.RLock()
defer configLock.RUnlock()
return config
}
func reloadConfig() {
newConfig := &Config{}
viper.Unmarshal(newConfig)
configLock.Lock()
config = newConfig
configLock.Unlock()
}
6.1.9.2. 陷阱 2:大小写敏感
// Viper 键名不区分大小写
viper.Set("myKey", "value")
fmt.Println(viper.GetString("MYKEY")) // value
fmt.Println(viper.GetString("mykey")) // value
6.1.9.3. 陷阱 3:环境变量命名
// 配置: database.host
// 环境变量需要使用前缀和替换规则
viper.SetEnvPrefix("APP")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
// 设置: APP_DATABASE_HOST=localhost
6.1.10. 最佳实践
使用结构体:将配置反序列化到结构体,类型安全
设置默认值:确保配置缺失时有合理默认值
验证配置:在启动时验证必要配置
分离敏感配置:密码等敏感信息使用环境变量