feat(config): implement layered configuration using config.yaml and .env
This commit is contained in:
172
config/config.go
172
config/config.go
@@ -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 获取应用程序配置
|
||||
|
||||
Reference in New Issue
Block a user