Viper 配置管理
简介
Viper 是 Go 的完整配置解决方案,支持:
- JSON/YAML/TOML/HCL/Properties 等多种格式
- 环境变量
- 命令行标志
- 远程配置系统 (etcd/Consul)
- 配置热重载
快速开始
安装
bash
go get github.com/spf13/viper基础用法
go
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
// 设置配置文件名 (不含扩展名)
viper.SetConfigName("config")
// 设置配置文件类型
viper.SetConfigType("yaml")
// 添加搜索路径
viper.AddConfigPath(".")
viper.AddConfigPath("./configs")
viper.AddConfigPath("/etc/myapp")
// 读取配置
if err := viper.ReadInConfig(); err != nil {
panic(fmt.Errorf("读取配置失败:%w", err))
}
// 读取配置值
port := viper.GetInt("server.port")
host := viper.GetString("server.host")
debug := viper.GetBool("debug")
fmt.Printf("Server: %s:%d\n", host, port)
fmt.Printf("Debug: %v\n", debug)
}配置文件示例
YAML 配置
yaml
# config.yaml
app:
name: myapp
version: 1.0.0
env: development
server:
host: localhost
port: 8080
read_timeout: 30s
write_timeout: 30s
database:
driver: mysql
host: localhost
port: 3306
username: root
password: secret
database: myapp
max_open_conns: 100
max_idle_conns: 10
redis:
host: localhost
port: 6379
password: ""
db: 0
pool_size: 10
log:
level: info
format: json
output: stdout
# 嵌套配置
features:
- name: feature1
enabled: true
- name: feature2
enabled: false
# 多环境配置
environments:
development:
debug: true
production:
debug: falseJSON 配置
json
{
"app": {
"name": "myapp",
"version": "1.0.0"
},
"server": {
"host": "localhost",
"port": 8080
},
"database": {
"driver": "mysql",
"host": "localhost",
"port": 3306
}
}TOML 配置
toml
# config.toml
[app]
name = "myapp"
version = "1.0.0"
[server]
host = "localhost"
port = 8080
[database]
driver = "mysql"
host = "localhost"
port = 3306配置结构体
定义配置结构
go
package config
import (
"time"
)
// Config 应用配置
type Config struct {
App AppConfig `mapstructure:"app"`
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
Redis RedisConfig `mapstructure:"redis"`
Log LogConfig `mapstructure:"log"`
}
type AppConfig struct {
Name string `mapstructure:"name"`
Version string `mapstructure:"version"`
Env string `mapstructure:"env"`
}
type ServerConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
ReadTimeout time.Duration `mapstructure:"read_timeout"`
WriteTimeout time.Duration `mapstructure:"write_timeout"`
}
type DatabaseConfig struct {
Driver string `mapstructure:"driver"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Database string `mapstructure:"database"`
MaxOpenConns int `mapstructure:"max_open_conns"`
MaxIdleConns int `mapstructure:"max_idle_conns"`
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
}
type RedisConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Password string `mapstructure:"password"`
DB int `mapstructure:"db"`
PoolSize int `mapstructure:"pool_size"`
}
type LogConfig struct {
Level string `mapstructure:"level"`
Format string `mapstructure:"format"`
Output string `mapstructure:"output"`
}读取到结构体
go
package main
import (
"fmt"
"github.com/spf13/viper"
)
func LoadConfig(path string) (*Config, error) {
viper.SetConfigFile(path)
// 读取环境变量 (自动转换)
// 例如:APP_NAME 会映射到 app.name
viper.SetEnvPrefix("APP")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
return nil, err
}
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
return nil, err
}
return &cfg, nil
}
func main() {
cfg, err := LoadConfig("config.yaml")
if err != nil {
panic(err)
}
fmt.Printf("App: %s v%s\n", cfg.App.Name, cfg.App.Version)
fmt.Printf("Server: %s:%d\n", cfg.Server.Host, cfg.Server.Port)
}环境变量支持
go
package main
import (
"fmt"
"strings"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigName("config")
viper.AddConfigPath(".")
// 环境变量配置
viper.SetEnvPrefix("MYAPP") // 前缀
viper.AutomaticEnv() // 自动绑定
// 手动绑定环境变量
viper.BindEnv("server.port", "MYAPP_SERVER_PORT")
viper.BindEnv("database.host", "MYAPP_DB_HOST")
// 自定义键名转换 (将 server.port 转为 SERVER_PORT)
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// 设置默认值
viper.SetDefault("server.port", 8080)
viper.SetDefault("server.host", "localhost")
viper.SetDefault("debug", false)
viper.ReadInConfig()
// 优先级:环境变量 > 配置文件 > 默认值
fmt.Println("Port:", viper.GetInt("server.port"))
}bash
# 使用环境变量覆盖配置
export MYAPP_SERVER_PORT=9090
export MYAPP_DB_HOST=remote-db.example.com
./myapp命令行标志
go
package main
import (
"github.com/spf13/viper"
"github.com/spf13/pflag"
)
func main() {
// 创建 flagset
flags := pflag.NewFlagSet("myapp", pflag.ExitOnError)
// 定义命令行标志
flags.String("config", "config.yaml", "配置文件路径")
flags.String("server.port", "", "服务器端口")
flags.String("server.host", "", "服务器地址")
flags.Bool("debug", false, "调试模式")
// 绑定到 viper
viper.BindPFlags(flags)
// 绑定环境变量
viper.BindEnv("server.port", "MYAPP_PORT")
// 读取配置
viper.SetConfigFile(viper.GetString("config"))
viper.ReadInConfig()
// 使用配置
port := viper.GetInt("server.port")
host := viper.GetString("server.host")
}远程配置
从 etcd 读取
go
package main
import (
"github.com/spf13/viper"
)
func main() {
// 添加 etcd 远程配置
viper.AddRemoteProvider("etcd", "http://localhost:2379", "/config/myapp")
viper.SetConfigType("yaml")
if err := viper.ReadRemoteConfig(); err != nil {
panic(err)
}
// 监听配置变化
go func() {
for {
viper.WatchRemoteConfig()
// 配置已变更,重新加载
reloadConfig()
}
}()
}从 Consul 读取
go
viper.AddRemoteProvider("consul", "http://localhost:8500", "config/myapp")
viper.SetConfigType("json")
if err := viper.ReadRemoteConfig(); err != nil {
panic(err)
}配置热重载
go
package main
import (
"fmt"
"log"
"sync"
"github.com/spf13/viper"
)
type ConfigManager struct {
mu sync.RWMutex
config *Config
hooks []func(*Config)
}
func NewConfigManager(path string) (*ConfigManager, error) {
cm := &ConfigManager{}
viper.SetConfigFile(path)
viper.WatchConfig() // 监听文件变化
// 配置变化回调
viper.OnConfigChange(func(e viper.ConfigEvent) {
log.Println("配置文件变更:", e.Key, "->", e.NewValue)
cm.reload()
})
if err := viper.ReadInConfig(); err != nil {
return nil, err
}
if err := cm.reload(); err != nil {
return nil, err
}
return cm, nil
}
func (cm *ConfigManager) reload() error {
cm.mu.Lock()
defer cm.mu.Unlock()
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
return err
}
cm.config = &cfg
// 通知回调
for _, hook := range cm.hooks {
hook(cm.config)
}
return nil
}
func (cm *ConfigManager) Get() *Config {
cm.mu.RLock()
defer cm.mu.RUnlock()
return cm.config
}
func (cm *ConfigManager) RegisterHook(hook func(*Config)) {
cm.hooks = append(cm.hooks, hook)
}
// 使用示例
func main() {
cm, err := NewConfigManager("config.yaml")
if err != nil {
panic(err)
}
// 注册配置变更回调
cm.RegisterHook(func(cfg *Config) {
log.Println("日志级别变更为:", cfg.Log.Level)
// 重新配置 logger...
})
// 获取当前配置
cfg := cm.Get()
fmt.Println("Port:", cfg.Server.Port)
}多环境配置
go
package main
import (
"fmt"
"github.com/spf13/viper"
)
func LoadConfigByEnv(env string) (*Config, error) {
// 根据环境加载不同配置
viper.SetConfigFile(fmt.Sprintf("config.%s.yaml", env))
// 通用配置
viper.SetConfigFile("config.base.yaml")
viper.MergeInConfig()
if err := viper.ReadInConfig(); err != nil {
return nil, err
}
var cfg Config
viper.Unmarshal(&cfg)
return &cfg, nil
}
// 或者使用子树
func loadConfigWithSubtree() {
viper.SetConfigFile("config.yaml")
viper.ReadInConfig()
// 获取当前环境
env := viper.GetString("app.env")
// 从 environments.development 或 environments.production 读取
dbConfig := viper.Sub(fmt.Sprintf("environments.%s", env))
fmt.Println("DB Host:", dbConfig.GetString("database.host"))
}配置验证
go
package config
import (
"errors"
"github.com/spf13/viper"
)
var (
ErrMissingPort = errors.New("server.port is required")
ErrInvalidLogLevel = errors.New("invalid log level")
)
func Validate() error {
// 必需配置检查
required := []string{
"server.host",
"server.port",
"database.driver",
"database.host",
}
for _, key := range required {
if !viper.IsSet(key) {
return fmt.Errorf("%s is required", key)
}
}
// 值验证
port := viper.GetInt("server.port")
if port <= 0 || port > 65535 {
return ErrMissingPort
}
level := viper.GetString("log.level")
validLevels := []string{"debug", "info", "warn", "error"}
if !contains(validLevels, level) {
return ErrInvalidLogLevel
}
return nil
}
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}完整项目实践
go
// config/config.go
package config
import (
"fmt"
"sync"
"github.com/spf13/viper"
)
var (
once sync.Once
instance *ConfigManager
)
type ConfigManager struct {
viper *viper.Viper
config *Config
mu sync.RWMutex
}
func Init(configPath string) error {
var err error
once.Do(func() {
instance, err = NewConfigManager(configPath)
})
return err
}
func NewConfigManager(path string) (*ConfigManager, error) {
v := viper.New()
v.SetConfigFile(path)
v.SetEnvPrefix("APP")
v.AutomaticEnv()
if err := v.ReadInConfig(); err != nil {
return nil, err
}
cm := &ConfigManager{viper: v}
if err := cm.reload(); err != nil {
return nil, err
}
v.WatchConfig()
v.OnConfigChange(func(e viper.ConfigEvent) {
cm.reload()
})
return cm, nil
}
func (cm *ConfigManager) reload() error {
cm.mu.Lock()
defer cm.mu.Unlock()
var cfg Config
if err := cm.viper.Unmarshal(&cfg); err != nil {
return err
}
cm.config = &cfg
return nil
}
func Get() *Config {
if instance == nil {
return nil
}
instance.mu.RLock()
defer instance.mu.RUnlock()
return instance.config
}
// 便捷函数
func GetServerPort() int {
return Get().Server.Port
}
func GetDSN() string {
db := Get().Database
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
db.Username, db.Password, db.Host, db.Port, db.Database)
}Viper 检查清单
[ ] 使用结构体定义配置
[ ] 设置合理的默认值
[ ] 支持环境变量覆盖
[ ] 配置文件与代码分离
[ ] 敏感信息使用环境变量
[ ] 实现配置验证
[ ] 支持配置热重载
[ ] 记录配置变更日志
[ ] 多环境配置分离