package config import ( "fmt" "os" ) // Config 服务器配置(环境变量优先,.env 兜底) type Config struct { ServerAddr string MySQLDSN string RedisAddr string RedisPass string JWTSecret string BaseURL string PublicBaseURL string // 浏览器侧访问地址(邀请链接等) StorageDir string StaticDir string // 前端生产构建目录(空=不托管静态) MaxUploadBytes int64 // multipart 上传体积上限 } // Load 从环境变量加载 func Load() *Config { jwt := getenv("JWT_SECRET", "dev-secret-change-me") if jwt == "dev-secret-change-me" || jwt == "please-change-me-in-production" || len(jwt) < 16 { // 生产安全红线:默认/过短密钥可被伪造 JWT(含 admin 提权)。 // 保留 dev 默认值以便本地开发,但在任何环境都打印醒目告警。 println("!! [security] JWT_SECRET 使用默认值或长度不足(<16)。生产环境必须设置强随机 JWT_SECRET,否则任何人都能伪造令牌(admin 提权)。") } return &Config{ ServerAddr: getenv("SERVER_ADDR", ":8090"), MySQLDSN: getenv("MYSQL_DSN", "root:everypublish@tcp(127.0.0.1:3306)/everypublish?charset=utf8mb4&parseTime=True&loc=Local"), RedisAddr: getenv("REDIS_ADDR", "127.0.0.1:6379"), RedisPass: getenv("REDIS_PASSWORD", ""), JWTSecret: jwt, BaseURL: getenv("BASE_URL", "http://127.0.0.1:8090"), PublicBaseURL: getenv("PUBLIC_BASE_URL", getenv("BASE_URL", "http://127.0.0.1:8090")), StorageDir: getenv("STORAGE_DIR", "./data/materials"), StaticDir: getenv("STATIC_DIR", "../apps/web/dist"), MaxUploadBytes: getenvInt64("MAX_UPLOAD_BYTES", 2<<30), } } func getenvInt64(k string, def int64) int64 { v := os.Getenv(k) if v == "" { return def } var n int64 if _, err := fmt.Sscanf(v, "%d", &n); err != nil || n <= 0 { return def } return n } func getenv(k, def string) string { if v := os.Getenv(k); v != "" { return v } return def }