Files
portal-mini-go/internal/pkg/config/config.go
T
2026-10-27 22:48:36 +08:00

130 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}
}