Selaa lähdekoodia

feat(GameShield): 添加获取在线SDK列表功能

- 新增 GameShieldRuleIdRequest 结构体用于处理规则ID请求
- 添加 SDKInfo 结构体表示SDK在线信息- 实现 GetGameShieldOnlineList 接口和服务方法
- 添加 FetchPageContent 方法以获取页面内容
- 实现 ParseSDKOnlineHTMLTable 方法解析SDK在线情况表格- 更新 HTTP路由以支持新功能
fusu 3 kuukautta sitten
vanhempi
sitoutus
592e881879

+ 14 - 2
api/v1/GameShield.go

@@ -41,6 +41,18 @@ type KeyAndFieldResponse struct {
 	FieldId int
 }
 
-type DelGameShieldRequest struct {
-	RuleId int `json:"id" form:"id"`
+type GameShieldRuleIdRequest struct {
+	RuleId int `json:"rule_id" form:"rule_id" binding:"required"`
+}
+
+type SDKInfo struct {
+	RuleID     string `json:"rule_id"`     // 规则ID
+	ClientIP   string `json:"client_ip"`   // 客户端IP
+	GatewayIP  string `json:"gateway_ip"`  // 网关IP
+	SDKUUID    string `json:"sdk_uuid"`    // SDK-UUID
+	SessionID  string `json:"session_id"`  // 会话ID
+	SDKType    string `json:"sdk_type"`    // SDK类型
+	SDKVersion string `json:"sdk_version"` // SDK版本
+	System     string `json:"system"`      // 系统
+	ExtraInfo  string `json:"extra_info"`  // 附加信息
 }

+ 15 - 1
internal/handler/gameshield.go

@@ -88,7 +88,7 @@ func (h *GameShieldHandler) GetGameShieldKey(ctx *gin.Context) {
 }
 
 func (h *GameShieldHandler) DeleteGameShield(ctx *gin.Context) {
-	req := new(v1.DelGameShieldRequest)
+	req := new(v1.GameShieldRuleIdRequest)
 	if err := ctx.ShouldBind(req); err != nil {
 		v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, err.Error())
 		return
@@ -100,3 +100,17 @@ func (h *GameShieldHandler) DeleteGameShield(ctx *gin.Context) {
 	}
 	v1.HandleSuccess(ctx, res)
 }
+
+func (h *GameShieldHandler) GetGameShieldOnlineList(ctx *gin.Context) {
+	req := new(v1.GameShieldRuleIdRequest)
+	if err := ctx.ShouldBind(req); err != nil {
+		v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, err.Error())
+		return
+	}
+	res, err := h.gameShieldService.GetGameShieldOnlineList(ctx, req.RuleId)
+	if err != nil {
+		v1.HandleError(ctx, http.StatusInternalServerError, err, err.Error())
+		return
+	}
+	v1.HandleSuccess(ctx, res)
+}

+ 1 - 0
internal/server/http.go

