配置管理

应用的配置管理很重要,不同环境需要不同的配置。Go 生态有很多配置管理库,Viper 是最流行的之一。

使用环境变量

最简单的配置方式:

package main

import (
    "log"
    "os"
    "strconv"
    
    "github.com/gin-gonic/gin"
)

type Config struct {
    Port         int
    DatabaseURL  string
    RedisURL     string
    LogLevel     string
    JWTSecret    string
}

func LoadConfig() *Config {
    port, _ := strconv.Atoi(getEnv("PORT", "8080"))
    
    return &Config{
        Port:        port,
        DatabaseURL: getEnv("DATABASE_URL", "localhost:3306"),
        RedisURL:    getEnv("REDIS_URL", "localhost:6379"),
        LogLevel:    getEnv("LOG_LEVEL", "info"),
        JWTSecret:   getEnv("JWT_SECRET", "secret"),
    }
}

func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

func main() {
    config := LoadConfig()
    
    gin.SetMode(config.LogLevel)
    
    r := gin.Default()
    
    log.Printf("Server starting on port %d", config.Port)
    r.Run(":" + strconv.Itoa(config.Port))
}

使用 Viper

Viper 支持多种配置源:

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

type Config struct {
    Server   ServerConfig
    Database DatabaseConfig
    Redis    RedisConfig
}

type ServerConfig struct {
    Port    int
    Timeout int
}

type DatabaseConfig struct {
    Host     string
    Port     int
    User     string
    Password string
    Database string
}

type RedisConfig struct {
    Host string
    Port int
}

func LoadConfig() (*Config, error) {
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath(".")
    viper.AddConfigPath("./config")
    
    viper.AutomaticEnv()
    viper.SetEnvPrefix("APP")
    
    if err := viper.ReadInConfig(); err != nil {
        return nil, err
    }
    
    var config Config
    if err := viper.Unmarshal(&config); err != nil {
        return nil, err
    }
    
    return &config, nil
}

配置文件示例

config.yaml

server:
  port: 8080
  timeout: 30

database:
  host: localhost
  port: 3306
  user: root
  password: password
  database: myapp

redis:
  host: localhost
  port: 6379

多环境配置

func LoadConfig() (*Config, error) {
    env := getEnv("APP_ENV", "development")
    
    viper.SetConfigName("config." + env)
    viper.SetConfigType("yaml")
    viper.AddConfigPath("./config")
    
    viper.SetDefault("server.port", 8080)
    viper.SetDefault("server.timeout", 30)
    
    if err := viper.ReadInConfig(); err != nil {
        return nil, err
    }
    
    var config Config
    viper.Unmarshal(&config)
    
    return &config, nil
}

配置文件结构:

config/
├── config.development.yaml
├── config.staging.yaml
└── config.production.yaml

热重载配置

func main() {
    viper.SetConfigFile("config.yaml")
    viper.ReadInConfig()
    
    viper.WatchConfig()
    viper.OnConfigChange(func(e fsnotify.Event) {
        log.Println("Config file changed:", e.Name)
    })
    
    r := gin.Default()
    r.Run(":8080")
}

配置验证

func (c *Config) Validate() error {
    if c.Server.Port < 1 || c.Server.Port > 65535 {
        return errors.New("invalid server port")
    }
    
    if c.Database.Host == "" {
        return errors.New("database host is required")
    }
    
    return nil
}

func LoadConfig() (*Config, error) {
    var config Config
    if err := viper.Unmarshal(&config); err != nil {
        return nil, err
    }
    
    if err := config.Validate(); err != nil {
        return nil, err
    }
    
    return &config, nil
}

敏感配置处理

func LoadConfig() (*Config, error) {
    viper.SetConfigFile("config.yaml")
    viper.ReadInConfig()
    
    viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
    viper.AutomaticEnv()
    
    var config Config
    viper.Unmarshal(&config)
    
    if secret := os.Getenv("DATABASE_PASSWORD"); secret != "" {
        config.Database.Password = secret
    }
    
    if secret := os.Getenv("JWT_SECRET"); secret != "" {
        config.JWTSecret = secret
    }
    
    return &config, nil
}

配置注入到 Gin

func SetupRouter(config *Config) *gin.Engine {
    r := gin.Default()
    
    r.Use(func(c *gin.Context) {
        c.Set("config", config)
        c.Next()
    })
    
    r.GET("/config", func(c *gin.Context) {
        cfg := c.MustGet("config").(*Config)
        c.JSON(200, gin.H{
            "port": cfg.Server.Port,
        })
    })
    
    return r
}

func main() {
    config, err := LoadConfig()
    if err != nil {
        log.Fatal(err)
    }
    
    r := SetupRouter(config)
    r.Run(":" + strconv.Itoa(config.Server.Port))
}

小结

配置管理是应用的基础设施。使用 Viper 可以灵活地管理多环境配置,支持配置热重载。敏感信息应该通过环境变量注入,不要硬编码在配置文件中。配置加载后要进行验证,确保值的有效性。