feat(api-gateway): 实现 W1-W8 网关硬化与 P2-P5 路由扩展
依据 coord-final-decisions §3.8 W1-W8 裁决与 president-final-rulings §2.15/§2.16/§2.19 完整实现网关硬化: - W1/W2: 错误码 GW_ 前缀 + ActionState 信封响应体 - W3: 全量替换为 log/slog 结构化日志 - W4: /readyz 并行 ping 9 下游 + 软失败规则 - W5: 7 个业务 Prometheus 指标 + /metrics 端点 - W6: tracer 资源属性补全(name/version/env/host) - W7: DevMode=true && ENV=production panic 防护 - W8: 保持共享 downstream 熔断 P2 RS256 升级:接入 shared-go/jwks.Fetcher(TTL 5min)。 P2.7+P3-P5 路由扩展:student/parent/messages/dashboard。 文档同步:README/01/02/known-issues,arch.db 已更新。 质量校验:go vet + build + test 均通过。
This commit is contained in:
@@ -1,20 +1,26 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Config 持有 api-gateway 运行时配置
|
||||
// Config 持有 api-gateway 运行时配置。
|
||||
// P2 起 JWT 验签改 RS256(IAM 签发,Gateway 用 JWKS 公钥校验),
|
||||
// JWTSecret 仅在 DevMode 下作为 mock 密钥保留。
|
||||
type Config struct {
|
||||
Port string
|
||||
JWTSecret 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
|
||||
TeacherBffURL string
|
||||
StudentBffURL string
|
||||
ParentBffURL string
|
||||
CoreEduServiceURL string
|
||||
ContentServiceURL string
|
||||
DataAnaServiceURL string
|
||||
@@ -23,33 +29,57 @@ type Config struct {
|
||||
OTLPEndpoint string
|
||||
LogLevel string
|
||||
DevMode bool
|
||||
Env string // 部署环境标识:production / development(W7 防护用)
|
||||
}
|
||||
|
||||
// devJWTSecret 是 DevMode 下的默认 JWT 密钥(仅用于本地联调,生产必须配置 JWT_SECRET)
|
||||
// devJWTSecret 是 DevMode 下的默认 JWT 密钥(仅用于本地联调,生产必须关闭 DevMode)
|
||||
const devJWTSecret = "p1-dev-secret-change-in-production"
|
||||
|
||||
// Load 从环境变量加载配置。
|
||||
// 非 DevMode 下若 JWT_SECRET 未配置则 fatal 退出;DevMode 下使用默认密钥并打印 warning。
|
||||
//
|
||||
// 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)
|
||||
jwtSecret := getEnv("JWT_SECRET", "")
|
||||
if jwtSecret == "" {
|
||||
if devMode {
|
||||
log.Println("warning: JWT_SECRET not set, using dev default (DEV_MODE=true)")
|
||||
jwtSecret = devJWTSecret
|
||||
} else {
|
||||
log.Fatal("JWT_SECRET must be set in non-dev mode")
|
||||
}
|
||||
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/.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"),
|
||||
TeacherBffURL: getEnv("TEACHER_BFF_URL", "http://localhost:3003"),
|
||||
StudentBffURL: getEnv("STUDENT_BFF_URL", "http://localhost:3009"),
|
||||
ParentBffURL: getEnv("PARENT_BFF_URL", "http://localhost:3010"),
|
||||
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"),
|
||||
@@ -58,10 +88,11 @@ func Load() *Config {
|
||||
OTLPEndpoint: getEnv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318"),
|
||||
LogLevel: getEnv("LOG_LEVEL", "info"),
|
||||
DevMode: devMode,
|
||||
Env: env,
|
||||
}
|
||||
}
|
||||
|
||||
// getEnv 读取环境变量,缺失时返回 fallback
|
||||
// getEnv 读取环境变量,缺失或空字符串时返回 fallback
|
||||
func getEnv(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
|
||||
Reference in New Issue
Block a user