package config import ( "errors" "fmt" "os" "strings" "gopkg.in/yaml.v3" ) // Config 是服务的全部运行配置,来自 YAML 文件(默认 ./config.yaml,可用 -config 指定)。 type Config struct { // Env 为 dev 时启动执行 AutoMigrate;其余一律视为 prod,绝不执行 DDL。 Env string `yaml:"env"` Listen string `yaml:"listen"` // BaseURL 拼接文件访问 URL 的前缀(客户端可达的地址);留空则按请求 Host 推断。 BaseURL string `yaml:"base_url"` // BasePath 反代保留的 URL 前缀(如 /pms-api)。1Panel 等不剥前缀时必填;留空挂在根路径。 BasePath string `yaml:"base_path"` // JWTSecret 签发/校验 access、refresh token 的密钥,不允许留空。 JWTSecret string `yaml:"jwt_secret"` // AccessTTLHours access token 有效小时数,默认 2。 AccessTTLHours int `yaml:"access_ttl_hours"` // RefreshTTLDays refresh token 有效天数,默认 30。 RefreshTTLDays int `yaml:"refresh_ttl_days"` StorageDir string `yaml:"storage_dir"` MaxUploadMB int64 `yaml:"max_upload_mb"` // MaxReleaseMB 发版安装包上传上限(MB),默认 200。 MaxReleaseMB int64 `yaml:"max_release_mb"` // TrustedProxies 可信反代 CIDR/IP,用于正确解析 ClientIP;空则不信任 X-Forwarded-For。 TrustedProxies []string `yaml:"trusted_proxies"` MySQL struct { DSN string `yaml:"dsn"` } `yaml:"mysql"` } // MaxUploadBytes 返回单文件字节上限。 func (c *Config) MaxUploadBytes() int64 { return c.MaxUploadMB << 20 } // MaxReleaseBytes 返回发版包字节上限。 func (c *Config) MaxReleaseBytes() int64 { return c.MaxReleaseMB << 20 } // Load 读取并校验配置,缺省值:prod / :8788 / ./uploads / 20MB / access 2h / refresh 30d。 func Load(path string) (*Config, error) { b, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("读取配置失败: %w", err) } c := &Config{} if err := yaml.Unmarshal(b, c); err != nil { return nil, fmt.Errorf("解析配置失败: %w", err) } if c.Env != "dev" { c.Env = "prod" } if strings.TrimSpace(c.Listen) == "" { c.Listen = ":8788" } if strings.TrimSpace(c.StorageDir) == "" { c.StorageDir = "./uploads" } if c.MaxUploadMB <= 0 { c.MaxUploadMB = 20 } if c.MaxReleaseMB <= 0 { c.MaxReleaseMB = 200 } if c.AccessTTLHours <= 0 { c.AccessTTLHours = 2 } if c.RefreshTTLDays <= 0 { c.RefreshTTLDays = 30 } c.BaseURL = strings.TrimRight(strings.TrimSpace(c.BaseURL), "/") c.BasePath = strings.TrimSpace(c.BasePath) if c.BasePath != "" { if !strings.HasPrefix(c.BasePath, "/") { c.BasePath = "/" + c.BasePath } c.BasePath = strings.TrimRight(c.BasePath, "/") if c.BasePath == "/" { c.BasePath = "" } } c.JWTSecret = strings.TrimSpace(c.JWTSecret) if c.JWTSecret == "" { return nil, errors.New("配置缺少 jwt_secret(JWT 签发密钥,不允许留空)") } if strings.TrimSpace(c.MySQL.DSN) == "" { return nil, errors.New("配置缺少 mysql.dsn") } return c, nil }