This commit is contained in:
2026-05-15 19:36:43 +08:00
parent 659054beb7
commit 085f59eb2b
7 changed files with 705 additions and 1 deletions
+155
View File
@@ -0,0 +1,155 @@
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