Viper Environment Configuration Layering
Layer application config with Viper across files, environment variables with key replacer, and overrides bound to flags.
Code
package main
import (
"fmt"
"strings"
"time"
"github.com/spf13/viper"
)
type ServerConfig struct {
Host string
Port int
Timeout time.Duration
DatabaseURL string
ReplicaHosts []string
}
// precedence: flags > env > config file > defaults
func loadConfig(path string) (*ServerConfig, error) {
v := viper.New()
v.SetConfigName("config")
v.SetConfigType("yaml")
v.SetConfigFile(path)
v.SetEnvPrefix("SRV")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AutomaticEnv()
v.SetDefault("host", "0.0.0.0")
v.SetDefault("port", 8080)
v.SetDefault("timeout", "5s")
v.SetDefault("replicaHosts", []string{})
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return nil, fmt.Errorf("read config: %w", err)
}
// 配置文件缺失不算错:全部回退到环境变量与默认值
}
if err := v.Validate(); err != nil {
return nil, fmt.Errorf("validate: %w", err)
}
cfg := &ServerConfig{
Host: v.GetString("host"),
Port: v.GetInt("port"),
DatabaseURL: v.GetString("database.url"),
}
if raw := v.GetString("timeout"); raw != "" {
d, err := time.ParseDuration(raw)
if err != nil {
return nil, fmt.Errorf("invalid timeout %q: %w", raw, err)
}
cfg.Timeout = d
}
if replicas := v.GetStringSlice("replicaHosts"); len(replicas) > 0 {
cfg.ReplicaHosts = replicas
}
if cfg.Port < 1 || cfg.Port > 65535 {
return nil, fmt.Errorf("port %d out of range", cfg.Port)
}
return cfg, nil
}
func main() {
cfg, err := loadConfig("configs/config.yaml")
fmt.Println(cfg, err)
}Report a problem with this exampleOpens a pre-filled issue on GitHub.