chore(api-gateway): merge review docs and update dependencies

This commit is contained in:
SpecialX
2026-07-10 16:50:33 +08:00
parent 8e91039896
commit 9e767b4e95
11 changed files with 1246 additions and 152 deletions

View File

@@ -1,28 +1,50 @@
// Package config loads runtime configuration for push-gateway from environment
// variables. Required variables (JWT_SECRET in production, INTERNAL_API_TOKEN
// when not in DevMode) cause a fatal exit when missing.
package config
import (
"log"
"os"
"strings"
"github.com/edu-cloud/shared-go/env"
)
// Config 持有 push-gateway 运行时配置
// Config holds all push-gateway runtime configuration.
type Config struct {
Port string
JWTSecret string
DevMode bool
RedisURL string
OTLPEndpoint string
InternalAPIKey string
Port string
JWTSecret string
DevMode bool
RedisURL string
OTLPEndpoint string
InternalAPIToken string
// WebSocket
AllowedOrigins []string
MaxConnsPerUser int
HeartbeatInterval int // seconds
// JWT RS256 (via shared-go/jwks, iam /.well-known/jwks.json)
JWKSURL string
// Kafka
KafkaBrokers []string
KafkaNotificationTopic string
KafkaConsumerGroup string
// Instance identity (for Redis SET membership)
InstanceID string
}
// devJWTSecret 是 DevMode 下的默认 JWT 密钥(仅用于本地联调,生产必须配置 JWT_SECRET
// devJWTSecret is the default JWT secret used only in DevMode for local
// development. Production deployments MUST set JWT_SECRET.
const devJWTSecret = "p1-dev-secret-change-in-production"
// Load 从环境变量加载配置。
// 非 DevMode 下若 JWT_SECRET 未配置则 fatal 退出DevMode 下使用默认密钥并打印 warning。
// Load reads configuration from environment variables.
//
// In DevMode (DEV_MODE=true) JWT_SECRET defaults to a dev value and internal
// API token validation is skipped; in production an unset JWT_SECRET or
// INTERNAL_API_TOKEN is fatal.
func Load() *Config {
devMode := getEnv("DEV_MODE", "false") == "true"
jwtSecret := getEnv("JWT_SECRET", "")
devMode := env.GetBool("DEV_MODE", false)
jwtSecret := env.Get("JWT_SECRET", "")
if jwtSecret == "" {
if devMode {
log.Println("warning: JWT_SECRET not set, using dev default (DEV_MODE=true)")
@@ -32,20 +54,64 @@ func Load() *Config {
}
}
internalToken := env.Get("INTERNAL_API_TOKEN", "")
if internalToken == "" && !devMode {
log.Fatal("INTERNAL_API_TOKEN must be set in non-dev mode")
}
return &Config{
Port: getEnv("PUSH_GATEWAY_PORT", "8081"),
JWTSecret: jwtSecret,
DevMode: devMode,
RedisURL: getEnv("REDIS_URL", ""),
OTLPEndpoint: getEnv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318"),
InternalAPIKey: getEnv("INTERNAL_API_KEY", ""),
Port: env.Get("PUSH_GATEWAY_PORT", "8081"),
JWTSecret: jwtSecret,
DevMode: devMode,
RedisURL: env.Get("REDIS_URL", "redis://localhost:6379/0"),
OTLPEndpoint: env.Get("OTEL_EXPORTER_OTLP_ENDPOINT", "localhost:4318"),
InternalAPIToken: internalToken,
AllowedOrigins: parseOrigins(env.Get("WS_ALLOWED_ORIGINS", "")),
MaxConnsPerUser: env.GetInt("MAX_CONNS_PER_USER", 5),
HeartbeatInterval: env.GetInt("HEARTBEAT_INTERVAL_SECONDS", 30),
JWKSURL: env.Get("JWKS_URL", "http://localhost:50052/.well-known/jwks.json"),
KafkaBrokers: parseBrokers(env.Get("KAFKA_BROKERS", "localhost:9092")),
KafkaNotificationTopic: env.Get("KAFKA_NOTIFICATION_TOPIC", "edu.notification.requested"),
KafkaConsumerGroup: env.Get("KAFKA_CONSUMER_GROUP", "push-gateway"),
InstanceID: env.Get("INSTANCE_ID", generateInstanceID()),
}
}
// getEnv 读取环境变量,缺失时返回 fallback
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
// parseOrigins splits a comma-separated list of allowed WebSocket origins.
// Empty string yields nil (all origins rejected in production).
func parseOrigins(raw string) []string {
if raw == "" {
return nil
}
return fallback
parts := strings.Split(raw, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
if trimmed := strings.TrimSpace(p); trimmed != "" {
out = append(out, trimmed)
}
}
return out
}
// parseBrokers splits a comma-separated list of Kafka broker addresses.
func parseBrokers(raw string) []string {
parts := strings.Split(raw, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
if trimmed := strings.TrimSpace(p); trimmed != "" {
out = append(out, trimmed)
}
}
return out
}
// generateInstanceID returns a stable process-unique identifier. Falls back to
// the hostname when INSTANCE_ID is not explicitly set, allowing each replica to
// be uniquely identifiable in the Redis online-presence SET.
func generateInstanceID() string {
host, err := os.Hostname()
if err != nil || host == "" {
return "push-gateway-unknown"
}
return host
}