From f9c34fcf8742ea5bd7069ce80771514194de456e Mon Sep 17 00:00:00 2001 From: CN-JS-HuiBai Date: Sat, 18 Apr 2026 00:52:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=8A=82=E7=82=B9=E4=BF=A1?= =?UTF-8?q?=E6=81=AFAPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/api/main_entry.go | 2 + internal/handler/node_api.go | 12 ++++ internal/middleware/node_auth_v2.go | 108 +++++++++++++++++++++++++++- internal/service/node.go | 6 +- 4 files changed, 123 insertions(+), 5 deletions(-) diff --git a/cmd/api/main_entry.go b/cmd/api/main_entry.go index c44de5d..045ba0b 100644 --- a/cmd/api/main_entry.go +++ b/cmd/api/main_entry.go @@ -174,6 +174,7 @@ func registerServerRoutesV1(v1 *gin.RouterGroup) { uniProxy.GET("/config", handler.NodeConfig) uniProxy.GET("/user", handler.NodeUser) uniProxy.POST("/push", handler.NodePush) + uniProxy.GET("/alive", handler.NodeAlive) uniProxy.POST("/alive", handler.NodeAlive) uniProxy.GET("/alivelist", handler.NodeAliveList) uniProxy.POST("/status", handler.NodeStatus) @@ -198,6 +199,7 @@ func registerServerRoutesV2(v2 *gin.RouterGroup) { server.GET("/config", handler.NodeConfig) server.GET("/user", handler.NodeUser) server.POST("/push", handler.NodePush) + server.GET("/alive", handler.NodeAlive) server.POST("/alive", handler.NodeAlive) server.GET("/alivelist", handler.NodeAliveList) server.POST("/status", handler.NodeStatus) diff --git a/internal/handler/node_api.go b/internal/handler/node_api.go index 54a5084..30196a7 100644 --- a/internal/handler/node_api.go +++ b/internal/handler/node_api.go @@ -2,7 +2,9 @@ package handler import ( "encoding/json" + "errors" "fmt" + "io" "net/http" "strconv" "strings" @@ -197,9 +199,19 @@ func NodeTidalabSubmit(c *gin.Context) { func NodeAlive(c *gin.Context) { node := c.MustGet("node").(*model.Server) + setNodeLastCheck(node) + + if c.Request.Method == http.MethodGet { + Success(c, true) + return + } var payload map[string][]string if err := c.ShouldBindJSON(&payload); err != nil { + if errors.Is(err, io.EOF) { + Success(c, true) + return + } Fail(c, 400, "invalid payload") return } diff --git a/internal/middleware/node_auth_v2.go b/internal/middleware/node_auth_v2.go index a467ab3..e7b60c4 100644 --- a/internal/middleware/node_auth_v2.go +++ b/internal/middleware/node_auth_v2.go @@ -1,17 +1,24 @@ package middleware import ( + "bytes" + "encoding/json" + "fmt" + "io" "net/http" + "strings" "xboard-go/internal/service" "github.com/gin-gonic/gin" ) +const nodeAuthPayloadKey = "_node_auth_payload" + func NodeAuth() gin.HandlerFunc { return func(c *gin.Context) { - token := c.Query("token") - nodeID := c.Query("node_id") - nodeType := service.NormalizeServerType(c.Query("node_type")) + token := nodeAuthValue(c, "token") + nodeID := nodeAuthValue(c, "node_id") + nodeType := service.NormalizeServerType(nodeAuthValue(c, "node_type")) if token == "" || nodeID == "" { c.JSON(http.StatusUnauthorized, gin.H{"message": "missing node credentials"}) @@ -49,3 +56,98 @@ func NodeAuth() gin.HandlerFunc { c.Next() } } + +func nodeAuthValue(c *gin.Context, key string) string { + if value := strings.TrimSpace(c.Query(key)); value != "" { + return value + } + + if value := strings.TrimSpace(c.PostForm(key)); value != "" { + return value + } + + switch key { + case "token": + if value := strings.TrimSpace(c.GetHeader("X-Server-Token")); value != "" { + return value + } + if auth := strings.TrimSpace(c.GetHeader("Authorization")); len(auth) > 7 && strings.EqualFold(auth[:7], "Bearer ") { + return strings.TrimSpace(auth[7:]) + } + case "node_id": + if value := strings.TrimSpace(c.GetHeader("X-Node-Id")); value != "" { + return value + } + case "node_type": + if value := strings.TrimSpace(c.GetHeader("X-Node-Type")); value != "" { + return value + } + } + + payload := nodeAuthPayload(c) + if payload == nil { + return "" + } + + return nodeAuthString(payload[key]) +} + +func nodeAuthPayload(c *gin.Context) map[string]any { + if cached, ok := c.Get(nodeAuthPayloadKey); ok { + if payload, ok := cached.(map[string]any); ok { + return payload + } + return nil + } + + if c.Request == nil || c.Request.Body == nil { + c.Set(nodeAuthPayloadKey, map[string]any(nil)) + return nil + } + + contentType := strings.ToLower(c.GetHeader("Content-Type")) + if !strings.Contains(contentType, "application/json") { + c.Set(nodeAuthPayloadKey, map[string]any(nil)) + return nil + } + + body, err := io.ReadAll(c.Request.Body) + if err != nil { + c.Request.Body = io.NopCloser(bytes.NewBuffer(nil)) + c.Set(nodeAuthPayloadKey, map[string]any(nil)) + return nil + } + c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) + + if len(bytes.TrimSpace(body)) == 0 { + c.Set(nodeAuthPayloadKey, map[string]any(nil)) + return nil + } + + var payload map[string]any + if err := json.Unmarshal(body, &payload); err != nil { + c.Set(nodeAuthPayloadKey, map[string]any(nil)) + return nil + } + + c.Set(nodeAuthPayloadKey, payload) + return payload +} + +func nodeAuthString(value any) string { + switch typed := value.(type) { + case string: + return strings.TrimSpace(typed) + case json.Number: + return strings.TrimSpace(typed.String()) + default: + if value == nil { + return "" + } + text := strings.TrimSpace(fmt.Sprint(value)) + if text == "" { + return "" + } + return text + } +} diff --git a/internal/service/node.go b/internal/service/node.go index c6d3f73..947baf6 100644 --- a/internal/service/node.go +++ b/internal/service/node.go @@ -17,6 +17,7 @@ import ( var serverTypeAliases = map[string]string{ "v2ray": "vmess", + "v2node": "", "hysteria2": "hysteria", } @@ -117,10 +118,11 @@ func NormalizeServerType(serverType string) string { } func IsValidServerType(serverType string) bool { - if serverType == "" { + normalized := NormalizeServerType(serverType) + if normalized == "" { return true } - _, ok := validServerTypes[NormalizeServerType(serverType)] + _, ok := validServerTypes[normalized] return ok }