package api import ( "context" "net/http" "strconv" "clash-speed-test/internal/config" "clash-speed-test/internal/core" "clash-speed-test/internal/database" "clash-speed-test/internal/logger" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type Server struct { config *config.Config db *gorm.DB speedTester *core.SpeedTester scheduler *core.Scheduler router *gin.Engine server *http.Server } func NewServer(cfg *config.Config, db *gorm.DB, speedTester *core.SpeedTester, scheduler *core.Scheduler) *Server { gin.SetMode(gin.ReleaseMode) router := gin.New() router.Use(gin.Recovery()) server := &Server{ config: cfg, db: db, speedTester: speedTester, scheduler: scheduler, router: router, } server.setupRoutes() return server } func (s *Server) setupRoutes() { // 健康检查 s.router.GET("/health", s.healthCheck) // API路由组 api := s.router.Group("/api") { // 节点管理 api.GET("/nodes", s.getNodes) api.POST("/nodes", s.createNode) api.PUT("/nodes/:id", s.updateNode) api.DELETE("/nodes/:id", s.deleteNode) // 测速相关 api.GET("/speed-test", s.getSpeedTestResults) api.POST("/speed-test/trigger", s.triggerSpeedTest) api.GET("/speed-test/history/:nodeId", s.getNodeTestHistory) // 系统状态 api.GET("/status", s.getSystemStatus) } // 静态文件服务 s.router.Static("/static", "./static") s.router.LoadHTMLGlob("templates/*") // 主页 s.router.GET("/", s.index) } // 启动服务器 func (s *Server) Start() error { addr := s.config.Server.Host + ":" + strconv.Itoa(s.config.Server.Port) s.server = &http.Server{ Addr: addr, Handler: s.router, } return s.server.ListenAndServe() } // 关闭服务器 func (s *Server) Shutdown(ctx context.Context) error { return s.server.Shutdown(ctx) } // 健康检查 func (s *Server) healthCheck(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "ok", "service": "clash-speed-test", "version": "1.0.0", }) } // 主页 func (s *Server) index(c *gin.Context) { c.HTML(http.StatusOK, "index.html", gin.H{ "title": "Clash Speed Test", }) } // 获取所有节点 func (s *Server) getNodes(c *gin.Context) { var nodes []database.Node if err := s.db.Find(&nodes).Error; err != nil { logger.Error("获取节点列表失败", map[string]interface{}{ "error": err.Error(), }) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取节点列表失败"}) return } c.JSON(http.StatusOK, gin.H{ "data": nodes, }) } // 创建节点 func (s *Server) createNode(c *gin.Context) { var node database.Node if err := c.ShouldBindJSON(&node); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"}) return } if err := s.db.Create(&node).Error; err != nil { logger.Error("创建节点失败", map[string]interface{}{ "error": err.Error(), }) c.JSON(http.StatusInternalServerError, gin.H{"error": "创建节点失败"}) return } c.JSON(http.StatusCreated, gin.H{ "data": node, }) } // 更新节点 func (s *Server) updateNode(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的节点ID"}) return } var node database.Node if err := s.db.First(&node, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "节点不存在"}) return } var updateData database.Node if err := c.ShouldBindJSON(&updateData); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"}) return } if err := s.db.Model(&node).Updates(updateData).Error; err != nil { logger.Error("更新节点失败", map[string]interface{}{ "error": err.Error(), }) c.JSON(http.StatusInternalServerError, gin.H{"error": "更新节点失败"}) return } c.JSON(http.StatusOK, gin.H{ "data": node, }) } // 删除节点 func (s *Server) deleteNode(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的节点ID"}) return } if err := s.db.Delete(&database.Node{}, id).Error; err != nil { logger.Error("删除节点失败", map[string]interface{}{ "error": err.Error(), }) c.JSON(http.StatusInternalServerError, gin.H{"error": "删除节点失败"}) return } c.JSON(http.StatusOK, gin.H{"message": "节点删除成功"}) } // 获取测速结果 func (s *Server) getSpeedTestResults(c *gin.Context) { limit := 50 if limitStr := c.Query("limit"); limitStr != "" { if l, err := strconv.Atoi(limitStr); err == nil && l > 0 { limit = l } } results, err := database.GetRecentTestResults(limit) if err != nil { logger.Error("获取测速结果失败", map[string]interface{}{ "error": err.Error(), }) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取测速结果失败"}) return } c.JSON(http.StatusOK, gin.H{ "data": results, }) } // 手动触发测速 func (s *Server) triggerSpeedTest(c *gin.Context) { s.scheduler.TriggerSpeedTest() c.JSON(http.StatusOK, gin.H{ "message": "测速任务已触发", }) } // 获取节点测试历史 func (s *Server) getNodeTestHistory(c *gin.Context) { nodeID, err := strconv.ParseUint(c.Param("nodeId"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的节点ID"}) return } limit := 20 if limitStr := c.Query("limit"); limitStr != "" { if l, err := strconv.Atoi(limitStr); err == nil && l > 0 { limit = l } } results, err := database.GetNodeTestHistory(uint(nodeID), limit) if err != nil { logger.Error("获取节点测试历史失败", map[string]interface{}{ "error": err.Error(), }) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取节点测试历史失败"}) return } c.JSON(http.StatusOK, gin.H{ "data": results, }) } // 获取系统状态 func (s *Server) getSystemStatus(c *gin.Context) { // 获取节点统计 var totalNodes, activeNodes int64 s.db.Model(&database.Node{}).Count(&totalNodes) s.db.Model(&database.Node{}).Where("is_active = ?", true).Count(&activeNodes) // 获取测试结果统计 var totalTests, successTests int64 s.db.Model(&database.TestResult{}).Count(&totalTests) s.db.Model(&database.TestResult{}).Where("is_success = ?", true).Count(&successTests) // 计算平均延迟 var avgLatency float64 s.db.Model(&database.TestResult{}). Where("is_success = ? AND latency IS NOT NULL", true). Select("AVG(latency)"). Scan(&avgLatency) // 计算成功率 var successRate float64 if totalTests > 0 { successRate = float64(successTests) / float64(totalTests) * 100 } c.JSON(http.StatusOK, gin.H{ "total_nodes": totalNodes, "active_nodes": activeNodes, "avg_latency": int(avgLatency), "success_rate": int(successRate), "scheduler_running": s.scheduler.IsRunning(), }) }