@@ -82,6 +82,7 @@ func NewHTTPServer(
 			noAuthRouter.POST("/gameShield/getKey", gameShieldHandler.GetGameShieldKey)
 			noAuthRouter.POST("/gameShield/edit", gameShieldHandler.EditGameShield)
 			noAuthRouter.POST("/gameShield/delete", gameShieldHandler.DeleteGameShield)
+			noAuthRouter.POST("/gameShield/getOnline", gameShieldHandler.GetGameShieldOnlineList)
 			noAuthRouter.POST("/webForward/add", webForwardingHandler.AddWebForwarding)
 			noAuthRouter.POST("/webForward/edit", webForwardingHandler.EditWebForwarding)
 			noAuthRouter.POST("/webForward/delete", webForwardingHandler.DeleteWebForwarding)

+ 65 - 0
internal/service/gameShieldCrawler.go

@@ -2,6 +2,8 @@ package service
 
 import (
 	"bytes"
+	"compress/flate"
+	"compress/gzip"
 	"context"
 	"crypto/rand"
 	"crypto/tls"
@@ -16,6 +18,7 @@ import (
 	"net/http"
 	"net/url"
 	"strings"
+	"time"
 )
 
 type CrawlerService interface {
@@ -25,6 +28,7 @@ type CrawlerService interface {
 	GetField(ctx context.Context, appName string) (map[string]interface{}, error)
 	GetKey(ctx context.Context, appName string) (string, error)
 	DeleteRule(ctx context.Context, ruleID int, ruleUrl string) (string, error)
+	FetchPageContent(ctx context.Context, url string, cookie string) ([]byte, error)
 }
 
 type CrawlerConfig struct {
@@ -297,3 +301,64 @@ func (service *crawlerService) DeleteRule(ctx context.Context, ruleID int, ruleU
 
 	return res, nil
 }
+
+func (service *crawlerService) FetchPageContent(ctx context.Context, url string, cookie string) ([]byte, error) {
+	fetchUrl := service.config.URL + url
+	// 配置 HTTP 客户端
+	client := &http.Client{
+		Transport: &http.Transport{
+			TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
+			MaxIdleConns:        100,
+			MaxIdleConnsPerHost: 100,
+			IdleConnTimeout:     90 * time.Second,
+		},
+		Timeout: 30 * time.Second,
+	}
+
+	// 构造请求
+	req, err := http.NewRequestWithContext(ctx, "GET", fetchUrl, nil)
+	if err != nil {
+		return nil, fmt.Errorf("创建请求失败: %v", err)
+	}
+
+	// 设置请求头
+	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
+	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8")
+	req.Header.Set("Accept-Encoding", "gzip, deflate, br")
+	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7")
+	req.Header.Set("Cookie", cookie)
+
+	// 发起请求
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("请求发送失败: %v", err)
+	}
+	defer resp.Body.Close()
+
+	// 检查响应状态码
+	if resp.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf("请求失败,状态码: %d", resp.StatusCode)
+	}
+
+	// 处理压缩响应
+	var reader io.Reader = resp.Body
+	switch resp.Header.Get("Content-Encoding") {
+	case "gzip":
+		gzipReader, err := gzip.NewReader(resp.Body)
+		if err != nil {
+			return nil, fmt.Errorf("解压 gzip 响应失败: %v", err)
+		}
+		defer gzipReader.Close()
+		reader = gzipReader
+	case "deflate":
+		reader = flate.NewReader(resp.Body)
+	}
+
+	// 读取响应内容
+	content, err := io.ReadAll(reader)
+	if err != nil {
+		return nil, fmt.Errorf("读取响应内容失败: %v", err)
+	}
+
+	return content, nil
+}

+ 21 - 0
internal/service/gameshield.go

@@ -17,6 +17,7 @@ type GameShieldService interface {
 	DeleteGameShield(ctx context.Context, req int) (string, error)
 	GetGameShieldKey(ctx context.Context, id int) (string, error)
 	GetKeyAndEditGameShield(ctx context.Context, hostId int, dunName string) (string, error)
+	GetGameShieldOnlineList(ctx context.Context, hostId int) ([]v1.SDKInfo, error)
 }
 
 func NewGameShieldService(
@@ -167,3 +168,23 @@ func (service *gameShieldService) GetKeyAndEditGameShield(ctx context.Context, h
 	}
 	return "", nil
 }
+
+func (service *gameShieldService) GetGameShieldOnlineList(ctx context.Context, hostId int) ([]v1.SDKInfo, error) {
+	strHostId := strconv.Itoa(hostId)
+	cookie, err := service.required.Required(ctx)
+	if err != nil {
+		return nil, err
+	}
+	respBody, err := service.crawlerService.FetchPageContent(ctx, "admin/sdk/online?rule_id="+strHostId, cookie)
+	if err != nil {
+		return nil, err
+	}
+	res, err := service.parser.ParseSDKOnlineHTMLTable(string(respBody))
+	if err != nil {
+		return nil, err
+	}
+	if len(res) == 0 {
+		return nil, fmt.Errorf("暂无数据")
+	}
+	return res, nil
+}

+ 57 - 0
internal/service/parser.go

@@ -6,6 +6,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"github.com/PuerkitoBio/goquery"
+	v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
 	"strings"
 )
 
@@ -13,6 +14,7 @@ type ParserService interface {
 	GetMessage(ctx context.Context, req []byte) (string, error)
 	ParseAlert(html string) (message string, err error)
 	GetRuleId(ctx context.Context, htmlBytes []byte) (string, error)
+	ParseSDKOnlineHTMLTable(htmlContent string) ([]v1.SDKInfo, error)
 }
 
 func NewParserService(
@@ -80,3 +82,58 @@ func (s *parserService) GetRuleId(ctx context.Context, htmlBytes []byte) (string
 		Find("td").Eq(1).Text()             // 第 2 个 td
 	return strings.TrimSpace(id), nil
 }
+
+// 解析 Sdk在线情况 表格
+func (s *parserService) ParseSDKOnlineHTMLTable(htmlContent string) ([]v1.SDKInfo, error) {
+	// 创建goquery文档
+	doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
+	if err != nil {
+		return nil, fmt.Errorf("解析HTML失败: %v", err)
+	}
+
+	var sdkInfos []v1.SDKInfo
+
+	// 查找表格并解析数据行
+	doc.Find("table.table.table-hover tbody tr").Each(func(i int, s *goquery.Selection) {
+		// 跳过表头行(如果有的话)
+		if s.Find("th").Length() > 0 {
+			return
+		}
+
+		var info v1.SDKInfo
+
+		// 解析每一列的数据
+		s.Find("td").Each(func(j int, td *goquery.Selection) {
+			text := strings.TrimSpace(td.Text())
+
+			// 根据列的位置分配到对应字段(跳过第一列的复选框)
+			switch j {
+			case 1: // 规则ID
+				info.RuleID = text
+			case 2: // 客户端IP
+				info.ClientIP = text
+			case 3: // 网关IP
+				info.GatewayIP = text
+			case 4: // SDK-UUID
+				info.SDKUUID = text
+			case 5: // 会话ID
+				info.SessionID = text
+			case 6: // SDK类型
+				info.SDKType = text
+			case 7: // SDK版本
+				info.SDKVersion = text
+			case 8: // 系统
+				info.System = text
+			case 9: // 附加信息
+				info.ExtraInfo = text
+			}
+		})
+
+		// 只有当规则ID不为空时才添加记录
+		if info.RuleID != "" {
+			sdkInfos = append(sdkInfos, info)
+		}
+	})
+
+	return sdkInfos, nil
+}