157 lines
5.0 KiB
Go
157 lines
5.0 KiB
Go
package router
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"net/http"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"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"
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"status": overall,
|
||
"timestamp": time.Now().Format(time.RFC3339),
|
||
"database": dbStatus,
|
||
"mysql": gin.H{
|
||
"ok": mysqlOK,
|
||
"status": dbStatus,
|
||
},
|
||
"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)
|
||
|
||
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)
|
||
}
|
||
}
|