Browse Source

feat(GameShield): 添加检测SDK Key是否存在且过期的功能

- 在 GameShield API 中新增 IsExistGameShieldKey 接口
- 实现后端服务和解析逻辑,检查指定的 SDK Key 是否存在并获取其状态
- 如果 Key存在但已过期,返回相应错误信息
- 优化 KeyAndFieldResponse 结构,添加 JSON 和 form 标签
fusu 2 months ago
parent
commit
20679de8bf

+ 1 - 1
api/v1/GameShield.go

@@ -38,7 +38,7 @@ type GetGameShieldRequiredResponse struct {
 }
 
 type KeyAndFieldResponse struct {
-	Key     string
+	Key     string `json:"key" form:"key"`
 	FieldId int
 }
 

+ 14 - 0
internal/handler/gameshield.go

@@ -114,3 +114,17 @@ func (h *GameShieldHandler) GetGameShieldOnlineList(ctx *gin.Context) {
 	}
 	v1.HandleSuccess(ctx, res)
 }
+
+func (h *GameShieldHandler) IsExistGameShieldKey(ctx *gin.Context) {
+	req := new(v1.KeyAndFieldResponse)
+	if err := ctx.ShouldBind(req); err != nil {
+		v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, err.Error())
+		return
+	}
+	res, err := h.gameShieldService.IsExistGameShieldKey(ctx, req.Key)
+	if err != nil {
+		v1.HandleError(ctx, http.StatusInternalServerError, err, err.Error())
+		return
+	}
+	v1.HandleSuccess(ctx, res)
+}

+ 1 - 0
internal/server/http.go

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

+ 16 - 0
internal/service/gameshield.go

@@ -18,6 +18,7 @@ type GameShieldService interface {
 	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)
+	IsExistGameShieldKey(ctx context.Context, key string) (string, error)
 }
 
 func NewGameShieldService(
@@ -188,3 +189,18 @@ func (service *gameShieldService) GetGameShieldOnlineList(ctx context.Context, h
 	}
 	return res, nil
 }
+
+func (service *gameShieldService) IsExistGameShieldKey(ctx context.Context, key string) (string, error) {
+	cookie, err := service.required.Required(ctx)
+	if err != nil {
+		return "", err
+	}
+	respBody, err := service.crawlerService.FetchPageContent(ctx, "admin/info/rule", cookie)
+	if err != nil {
+		return "", err
+	}
+	if err := service.parser.CheckSDKKeyStatus(string(respBody), key); err != nil {
+		return "", err
+	}
+	return "", nil
+}

+ 70 - 0
internal/service/parser.go

@@ -15,6 +15,7 @@ type ParserService interface {
 	ParseAlert(html string) (message string, err error)
 	GetRuleId(ctx context.Context, htmlBytes []byte) (string, error)
 	ParseSDKOnlineHTMLTable(htmlContent string) ([]v1.SDKInfo, error)
+	CheckSDKKeyStatus(htmlData string, sdkKeyToFind string) error
 }
 
 func NewParserService(
@@ -198,3 +199,72 @@ func extractFromComplexText(text string) string {
 	}
 	return "查看"
 }
+
+// CheckSDKKeyStatus 检查SDKKey是否存在且过期
+func (s *parserService) CheckSDKKeyStatus(htmlData string, sdkKeyToFind string) error {
+	// 使用 strings.NewReader 将字符串转换为一个 io.Reader,这是 go-query 所需的输入格式。
+	// go-query 会加载并解析这个HTML,返回一个可供查询的文档对象(doc)。
+	doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlData))
+	if err != nil {
+		// 如果go-query无法加载或解析HTML,则返回错误。
+		return fmt.Errorf("无法解析HTML: %w", err)
+	}
+
+	// 定义两个变量用于在循环结束后判断状态
+	var keyFound bool = false // 标记是否找到了Key
+	var resultErr error = nil // 用于存储找到Key后的最终错误状态(如果是过期的话)
+
+	// 使用go-query的选择器找到class为 "table" 的表格(table.table)的主体(tbody)中的所有行(tr)。
+	// .EachWithBreak 方法会遍历每一行,并允许我们在满足特定条件时提前中断循环。
+	doc.Find("table.table tbody tr").EachWithBreak(func(i int, row *goquery.Selection) bool {
+		// 在当前行(row)中,查找第7个单元格(td:nth-of-type(7)),这是“SDK启动KEY”所在的列。
+		// Key本身被隐藏在一个 <pre> 标签内,我们直接定位它。
+		keyCell := row.Find("td:nth-of-type(7) pre")
+		fullKeyText := keyCell.Text() // 获取 <pre> 标签内的所有文本内容。
+
+		// 原始文本的格式是固定的,我们需要从中提取出真正的KEY。
+		// 格式: "原始内容:... >>> SDK启动KEY如下,复制后启动SDK使用 <<< [THE_ACTUAL_KEY]"
+		parts := strings.Split(fullKeyText, ">>> SDK启动KEY如下,复制后启动SDK使用 <<<")
+		if len(parts) < 2 {
+			// 如果当前行不符合这个格式,跳到下一行处理。
+			return true // `true` 在 EachWithBreak 中表示继续循环
+		}
+
+		// 提取并清理KEY字符串,去掉它前后的所有空格和换行符。
+		extractedKey := strings.TrimSpace(parts[1])
+		// 检查从页面提取出的KEY是否与我们要找的KEY相匹配。
+		if extractedKey == sdkKeyToFind {
+			keyFound = true // 首先,标记我们已经找到了Key
+			// 接着,在同一行中查找过期状态。
+			// 第11个单元格(td:nth-of-type(11))包含“过期时间”信息。
+			expirationCell := row.Find("td:nth-of-type(11)")
+			expirationText := expirationCell.Text() // 获取该单元格的文本内容。
+
+			// 检查过期时间文本中是否包含`(已过期)`
+			if strings.Contains(expirationText, "(已过期)") {
+				// 如果包含,我们将错误信息赋值给外部的 resultErr 变量
+				resultErr = fmt.Errorf("该KEY已过期")
+			}
+			// 注意:即使未过期,resultErr 仍然是 nil,这正是我们想要的结果。
+
+			// 我们已经找到了目标并处理完毕,没有必要再检查剩下的行了。
+			return false // `false` 在 EachWithBreak 中表示中断循环
+		}
+
+		// 如果当前行的KEY不匹配,继续下一行的查找。
+		return true
+	})
+
+	// --- 循环结束后的最终判断 ---
+
+	// 如果 keyFound 标志位仍然是 false,说明遍历了所有行都没有找到匹配的Key。
+	if !keyFound {
+		return fmt.Errorf("未找到指定的Key")
+	}
+
+	// 如果 keyFound 是 true,说明找到了Key。
+	// 此时,我们返回在循环中确定的 resultErr。
+	// - 如果Key未过期,resultErr 就是它初始的 nil 值。
+	// - 如果Key已过期,resultErr 就是我们在循环里设置的那个 error。
+	return resultErr
+}