package router import ( "context" "fmt" "net/http" "strings" "time" "github.com/gin-gonic/gin" "infogenie-backend/config" "infogenie-backend/internal/cache" "infogenie-backend/internal/database" "infogenie-backend/internal/handler" "infogenie-backend/internal/middleware" ) func probeSixtyPublicAPI(ctx context.Context, base string) (ok bool, httpStatus int, errMsg string, ms int64, probeURL string) { b := strings.TrimSpace(base) if b == "" { return false, 0, "empty_base", 0, "" } probeURL = strings.TrimRight(b, "/") + "/v2/ip" client := &http.Client{Timeout: 6 * time.Second} t0 := time.Now() req, err := http.NewRequestWithContext(ctx, http.MethodGet, probeURL, nil) if err != nil { return false, 0, err.Error(), time.Since(t0).Milliseconds(), probeURL } resp, err := client.Do(req) ms = time.Since(t0).Milliseconds() if err != nil { return false, 0, err.Error(), ms, probeURL } _ = resp.Body.Close() ok = resp.StatusCode >= 200 && resp.StatusCode < 500 if !ok && errMsg == "" { errMsg = fmt.Sprintf("http_%d", resp.StatusCode) } return ok, resp.StatusCode, "", ms, probeURL } func Setup(r *gin.Engine) { r.Use(middleware.CORS()) authH := handler.NewAuthHandler() userH := handler.NewUserHandler() aiH := handler.NewAIModelHandler() siteH := handler.NewSiteConfigHandler() aiRtH := handler.NewAIRuntimeHandler() r.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "万象口袋 后端 API 服务运行中", "description": "提供AI模型应用接口,用户认证由萌芽账户认证中心提供", "version": "3.3.0-go", "timestamp": time.Now().Format(time.RFC3339), "endpoints": gin.H{ "auth": "/api/auth (via 萌芽认证中心)", "user": "/api/user", "aimodelapp": "/api/aimodelapp", "site": "/api/site (含 feature-card-clicks)", "admin_site": "/api/admin/site/*", }, }) }) // 健康检查:数据库 Ping + 当前配置的 60s 上游轻量探测(GET …/v2/ip) r.GET("/api/health", func(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), 8*time.Second) defer cancel() dbStatus := "connected" if database.DB != nil { sqlDB, err := database.DB.DB() if err != nil || sqlDB.PingContext(ctx) != nil { dbStatus = "disconnected" } } else { dbStatus = "not_initialized" } mysqlOK := dbStatus == "connected" sid, sixtyBase, sixtyLabel := handler.EffectiveSixtyUpstream(database.DB) sixtyOK, sixtyHTTP, sixtyErr, sixtyMs, probeURL := probeSixtyPublicAPI(ctx, sixtyBase) if !sixtyOK && sixtyErr == "" && sixtyHTTP >= 500 { sixtyErr = fmt.Sprintf("http_%d", sixtyHTTP) } overall := "running" if !mysqlOK || !sixtyOK { overall = "degraded" } redisHealth := gin.H{"enabled": false} if config.Cfg != nil && config.Cfg.Redis.Enabled { if err := cache.Ping(ctx); err != nil { redisHealth = gin.H{"enabled": true, "ok": false, "error": err.Error()} } else { redisHealth = gin.H{"enabled": true, "ok": true} } } c.JSON(http.StatusOK, gin.H{ "status": overall, "timestamp": time.Now().Format(time.RFC3339), "database": dbStatus, "mysql": gin.H{ "ok": mysqlOK, "status": dbStatus, }, "redis": redisHealth, "backend_api": gin.H{ "ok": true, }, "sixty_api": gin.H{ "ok": sixtyOK, "source_id": sid, "base_url": sixtyBase, "label": sixtyLabel, "probe_url": probeURL, "http_status": sixtyHTTP, "latency_ms": sixtyMs, "error": sixtyErr, }, }) }) auth := r.Group("/api/auth") { auth.GET("/check", middleware.OptionalJWTAuth(), authH.Check) } user := r.Group("/api/user", middleware.JWTAuth()) { user.GET("/profile", userH.GetProfile) } // 站点公开配置(无需登录) r.GET("/api/site/60s-disabled", siteH.Get60sDisabled) r.GET("/api/site/60s-source", siteH.Get60sSource) r.GET("/api/site/ai-model-disabled", siteH.GetAIModelDisabled) r.GET("/api/site/feature-card-clicks", siteH.GetFeatureCardClicks) r.POST("/api/site/feature-card-clicks/increment", siteH.PostFeatureCardClickIncrement) r.PUT("/api/admin/site/60s-disabled", siteH.Put60sDisabled) r.PUT("/api/admin/site/60s-source", siteH.Put60sSource) r.PUT("/api/admin/site/ai-model-disabled", siteH.PutAIModelDisabled) r.GET("/api/admin/site/ai-runtime", aiRtH.GetAIRuntime) r.PUT("/api/admin/site/ai-runtime", aiRtH.PutAIRuntime) r.GET("/api/admin/site/diagnostics", siteH.GetDiagnostics) ai := r.Group("/api/aimodelapp") { ai.POST("/chat", middleware.JWTAuth(), aiH.Chat) ai.POST("/chat/stream", middleware.JWTAuth(), aiH.ChatStream) ai.POST("/name-analysis", middleware.JWTAuth(), aiH.NameAnalysis) ai.POST("/variable-naming", middleware.JWTAuth(), aiH.VariableNaming) ai.POST("/poetry", middleware.JWTAuth(), aiH.Poetry) ai.POST("/translation", middleware.JWTAuth(), aiH.Translation) ai.POST("/classical_conversion", middleware.JWTAuth(), aiH.ClassicalConversion) ai.POST("/expression-maker", middleware.JWTAuth(), aiH.ExpressionMaker) ai.POST("/linux-command", middleware.JWTAuth(), aiH.LinuxCommand) ai.POST("/markdown_formatting", middleware.JWTAuth(), aiH.MarkdownFormatting) ai.POST("/kinship-calculator", middleware.JWTAuth(), aiH.KinshipCalculator) // models 端点添加认证保护 ai.GET("/models", middleware.JWTAuth(), aiH.GetModels) } }