feat(config): migrate configuration management to viper

Replace manual environment variable parsing with Viper to support
file-based configuration.

- Enable loading config from `config.yaml` or `./config/config.yaml`
- Allow custom config path via `EZ_CONFIG_FILE` environment variable
- Bind existing environment variables to maintain backward compatibility
- Update documentation with configuration examples and precedence rules
This commit is contained in:
zenfun
2025-12-12 22:50:54 +08:00
parent 5891722526
commit eaf99e1582
4 changed files with 104 additions and 39 deletions

View File

@@ -1,9 +1,12 @@
package config
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/spf13/viper"
)
type Config struct {
@@ -39,50 +42,67 @@ type LogConfig struct {
}
func Load() (*Config, error) {
return &Config{
v := viper.New()
v.SetDefault("server.port", "8080")
v.SetDefault("postgres.dsn", "host=localhost user=postgres password=postgres dbname=ezapi port=5432 sslmode=disable")
v.SetDefault("redis.addr", "localhost:6379")
v.SetDefault("redis.password", "")
v.SetDefault("redis.db", 0)
v.SetDefault("log.batch_size", 10)
v.SetDefault("log.flush_ms", 1000)
v.SetDefault("log.queue_capacity", 10000)
v.SetDefault("auth.jwt_secret", "change_me_in_production")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AutomaticEnv()
_ = v.BindEnv("server.port", "EZ_API_PORT")
_ = v.BindEnv("postgres.dsn", "EZ_PG_DSN")
_ = v.BindEnv("redis.addr", "EZ_REDIS_ADDR")
_ = v.BindEnv("redis.password", "EZ_REDIS_PASSWORD")
_ = v.BindEnv("redis.db", "EZ_REDIS_DB")
_ = v.BindEnv("log.batch_size", "EZ_LOG_BATCH_SIZE")
_ = v.BindEnv("log.flush_ms", "EZ_LOG_FLUSH_MS")
_ = v.BindEnv("log.queue_capacity", "EZ_LOG_QUEUE")
_ = v.BindEnv("auth.jwt_secret", "EZ_JWT_SECRET")
if configFile := os.Getenv("EZ_CONFIG_FILE"); configFile != "" {
v.SetConfigFile(configFile)
} else {
v.SetConfigName("config")
v.SetConfigType("yaml")
v.AddConfigPath(".")
v.AddConfigPath("./config")
}
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return nil, fmt.Errorf("read config: %w", err)
}
}
cfg := &Config{
Server: ServerConfig{
Port: getEnv("EZ_API_PORT", "8080"),
Port: v.GetString("server.port"),
},
Postgres: PostgresConfig{
DSN: getEnv("EZ_PG_DSN", "host=localhost user=postgres password=postgres dbname=ezapi port=5432 sslmode=disable"),
DSN: v.GetString("postgres.dsn"),
},
Redis: RedisConfig{
Addr: getEnv("EZ_REDIS_ADDR", "localhost:6379"),
Password: getEnv("EZ_REDIS_PASSWORD", ""),
DB: getEnvInt("EZ_REDIS_DB", 0),
Addr: v.GetString("redis.addr"),
Password: v.GetString("redis.password"),
DB: v.GetInt("redis.db"),
},
Log: LogConfig{
BatchSize: getEnvInt("EZ_LOG_BATCH_SIZE", 10),
FlushInterval: getEnvDuration("EZ_LOG_FLUSH_MS", 1000),
QueueCapacity: getEnvInt("EZ_LOG_QUEUE", 10000),
BatchSize: v.GetInt("log.batch_size"),
FlushInterval: time.Duration(v.GetInt("log.flush_ms")) * time.Millisecond,
QueueCapacity: v.GetInt("log.queue_capacity"),
},
Auth: AuthConfig{
JWTSecret: getEnv("EZ_JWT_SECRET", "change_me_in_production"),
JWTSecret: v.GetString("auth.jwt_secret"),
},
}, nil
}
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
func getEnvInt(key string, fallback int) int {
if value, ok := os.LookupEnv(key); ok {
if i, err := strconv.Atoi(value); err == nil {
return i
}
}
return fallback
}
func getEnvDuration(key string, fallbackMs int) time.Duration {
if value, ok := os.LookupEnv(key); ok {
if i, err := strconv.Atoi(value); err == nil {
return time.Duration(i) * time.Millisecond
}
}
return time.Duration(fallbackMs) * time.Millisecond
return cfg, nil
}