NestJS (6 services): implement @RequirePermission decorator with SetMetadata+Reflector, register APP_GUARD globally, fix as assertions to type guards, add explicit return types, fix import type for express, fix /metrics implicit any, replace native Error with ApplicationError, remove typeorm remnants, register LifecycleService. teacher-bff: add logger, ApplicationError, GlobalErrorFilter, forward real userId to downstream, log downstream failures, migrate health controller to shared/health. Go (2 services): interface to any, doc comments, CORS dev whitelist, JWT secret fail-fast, push-gateway internal API auth, metrics and readyz endpoints, remove dead code. Python (2 services): lifespan return type, dev_mode to bool, data-ana APIRouter, ai POST body model, ClickHouse async wrapping.
86 lines
2.2 KiB
Go
86 lines
2.2 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"log"
|
||
"net/http"
|
||
"os"
|
||
"os/signal"
|
||
"syscall"
|
||
"time"
|
||
|
||
"github.com/edu-cloud/push-gateway/internal/config"
|
||
"github.com/edu-cloud/push-gateway/internal/hub"
|
||
"github.com/edu-cloud/push-gateway/internal/observability"
|
||
"github.com/edu-cloud/push-gateway/internal/ws"
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
|
||
)
|
||
|
||
func main() {
|
||
cfg := config.Load()
|
||
// 初始化 OpenTelemetry tracer(endpoint 为空时自动跳过)
|
||
tracerShutdown := observability.InitTracer("push-gateway", cfg.OTLPEndpoint)
|
||
defer tracerShutdown()
|
||
|
||
gin.SetMode(gin.ReleaseMode)
|
||
|
||
h := hub.NewHub()
|
||
wsHandler := ws.NewHandler(h, cfg.JWTSecret, cfg.DevMode, cfg.InternalAPIKey)
|
||
|
||
r := gin.New()
|
||
r.Use(gin.Recovery())
|
||
// OpenTelemetry 自动埋点(HTTP 请求/响应 span)
|
||
r.Use(otelgin.Middleware("push-gateway"))
|
||
|
||
r.GET("/healthz", func(c *gin.Context) {
|
||
c.JSON(200, gin.H{"status": "ok", "service": "push-gateway"})
|
||
})
|
||
|
||
// 就绪探针:检查 WebSocket hub 状态
|
||
r.GET("/readyz", func(c *gin.Context) {
|
||
c.JSON(200, gin.H{
|
||
"status": "ok",
|
||
"service": "push-gateway",
|
||
"connections": h.ClientCount(),
|
||
})
|
||
})
|
||
|
||
// Prometheus 指标端点
|
||
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
|
||
|
||
// WebSocket 升级端点
|
||
r.GET("/ws", wsHandler.HandleWebSocket)
|
||
|
||
// 内部推送 API(Msg 服务调用)
|
||
api := r.Group("/internal")
|
||
api.POST("/push", wsHandler.PushHandler)
|
||
api.POST("/broadcast", wsHandler.BroadcastHandler)
|
||
|
||
srv := &http.Server{
|
||
Addr: ":" + cfg.Port,
|
||
Handler: r,
|
||
ReadTimeout: 10 * time.Second,
|
||
WriteTimeout: 60 * time.Second, // WebSocket 长连接
|
||
}
|
||
|
||
go func() {
|
||
log.Printf("Push Gateway listening on :%s", cfg.Port)
|
||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||
log.Fatalf("listen: %s\n", err)
|
||
}
|
||
}()
|
||
|
||
quit := make(chan os.Signal, 1)
|
||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||
<-quit
|
||
log.Println("Shutting down Push Gateway...")
|
||
|
||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||
defer cancel()
|
||
if err := srv.Shutdown(ctx); err != nil {
|
||
log.Fatal("Server forced to shutdown:", err)
|
||
}
|
||
}
|