فهرست منبع

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

- 在 GameShield API 中新增 IsExistGameShieldKey 接口
- 实现后端服务和解析逻辑,检查指定的 SDK Key 是否存在并获取其状态
- 如果 Key存在但已过期,返回相应错误信息
- 优化 KeyAndFieldResponse 结构,添加 JSON 和 form 标签
fusu 2 ماه پیش
والد
کامیت
20679de8bf
5فایلهای تغییر یافته به همراه102 افزوده شده و 1 حذف شده
  1. 1 1
      api/v1/GameShield.go
  2. 14 0
      internal/handler/gameshield.go
  3. 1 0
      internal/server/http.go
  4. 16 0
      internal/service/gameshield.go
  5. 70 0
      internal/service/parser.go

+ 1 - 1
api/v1/GameShield.go

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

+ 14 - 0
internal/handler/gameshield.go

@@ -114,3 +114,17 @@ func (h *GameShieldHandler) GetGameShieldOnlineList(ctx *gin.Context) {
 	}
 	}
 	v1.HandleSuccess(ctx, res)
 	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/edit", gameShieldHandler.EditGameShield)
 			noAuthRouter.POST("/gameShield/delete", gameShieldHandler.DeleteGameShield)
 			noAuthRouter.POST("/gameShield/delete", gameShieldHandler.DeleteGameShield)
 			noAuthRouter.POST("/gameShield/getOnline", gameShieldHandler.GetGameShieldOnlineList)
 			noAuthRouter.POST("/gameShield/getOnline", gameShieldHandler.GetGameShieldOnlineList)
+			noAuthRouter.POST("/gameShield/IsExistKey", gameShieldHandler.IsExistGameShieldKey)
 			noAuthRouter.POST("/webForward/add", webForwardingHandler.AddWebForwarding)
 			noAuthRouter.POST("/webForward/add", webForwardingHandler.AddWebForwarding)
 			noAuthRouter.POST("/webForward/edit", webForwardingHandler.EditWebForwarding)
 			noAuthRouter.POST("/webForward/edit", webForwardingHandler.EditWebForwarding)
 			noAuthRouter.POST("/webForward/delete", webForwardingHandler.DeleteWebForwarding)
 			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)
 	GetGameShieldKey(ctx context.Context, id int) (string, error)
 	GetKeyAndEditGameShield(ctx context.Context, hostId int, dunName string) (string, error)
 	GetKeyAndEditGameShield(ctx context.Context, hostId int, dunName string) (string, error)
 	GetGameShieldOnlineList(ctx context.Context, hostId int) ([]v1.SDKInfo, error)
 	GetGameShieldOnlineList(ctx context.Context, hostId int) ([]v1.SDKInfo, error)
+	IsExistGameShieldKey(ctx context.Context, key string) (string, error)
 }
 }
 
 
 func NewGameShieldService(
 func NewGameShieldService(
@@ -188,3 +189,18 @@ func (service *gameShieldService) GetGameShieldOnlineList(ctx context.Context, h
 	}
 	}
 	return res, nil
 	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)
 	ParseAlert(html string) (message string, err error)
 	GetRuleId(ctx context.Context, htmlBytes []byte) (string, error)
 	GetRuleId(ctx context.Context, htmlBytes []byte) (string, error)
 	ParseSDKOnlineHTMLTable(htmlContent string) ([]v1.SDKInfo, error)
 	ParseSDKOnlineHTMLTable(htmlContent string) ([]v1.SDKInfo, error)
+	CheckSDKKeyStatus(htmlData string, sdkKeyToFind string) error
 }
 }
 
 
 func NewParserService(
 func NewParserService(
@@ -198,3 +199,72 @@ func extractFromComplexText(text string) string {
 	}
 	}
 	return "查看"
 	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
+}