130 lines
3.8 KiB
Go
130 lines
3.8 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"`
|
||
Upload UploadConfig `mapstructure:"upload"`
|
||
}
|
||
|
||
// UploadConfig 文件上传相关配置。
|
||
type UploadConfig struct {
|
||
StorageType string `mapstructure:"storage_type"` // local | minio(minio 预留)
|
||
LocalPath string `mapstructure:"local_path"` // 本地存储根目录(storage_type=local)
|
||
BaseURL string `mapstructure:"base_url"` // 文件可访问的基础路径/域名
|
||
MaxSize int64 `mapstructure:"max_size"` // 单文件大小上限(字节),0 表示不限制
|
||
AllowedExt []string `mapstructure:"allowed_ext"` // 扩展名白名单;为空表示不限制类型
|
||
}
|
||
|
||
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)
|
||
}
|
||
applyUploadDefaults(&cfg.Upload)
|
||
return &cfg, nil
|
||
}
|
||
|
||
// applyUploadDefaults 为上传配置补齐合理默认值。
|
||
func applyUploadDefaults(u *UploadConfig) {
|
||
if u.StorageType == "" {
|
||
u.StorageType = "local"
|
||
}
|
||
if u.LocalPath == "" {
|
||
u.LocalPath = "./uploads"
|
||
}
|
||
if u.BaseURL == "" {
|
||
u.BaseURL = "/files/static"
|
||
}
|
||
// MaxSize=0 视为不限制;仅当完全未配置(<0)时兜底为 10MB,避免误配导致无限大。
|
||
if u.MaxSize < 0 {
|
||
u.MaxSize = 10 << 20
|
||
}
|
||
}
|