应用的配置管理很重要,不同环境需要不同的配置。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 支持多种配置源:
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
}
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 可以灵活地管理多环境配置,支持配置热重载。敏感信息应该通过环境变量注入,不要硬编码在配置文件中。配置加载后要进行验证,确保值的有效性。