102 lines
2.7 KiB
Go
102 lines
2.7 KiB
Go
// 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
|
|
}
|