修复涵盖 6 大类问题: 1. api-gateway - 路径前缀剥离 /api 而非 /api/v1,保留下游 /v1/ controller 前缀 - JWKS URL 默认值修复 - publicPaths 白名单对齐 /v1/iam/* 2. iam - iam.module.ts exports 补充 PermissionCacheService 和 IamRepository - main.ts resolveProtoPath() 多路径探测 proto 文件 3. core-edu - app.module.ts AuthMiddleware 全局注册 4. BFF 层 GraphQL 端点(teacher-bff / parent-bff / student-bff) - teacher-bff: mock dataScope OWN→SELF 对齐 GraphQL enum;WHATWG Request header .get() 提取 - parent-bff: handleNodeRequestAndResponse 不存在 → 直接 yoga(req,res);WHATWG Request header .get() 提取 - student-bff: auth.resolver 移除 ActionState 信封返回扁平对象;WHATWG Request header .get() 提取 5. ai - Kafka 事务降级 + 10s 超时 - gRPC 拦截器降级 - dev mode 禁用事务模式 6. 前端 + 共享包 - teacher-portal: MF 插件条件实例化 + transpilePackages + extensionAlias - ui-components: error-boundary.tsx 添加 use client - ui-tokens: tailwind-theme.css 移除 @layer base - shared-ts: 导出从源码改为 dist 编译产物;OutboxModule global:true 7. infra - .gitignore 补充 keys/ *.pem *.key secrets/ 排除规则 - infra/init-sql/02-all-services-schema.sql 36 张表 DDL 验证结果: - TS typecheck: 19 个 workspace 项目全部通过 - Go vet + Ruff: 通过 - 15 服务全部启动成功 - 3 个 BFF GraphQL 端点 + 4 个前端页面全部 200 - Gateway → iam → core-edu 端到端链路验证通过 AI identity: trae-main(集成测试修复会话)
112 lines
3.9 KiB
Go
112 lines
3.9 KiB
Go
package config
|
||
|
||
import (
|
||
"log/slog"
|
||
"os"
|
||
"strconv"
|
||
)
|
||
|
||
// Config 持有 api-gateway 运行时配置。
|
||
// P2 起 JWT 验签改 RS256(IAM 签发,Gateway 用 JWKS 公钥校验),
|
||
// JWTSecret 仅在 DevMode 下作为 mock 密钥保留。
|
||
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
|
||
TeacherBffURL string
|
||
StudentBffURL string
|
||
ParentBffURL string
|
||
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"),
|
||
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"),
|
||
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
|
||
}
|