Files
Edu/services/api-gateway/internal/config/config.go
SpecialX 61d824924a fix: 修复集成测试中发现的全部 bug — 15 服务端到端验证通过
修复涵盖 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(集成测试修复会话)
2026-07-11 01:41:46 +08:00

112 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package config
import (
"log/slog"
"os"
"strconv"
)
// Config 持有 api-gateway 运行时配置。
// P2 起 JWT 验签改 RS256IAM 签发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 / developmentW7 防护用)
}
// 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_URLJWKS 公钥端点),
// 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 URLRS256 验签)
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
}