package config import ( "log/slog" "os" "strconv" ) // Config 持有 api-gateway 运行时配置。 // P2 起 JWT 验签改 RS256(IAM 签发,Gateway 用 JWKS 公钥校验), // JWTSecret 仅在 DevMode 下作为 mock 密钥保留。 // // v2.1 M9:teacher-bff / student-bff / parent-bff 已下线, // 由 apollo-router(GraphQL 联邦)替代 BFF 聚合层。 type Config struct { Port string JWKSURL string // RS256 公钥端点(IAM GET /.well-known/jwks.json) JWTSecret string // DevMode 下 mock 用(生产不再使用 HS256) JWTIssuer string JWTAudience string CORSOrigins string ClassesServiceURL string IamServiceURL string ApolloRouterURL string // v2.1 M9:apollo-router GraphQL 联邦入口 CoreEduServiceURL string ContentServiceURL string DataAnaServiceURL string MsgServiceURL string AiServiceURL string OTLPEndpoint string LogLevel string DevMode bool Env string // 部署环境标识:production / development(W7 防护用) } // devJWTSecret 是 DevMode 下的默认 JWT 密钥(仅用于本地联调,生产必须关闭 DevMode) const devJWTSecret = "p1-dev-secret-change-in-production" // Load 从环境变量加载配置。 // // W7 生产防护:DevMode=true 且 ENV=production 时 panic 拒绝启动, // 避免 dev-token 旁路鉴权被误开到生产环境。 // P2 起 RS256:非 DevMode 下要求 IAM_JWKS_URL(JWKS 公钥端点), // JWT_SECRET 不再是生产必填(仅 DevMode mock 用)。 func Load() *Config { devMode := getEnvBool("DEV_MODE", false) env := getEnv("ENV", "development") // W7 生产防护:DevMode 旁路仅允许非生产环境 if devMode && env == "production" { panic("DEV_MODE=true is not allowed in production (ENV=production)") } jwtSecret := getEnv("JWT_SECRET", "") if jwtSecret == "" && devMode { slog.Warn("JWT_SECRET not set, using dev default (DEV_MODE=true)") jwtSecret = devJWTSecret } // 非 DevMode 下要求 JWKS URL(RS256 验签) jwksURL := getEnv("IAM_JWKS_URL", "http://localhost:3002/v1/iam/.well-known/jwks.json") if !devMode && jwksURL == "" { panic("IAM_JWKS_URL must be set in non-dev mode (RS256 JWT verification)") } slog.Info("config loaded", "env", env, "dev_mode", devMode, "jwks_url", jwksURL, ) return &Config{ Port: getEnv("API_GATEWAY_PORT", "8080"), JWKSURL: jwksURL, JWTSecret: jwtSecret, JWTIssuer: getEnv("JWT_ISSUER", "next-edu-cloud"), JWTAudience: getEnv("JWT_AUDIENCE", "next-edu-cloud"), CORSOrigins: getEnv("CORS_ORIGINS", ""), ClassesServiceURL: getEnv("CLASSES_SERVICE_URL", "http://localhost:3001"), IamServiceURL: getEnv("IAM_SERVICE_URL", "http://localhost:3002"), ApolloRouterURL: getEnv("APOLLO_ROUTER_URL", "http://localhost:3000"), CoreEduServiceURL: getEnv("CORE_EDU_SERVICE_URL", "http://localhost:3004"), ContentServiceURL: getEnv("CONTENT_SERVICE_URL", "http://localhost:3005"), DataAnaServiceURL: getEnv("DATA_ANA_SERVICE_URL", "http://localhost:3006"), MsgServiceURL: getEnv("MSG_SERVICE_URL", "http://localhost:3007"), AiServiceURL: getEnv("AI_SERVICE_URL", "http://localhost:3008"), OTLPEndpoint: getEnv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318"), LogLevel: getEnv("LOG_LEVEL", "info"), DevMode: devMode, Env: env, } } // getEnv 读取环境变量,缺失或空字符串时返回 fallback func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback } // getEnvBool 读取环境变量并解析为 bool,缺失或解析失败时返回 fallback func getEnvBool(key string, fallback bool) bool { if v := os.Getenv(key); v != "" { if b, err := strconv.ParseBool(v); err == nil { return b } } return fallback }