v2.1 M9+M10: BFF layer and old portals retired.
- api-gateway: remove TeacherBffURL/StudentBffURL/ParentBffURL config
- api-gateway: add ApolloRouterURL config and /api/graphql route
- api-gateway: /api/admin/graphql now proxies to apollo-router
- api-gateway: health checks now ping apollo-router instead of BFF
- deploy.yml: replace teacher-bff service block with apollo-router
- deploy.yml: add config-service service block (M3 dependency)
- deploy.yml: remove teacher-portal and admin-portal service blocks
- source code in services/{teacher,student,parent}-bff/ and apps/*-portal/ retained for rollback
111 lines
3.8 KiB
Go
111 lines
3.8 KiB
Go
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
|
||
}
|