feat(config): implement layered configuration using config.yaml and .env

This commit is contained in:
2026-02-12 14:41:49 +08:00
parent e01cdc9889
commit aed996f409
6 changed files with 228 additions and 87 deletions

View File

@@ -2,9 +2,11 @@ package config
import (
"fmt"
"os"
"strings"
"github.com/joho/godotenv"
"github.com/spf13/viper"
"github.com/subosito/gotenv"
)
// ServerConfig 服务器配置
@@ -15,18 +17,24 @@ type ServerConfig struct {
// DatabaseConfig 数据库配置
type DatabaseConfig struct {
Driver string `mapstructure:"driver"`
SQLite struct {
Path string `mapstructure:"path"`
} `mapstructure:"sqlite"`
Postgres struct {
Host string `mapstructure:"host"`
Port string `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
DBName string `mapstructure:"dbname"`
SSLMode string `mapstructure:"sslmode"`
} `mapstructure:"postgres"`
Driver string `mapstructure:"driver"`
SQLite SQLiteConfig `mapstructure:"sqlite"`
Postgres PostgresConfig `mapstructure:"postgres"`
}
// SQLiteConfig SQLite 配置
type SQLiteConfig struct {
Path string `mapstructure:"path"`
}
// PostgresConfig PostgreSQL 配置
type PostgresConfig struct {
Host string `mapstructure:"host"`
Port string `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
DBName string `mapstructure:"dbname"`
SSLMode string `mapstructure:"sslmode"`
}
// JWTConfig JWT 配置
@@ -46,29 +54,70 @@ type AppConfig struct {
var appConfig AppConfig
// LoadConfig 加载配置
// 优先级:环境变量 > .env 文件 > config.yaml > 默认值
func LoadConfig() {
// 使用 gotenv 加载 .env 文件
if err := gotenv.Load(); err != nil {
fmt.Printf("警告: 未找到 .env 文件: %v\n", err)
}
// 设置默认值(最先设置,优先级最低)
setDefaults()
// 配置 Viper 以读取 YAML 配置文件
// 1. 加载 config.yaml 配置文件
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AddConfigPath("./config")
viper.AddConfigPath("../")
if err := viper.MergeInConfig(); err != nil {
fmt.Printf("警告: 未找到配置文件,使用默认值: %v\n", err)
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
fmt.Println("提示: 未找到 config.yaml使用默认配置")
} else {
fmt.Printf("错误: 读取配置文件失败: %v\n", err)
}
} else {
fmt.Printf("已加载配置文件: %s\n", viper.ConfigFileUsed())
}
// 环境变量前缀
viper.SetEnvPrefix("TRACE")
// 2. 加载 .env 文件(如果存在)
// .env 文件中的变量会被加载到环境变量中
if err := godotenv.Load(); err != nil {
// .env 文件是可选的,不存在不报错
if os.IsNotExist(err) {
fmt.Println("提示: 未找到 .env 文件,将使用环境变量或默认值")
} else {
fmt.Printf("警告: 加载 .env 文件失败: %v\n", err)
}
} else {
fmt.Println("已加载 .env 文件")
}
// 3. 设置环境变量支持
// 设置环境变量前缀为 APP_
viper.SetEnvPrefix("APP")
// 设置环境变量键名替换规则(将 _ 替换为 .
// 例如APP_SERVER_PORT 会映射到 server.port
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// 启用自动环境变量读取
viper.AutomaticEnv()
// 默认配置
// 4. 绑定特定的环境变量(确保嵌套结构体能正确映射)
bindEnvVariables()
// 解析配置到结构体
if err := viper.Unmarshal(&appConfig); err != nil {
fmt.Printf("错误: 配置解析失败: %v\n", err)
}
// 验证配置
validateConfig()
// 打印配置信息(开发环境)
printConfig()
}
// setDefaults 设置默认配置值
func setDefaults() {
// Server 默认值
viper.SetDefault("server.port", "3000")
viper.SetDefault("server.environment", "development")
// Database 默认值
viper.SetDefault("database.driver", "sqlite")
viper.SetDefault("database.sqlite.path", "./data/database.sqlite")
viper.SetDefault("database.postgres.host", "localhost")
@@ -77,36 +126,69 @@ func LoadConfig() {
viper.SetDefault("database.postgres.password", "trace123")
viper.SetDefault("database.postgres.dbname", "trace")
viper.SetDefault("database.postgres.sslmode", "disable")
// JWT 默认值
viper.SetDefault("jwt.secret", "your-secret-key-here-change-in-production")
viper.SetDefault("jwt.expire", 7200)
}
// 绑定环境变量(支持无前缀的 .env 文件)
viper.BindEnv("server.port", "PORT", "TRACE_SERVER_PORT")
viper.BindEnv("server.environment", "ENVIRONMENT", "TRACE_SERVER_ENVIRONMENT")
viper.BindEnv("jwt.secret", "JWT_SECRET", "TRACE_JWT_SECRET")
viper.BindEnv("jwt.expire", "JWT_EXPIRE", "TRACE_JWT_EXPIRE")
viper.BindEnv("database.driver", "DATABASE_DRIVER", "TRACE_DATABASE_DRIVER")
viper.BindEnv("database.sqlite.path", "DATABASE_PATH", "TRACE_DATABASE_SQLITE_PATH")
viper.BindEnv("database.postgres.host", "POSTGRES_HOST", "TRACE_DATABASE_POSTGRES_HOST")
viper.BindEnv("database.postgres.port", "POSTGRES_PORT", "TRACE_DATABASE_POSTGRES_PORT")
viper.BindEnv("database.postgres.user", "POSTGRES_USER", "TRACE_DATABASE_POSTGRES_USER")
viper.BindEnv("database.postgres.password", "POSTGRES_PASSWORD", "TRACE_DATABASE_POSTGRES_PASSWORD")
viper.BindEnv("database.postgres.dbname", "POSTGRES_DB", "TRACE_DATABASE_POSTGRES_DBNAME")
viper.BindEnv("database.postgres.sslmode", "POSTGRES_SSLMODE", "TRACE_DATABASE_POSTGRES_SSLMODE")
// bindEnvVariables 绑定环境变量
// 支持以下格式:
// - APP_SERVER_PORT=8080 (映射到 server.port)
// - APP_DATABASE_DRIVER=postgres (映射到 database.driver)
// - APP_DATABASE_POSTGRES_HOST=db.example.com (映射到 database.postgres.host)
func bindEnvVariables() {
// 服务器配置
viper.BindEnv("server.port")
viper.BindEnv("server.environment")
// 解析配置
if err := viper.Unmarshal(&appConfig); err != nil {
fmt.Printf("配置解析失败: %v\n", err)
}
// 数据库配置
viper.BindEnv("database.driver")
viper.BindEnv("database.sqlite.path")
viper.BindEnv("database.postgres.host")
viper.BindEnv("database.postgres.port")
viper.BindEnv("database.postgres.user")
viper.BindEnv("database.postgres.password")
viper.BindEnv("database.postgres.dbname")
viper.BindEnv("database.postgres.sslmode")
// JWT 配置
viper.BindEnv("jwt.secret")
viper.BindEnv("jwt.expire")
}
// validateConfig 验证配置
func validateConfig() {
// 验证 JWT 密钥
if appConfig.JWT.Secret == "your-secret-key-here-change-in-production" {
fmt.Println("警告: 使用默认 JWT 密钥,请在生产环境中设置 JWT_SECRET 环境变量")
if appConfig.Server.Environment == "production" {
fmt.Println("警告: 生产环境使用了默认 JWT 密钥,请设置 APP_JWT_SECRET 环境变量")
} else {
fmt.Println("提示: 使用默认 JWT 密钥(仅适用于开发环境)")
}
}
// 调试打印
fmt.Printf("加载的配置 - 环境: %s\n", appConfig.Server.Environment)
fmt.Printf("加载的配置 - 数据库驱动: %s\n", appConfig.Database.Driver)
// 验证端口
if appConfig.Server.Port == "" {
appConfig.Server.Port = "3000"
}
}
// printConfig 打印配置信息(仅开发环境)
func printConfig() {
fmt.Printf("配置加载完成:\n")
fmt.Printf(" 环境: %s\n", appConfig.Server.Environment)
fmt.Printf(" 端口: %s\n", appConfig.Server.Port)
fmt.Printf(" 数据库驱动: %s\n", appConfig.Database.Driver)
if appConfig.Database.Driver == "sqlite" {
fmt.Printf(" SQLite 路径: %s\n", appConfig.Database.SQLite.Path)
} else if appConfig.Database.Driver == "postgres" {
fmt.Printf(" PostgreSQL: %s:%s/%s\n",
appConfig.Database.Postgres.Host,
appConfig.Database.Postgres.Port,
appConfig.Database.Postgres.DBName)
}
}
// GetAppConfig 获取应用程序配置