package http import ( "context" _ "embed" "log" "meshgo/config" "meshgo/stats" "net/http" "time" "github.com/gin-gonic/gin" ) // Server HTTP 管理界面服务 type Server struct { srv *http.Server enabled bool } // New 创建 HTTP 服务(不启动) func New(cfg *config.AdminConfig) *Server { if !cfg.Enabled { return &Server{enabled: false} } gin.SetMode(gin.ReleaseMode) r := gin.New() r.Use(gin.Recovery()) // 预留认证中间件扩展点 r.Use(AuthMiddleware(cfg)) // 路由 admin := r.Group("/admin/mqtt") { admin.GET("/api/stats", handleStats) admin.GET("/api/clients", handleClients) } r.GET("/admin/mqtt", serveIndex) r.GET("/", serveHome) addr := cfg.Port if addr == "" { addr = ":8080" } return &Server{ enabled: true, srv: &http.Server{ Addr: addr, Handler: r, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, }, } } // Start 启动 HTTP 服务(goroutine) func (s *Server) Start() { if !s.enabled { return } go func() { addr := s.srv.Addr log.Printf("[http] 管理界面已启动: http://%s/", addr) if err := s.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Printf("[http] 管理界面启动失败: %v", err) } }() } // Close 优雅关闭 func (s *Server) Close() error { if !s.enabled { return nil } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() return s.srv.Shutdown(ctx) } // Enabled 返回是否启用 func (s *Server) Enabled() bool { return s.enabled } // --------------------------------------------------------------------------- // 路由处理 // --------------------------------------------------------------------------- // serveIndex 返回管理页面 func serveIndex(c *gin.Context) { c.Header("Content-Type", "text/html; charset=utf-8") c.String(http.StatusOK, indexHTML) } // handleStats 返回实时统计(JSON) func handleStats(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": 0, "data": stats.GetStats(), }) } // handleClients 返回在线客户端列表(JSON) func handleClients(c *gin.Context) { st := stats.GetStats() c.JSON(http.StatusOK, gin.H{ "code": 0, "data": st.Clients, }) } // --------------------------------------------------------------------------- // 认证中间件(预留扩展点) // AuthMiddleware 根据 cfg.AuthType 选择对应认证策略 // 当前支持:none // 预留支持:basic、token // --------------------------------------------------------------------------- // AuthMiddleware 认证中间件工厂 func AuthMiddleware(cfg *config.AdminConfig) gin.HandlerFunc { switch cfg.AuthType { case "basic": return basicAuth(cfg.Username, cfg.Password) case "token": // TODO: token 认证 return func(c *gin.Context) { c.Next() } default: // none:不做认证 return func(c *gin.Context) { c.Next() } } } // basicAuth Basic 认证 func basicAuth(user, pass string) gin.HandlerFunc { return func(c *gin.Context) { u, p, ok := c.Request.BasicAuth() if !ok || u != user || p != pass { c.Header("WWW-Authenticate", `Basic realm="meshgo"`) c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "code": 401, "msg": "未授权", }) return } c.Next() } } // --------------------------------------------------------------------------- // 模板(go:embed 内嵌) // --------------------------------------------------------------------------- //go:embed index.html var indexHTML string