// Package config 负责加载与解析应用配置。 // 优先级:默认值 < 配置文件 < 环境变量。 package config import ( "fmt" "os" "strings" "github.com/spf13/viper" ) // Config 是应用的顶层配置。 type Config struct { Server ServerConfig `mapstructure:"server"` MySQL MySQLConfig `mapstructure:"mysql"` Logging LoggingConfig `mapstructure:"logging"` Admin AdminConfig `mapstructure:"admin"` } type ServerConfig struct { Port int `mapstructure:"port"` Mode string `mapstructure:"mode"` } type MySQLConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` User string `mapstructure:"user"` Password string `mapstructure:"password"` Database string `mapstructure:"database"` // 连接与字符集参数。 Charset string `mapstructure:"charset"` ParseTime string `mapstructure:"parse_time"` Loc string `mapstructure:"loc"` // 连接池参数。 MaxIdleConns int `mapstructure:"max_idle_conns"` MaxOpenConns int `mapstructure:"max_open_conns"` ConnMaxLifetime int `mapstructure:"conn_max_lifetime"` // 单位:秒 } // DSN 根据各独立字段拼装 MySQL 连接串。 // 优先级:显式字段 < 环境变量;缺省项使用合理默认值。 func (d MySQLConfig) DSN() string { charset := d.Charset if charset == "" { charset = "utf8mb4" } parseTime := d.ParseTime if parseTime == "" { parseTime = "True" } loc := d.Loc if loc == "" { loc = "Local" } return fmt.Sprintf( "%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=%s&loc=%s", d.User, d.Password, d.Host, d.Port, d.Database, charset, parseTime, loc, ) } type LoggingConfig struct { Level string `mapstructure:"level"` Format string `mapstructure:"format"` } // AdminConfig 后台管理相关配置。 type AdminConfig struct { JWTSecret string `mapstructure:"jwt_secret"` TokenTTL string `mapstructure:"token_ttl"` } // Load 从 configPath 加载配置(为空则默认 configs/config.yaml)。 // 配置文件中使用 ${ENV} 占位符的字段会被对应环境变量替换, // 敏感信息(dsn/secret/token)据此注入,禁止硬编码。 func Load(configPath string) (*Config, error) { if configPath == "" { configPath = "configs/config.yaml" } raw, err := os.ReadFile(configPath) if err != nil { return nil, fmt.Errorf("read config %s: %w", configPath, err) } // 先展开 ${ENV} 占位符,再交给 viper 解析。 expanded := os.ExpandEnv(string(raw)) v := viper.New() v.SetConfigType("yaml") if err := v.ReadConfig(strings.NewReader(expanded)); err != nil { return nil, fmt.Errorf("parse config: %w", err) } var cfg Config if err := v.Unmarshal(&cfg); err != nil { return nil, fmt.Errorf("unmarshal config: %w", err) } return &cfg, nil }