← 中文资源

简体中文版本

Viper 环境配置分层加载

用 Viper 分层加载配置:YAML 文件打底、SRV 前缀环境变量覆盖点号键、默认值兜底,加载后做类型解析与范围校验。

阅读英文原文

示例说明

Viper 把配置合并的优先级内化:显式 Set 最高、环境变量次之、配置文件、最后默认值。SetEnvKeyReplacer 让 database.url 这样的嵌套键映射到 SRV_DATABASE_URL——环境变量扁平命名与配置文件的层级结构自动对上。文件缺失按设计降级而非报错,本地开发可以只靠环境变量与默认值跑起来。

技术要点

  • 前缀与键名替换映射环境变量
  • 配置文件缺失时优雅降级
  • 加载后显式类型解析与校验

代码解读

loadConfig 依次设置文件源、环境变量前缀与替换器、四组默认值;ReadInConfig 区分文件不存在与文件损坏。GetString 拿到的超时是字符串,显式 ParseDuration 转类型——Viper 的自动类型转换对 duration 不可靠。端口范围校验兜住最后一道。

使用建议

环境变量是运维覆盖的正道,禁止在文件里提交生产密钥。校验失败快速退出优于带病运行。结构体映射可用 viper.Unmarshal 前先确认 tag。

代码示例

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)
}