318 lines
8.4 KiB
Go
318 lines
8.4 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type AppConfig struct {
|
|
Host string `yaml:"host"`
|
|
Port int `yaml:"port"`
|
|
Version string `yaml:"version"`
|
|
Debug bool `yaml:"debug"`
|
|
Timezone string `yaml:"timezone"`
|
|
Environment string `yaml:"environment"`
|
|
}
|
|
|
|
type LoggerConfig struct {
|
|
Level string `yaml:"level"`
|
|
Format string `yaml:"format"`
|
|
Output string `yaml:"output"`
|
|
Filename string `yaml:"filename"`
|
|
MaxSize int `yaml:"max_size"`
|
|
MaxAge int `yaml:"max_age"`
|
|
MaxBackups int `yaml:"max_backups"`
|
|
Compress bool `yaml:"compress"`
|
|
}
|
|
|
|
type DatabaseConfig struct {
|
|
Driver string `yaml:"driver"`
|
|
DSN string `yaml:"dsn"`
|
|
Host string `yaml:"host"`
|
|
Port int `yaml:"port"`
|
|
Username string `yaml:"username"`
|
|
Password string `yaml:"password"`
|
|
DBName string `yaml:"dbname"`
|
|
SSLMode string `yaml:"sslmode"`
|
|
SQLitePath string `yaml:"sqlite_path"`
|
|
MaxIdleConns int `yaml:"max_idle_conns"`
|
|
MaxOpenConns int `yaml:"max_open_conns"`
|
|
ConnMaxLifetime int `yaml:"conn_max_lifetime"`
|
|
}
|
|
|
|
type RedisConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Host string `yaml:"host"`
|
|
Port int `yaml:"port"`
|
|
Password string `yaml:"password"`
|
|
Database int `yaml:"database"`
|
|
PoolSize int `yaml:"pool_size"`
|
|
MinIdleConns int `yaml:"min_idle_conns"`
|
|
MaxRetries int `yaml:"max_retries"`
|
|
}
|
|
|
|
type JWTConfig struct {
|
|
Secret string `yaml:"secret"`
|
|
ExpireHours int `yaml:"expire_hours"` // 兼容旧配置,作为 access token 默认值
|
|
AccessExpireMinutes int `yaml:"access_expire_minutes"`
|
|
RefreshExpireHours int `yaml:"refresh_expire_hours"`
|
|
Issuer string `yaml:"issuer"`
|
|
}
|
|
|
|
type ObservabilityConfig struct {
|
|
MetricsEnabled bool `yaml:"metrics_enabled"`
|
|
MetricsPath string `yaml:"metrics_path"`
|
|
TracingEnabled bool `yaml:"tracing_enabled"`
|
|
TracingService string `yaml:"tracing_service"`
|
|
TracingEndpoint string `yaml:"tracing_endpoint"`
|
|
TracingInsecure bool `yaml:"tracing_insecure"`
|
|
}
|
|
|
|
type Config struct {
|
|
App AppConfig `yaml:"app"`
|
|
Logger LoggerConfig `yaml:"logger"`
|
|
Database DatabaseConfig `yaml:"database"`
|
|
Redis RedisConfig `yaml:"redis"`
|
|
JWT JWTConfig `yaml:"jwt"`
|
|
Observability ObservabilityConfig `yaml:"observability"`
|
|
}
|
|
|
|
func Load() (*Config, error) {
|
|
path := os.Getenv("SKELETON_CONFIG")
|
|
if path == "" {
|
|
path = "config/app.yaml"
|
|
}
|
|
return LoadFrom(path)
|
|
}
|
|
|
|
func LoadFrom(path string) (*Config, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取配置文件失败: %w", err)
|
|
}
|
|
|
|
var cfg Config
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("解析配置文件失败: %w", err)
|
|
}
|
|
|
|
cfg.applyEnvironment()
|
|
cfg.setDefaults()
|
|
if err := cfg.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
|
|
func (c *Config) validate() error {
|
|
switch c.Database.Driver {
|
|
case "postgres", "mysql", "sqlite":
|
|
default:
|
|
return fmt.Errorf("不支持的数据库驱动 %q", c.Database.Driver)
|
|
}
|
|
if strings.EqualFold(c.App.Environment, "production") &&
|
|
(c.JWT.Secret == "change-this-secret-key-in-production" || len(c.JWT.Secret) < 32) {
|
|
return fmt.Errorf("生产环境 JWT_SECRET 必须修改且至少 32 个字符")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) applyEnvironment() {
|
|
stringEnv("APP_HOST", &c.App.Host)
|
|
intEnv("APP_PORT", &c.App.Port)
|
|
stringEnv("APP_VERSION", &c.App.Version)
|
|
boolEnv("APP_DEBUG", &c.App.Debug)
|
|
stringEnv("APP_TIMEZONE", &c.App.Timezone)
|
|
stringEnv("APP_ENVIRONMENT", &c.App.Environment)
|
|
|
|
stringEnv("LOG_LEVEL", &c.Logger.Level)
|
|
stringEnv("LOG_FORMAT", &c.Logger.Format)
|
|
stringEnv("LOG_OUTPUT", &c.Logger.Output)
|
|
stringEnv("LOG_FILENAME", &c.Logger.Filename)
|
|
|
|
stringEnv("DATABASE_DRIVER", &c.Database.Driver)
|
|
stringEnv("DATABASE_DSN", &c.Database.DSN)
|
|
stringEnv("DATABASE_HOST", &c.Database.Host)
|
|
intEnv("DATABASE_PORT", &c.Database.Port)
|
|
stringEnv("DATABASE_USERNAME", &c.Database.Username)
|
|
stringEnv("DATABASE_PASSWORD", &c.Database.Password)
|
|
stringEnv("DATABASE_NAME", &c.Database.DBName)
|
|
stringEnv("DATABASE_SSLMODE", &c.Database.SSLMode)
|
|
stringEnv("DATABASE_SQLITE_PATH", &c.Database.SQLitePath)
|
|
intEnv("DATABASE_MAX_IDLE_CONNS", &c.Database.MaxIdleConns)
|
|
intEnv("DATABASE_MAX_OPEN_CONNS", &c.Database.MaxOpenConns)
|
|
intEnv("DATABASE_CONN_MAX_LIFETIME", &c.Database.ConnMaxLifetime)
|
|
|
|
boolEnv("REDIS_ENABLED", &c.Redis.Enabled)
|
|
stringEnv("REDIS_HOST", &c.Redis.Host)
|
|
intEnv("REDIS_PORT", &c.Redis.Port)
|
|
stringEnv("REDIS_PASSWORD", &c.Redis.Password)
|
|
intEnv("REDIS_DATABASE", &c.Redis.Database)
|
|
|
|
stringEnv("JWT_SECRET", &c.JWT.Secret)
|
|
intEnv("JWT_ACCESS_EXPIRE_MINUTES", &c.JWT.AccessExpireMinutes)
|
|
intEnv("JWT_REFRESH_EXPIRE_HOURS", &c.JWT.RefreshExpireHours)
|
|
stringEnv("JWT_ISSUER", &c.JWT.Issuer)
|
|
|
|
boolEnv("METRICS_ENABLED", &c.Observability.MetricsEnabled)
|
|
stringEnv("METRICS_PATH", &c.Observability.MetricsPath)
|
|
boolEnv("TRACING_ENABLED", &c.Observability.TracingEnabled)
|
|
stringEnv("OTEL_SERVICE_NAME", &c.Observability.TracingService)
|
|
stringEnv("OTEL_EXPORTER_OTLP_ENDPOINT", &c.Observability.TracingEndpoint)
|
|
boolEnv("OTEL_EXPORTER_OTLP_INSECURE", &c.Observability.TracingInsecure)
|
|
}
|
|
|
|
func stringEnv(key string, target *string) {
|
|
if value, ok := os.LookupEnv(key); ok {
|
|
*target = value
|
|
}
|
|
}
|
|
|
|
func intEnv(key string, target *int) {
|
|
if value, ok := os.LookupEnv(key); ok {
|
|
if parsed, err := strconv.Atoi(value); err == nil {
|
|
*target = parsed
|
|
}
|
|
}
|
|
}
|
|
|
|
func boolEnv(key string, target *bool) {
|
|
if value, ok := os.LookupEnv(key); ok {
|
|
if parsed, err := strconv.ParseBool(value); err == nil {
|
|
*target = parsed
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Config) setDefaults() {
|
|
if c.App.Host == "" {
|
|
c.App.Host = "0.0.0.0"
|
|
}
|
|
if c.App.Port == 0 {
|
|
c.App.Port = 8080
|
|
}
|
|
if c.App.Version == "" {
|
|
c.App.Version = "0.1.0"
|
|
}
|
|
if c.App.Timezone == "" {
|
|
c.App.Timezone = "UTC"
|
|
}
|
|
if c.App.Environment == "" {
|
|
c.App.Environment = "development"
|
|
}
|
|
|
|
if c.Logger.Level == "" {
|
|
c.Logger.Level = "info"
|
|
}
|
|
if c.Logger.Format == "" {
|
|
c.Logger.Format = "console"
|
|
}
|
|
if c.Logger.Output == "" {
|
|
c.Logger.Output = "stdout"
|
|
}
|
|
if c.Logger.MaxSize == 0 {
|
|
c.Logger.MaxSize = 100
|
|
}
|
|
if c.Logger.MaxAge == 0 {
|
|
c.Logger.MaxAge = 30
|
|
}
|
|
if c.Logger.MaxBackups == 0 {
|
|
c.Logger.MaxBackups = 3
|
|
}
|
|
|
|
if c.Database.Driver == "" {
|
|
c.Database.Driver = "postgres"
|
|
}
|
|
c.Database.Driver = strings.ToLower(strings.TrimSpace(c.Database.Driver))
|
|
switch c.Database.Driver {
|
|
case "postgresql":
|
|
c.Database.Driver = "postgres"
|
|
case "sqlite3":
|
|
c.Database.Driver = "sqlite"
|
|
}
|
|
if c.Database.Port == 0 {
|
|
switch c.Database.Driver {
|
|
case "postgres":
|
|
c.Database.Port = 5432
|
|
case "mysql":
|
|
c.Database.Port = 3306
|
|
}
|
|
}
|
|
if c.Database.SSLMode == "" {
|
|
c.Database.SSLMode = "disable"
|
|
}
|
|
if c.Database.SQLitePath == "" {
|
|
c.Database.SQLitePath = "data/skeleton.db"
|
|
}
|
|
if c.Database.MaxIdleConns == 0 {
|
|
if c.Database.Driver == "sqlite" {
|
|
c.Database.MaxIdleConns = 1
|
|
} else {
|
|
c.Database.MaxIdleConns = 10
|
|
}
|
|
}
|
|
if c.Database.MaxOpenConns == 0 {
|
|
if c.Database.Driver == "sqlite" {
|
|
c.Database.MaxOpenConns = 1
|
|
} else {
|
|
c.Database.MaxOpenConns = 100
|
|
}
|
|
}
|
|
if c.Database.ConnMaxLifetime == 0 {
|
|
c.Database.ConnMaxLifetime = 60
|
|
}
|
|
|
|
if c.Redis.Host == "" {
|
|
c.Redis.Host = "localhost"
|
|
}
|
|
if c.Redis.Port == 0 {
|
|
c.Redis.Port = 6379
|
|
}
|
|
if c.Redis.Database == 0 {
|
|
c.Redis.Database = 0
|
|
}
|
|
if c.Redis.PoolSize == 0 {
|
|
c.Redis.PoolSize = 10
|
|
}
|
|
if c.Redis.MinIdleConns == 0 {
|
|
c.Redis.MinIdleConns = 5
|
|
}
|
|
if c.Redis.MaxRetries == 0 {
|
|
c.Redis.MaxRetries = 3
|
|
}
|
|
|
|
if c.JWT.Secret == "" {
|
|
c.JWT.Secret = "change-this-secret-key-in-production"
|
|
}
|
|
if c.JWT.ExpireHours == 0 {
|
|
c.JWT.ExpireHours = 24
|
|
}
|
|
if c.JWT.AccessExpireMinutes == 0 {
|
|
c.JWT.AccessExpireMinutes = c.JWT.ExpireHours * 60
|
|
}
|
|
if c.JWT.RefreshExpireHours == 0 {
|
|
c.JWT.RefreshExpireHours = 24 * 7
|
|
}
|
|
if c.JWT.Issuer == "" {
|
|
c.JWT.Issuer = "HeTianXia"
|
|
}
|
|
|
|
if c.Observability.MetricsPath == "" {
|
|
c.Observability.MetricsPath = "/metrics"
|
|
}
|
|
if c.Observability.TracingService == "" {
|
|
c.Observability.TracingService = "skeleton"
|
|
}
|
|
if c.Observability.TracingEndpoint == "" {
|
|
c.Observability.TracingEndpoint = "localhost:4318"
|
|
}
|
|
}
|
|
|
|
func (c *Config) GetAddr() string {
|
|
return fmt.Sprintf("%s:%d", c.App.Host, c.App.Port)
|
|
}
|