parser.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. package service
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "github.com/PuerkitoBio/goquery"
  8. v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
  9. "strings"
  10. )
  11. type ParserService interface {
  12. GetMessage(ctx context.Context, req []byte) (string, error)
  13. ParseAlert(html string) (message string, err error)
  14. GetRuleId(ctx context.Context, htmlBytes []byte) (string, error)
  15. ParseSDKOnlineHTMLTable(htmlContent string) ([]v1.SDKInfo, error)
  16. CheckSDKKeyStatus(htmlData string, sdkKeyToFind string) error
  17. }
  18. func NewParserService(
  19. service *Service,
  20. ) ParserService {
  21. return &parserService{
  22. Service: service,
  23. }
  24. }
  25. type parserService struct {
  26. *Service
  27. }
  28. // 解析 alert 消息
  29. func (s *parserService) ParseAlert(html string) (message string, err error) {
  30. doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
  31. if err != nil {
  32. return "", err
  33. }
  34. sel := doc.Find(".alert")
  35. if sel.Length() == 0 {
  36. // 没有 .alert 元素
  37. return "", nil
  38. }
  39. // 找到 .alert,继续提取
  40. t := strings.TrimSpace(sel.Find("h4").Text())
  41. full := strings.TrimSpace(sel.Text())
  42. full = strings.TrimPrefix(full, "×")
  43. full = strings.TrimSpace(full)
  44. m := strings.TrimSpace(strings.TrimPrefix(full, t))
  45. return m, nil
  46. }
  47. func (s *parserService) GetMessage(ctx context.Context, req []byte) (string, error) {
  48. type msg struct {
  49. Message string `json:"msg"` // 如果字段叫 msg,用 `json:"msg"`
  50. }
  51. var m msg
  52. if err := json.Unmarshal(req, &m); err != nil {
  53. return "", fmt.Errorf("解析 message 失败: %v", err)
  54. }
  55. if m.Message == "no affect row" {
  56. return "", fmt.Errorf("没有该条数据")
  57. }
  58. return m.Message, nil
  59. }
  60. func (s *parserService) GetRuleId(ctx context.Context, htmlBytes []byte) (string, error) {
  61. // 1. 把 []byte 包成 io.Reader
  62. reader := bytes.NewReader(htmlBytes)
  63. // 2. 用 goquery 解析
  64. doc, err := goquery.NewDocumentFromReader(reader)
  65. if err != nil {
  66. return "", err
  67. }
  68. // 方法一:按位置拿(第 2 个 tr、第 2 个 td)
  69. id := doc.
  70. Find("table.table tbody tr").Eq(1). // 跳过表头行,拿第一条数据
  71. Find("td").Eq(1).Text() // 第 2 个 td
  72. return strings.TrimSpace(id), nil
  73. }
  74. // 解析 Sdk在线情况 表格
  75. func (s *parserService) ParseSDKOnlineHTMLTable(htmlContent string) ([]v1.SDKInfo, error) {
  76. // 创建goquery文档
  77. doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
  78. if err != nil {
  79. return nil, fmt.Errorf("解析HTML失败: %v", err)
  80. }
  81. var sdkInfos []v1.SDKInfo
  82. // 查找表格并解析数据行
  83. doc.Find("table.table.table-hover tbody tr").Each(func(i int, s *goquery.Selection) {
  84. // 跳过表头行(如果有的话)
  85. if s.Find("th").Length() > 0 {
  86. return
  87. }
  88. var info v1.SDKInfo
  89. // 解析每一列的数据
  90. s.Find("td").Each(func(j int, td *goquery.Selection) {
  91. text := strings.TrimSpace(td.Text())
  92. // 根据列的位置分配到对应字段(跳过第一列的复选框)
  93. switch j {
  94. case 1: // 规则ID
  95. info.RuleID = text
  96. case 2: // 客户端IP
  97. info.ClientIP = text
  98. //case 3: // 网关IP
  99. // info.GatewayIP = text
  100. case 4: // SDK-UUID
  101. info.SDKUUID = text
  102. case 5: // 会话ID
  103. info.SessionID = text
  104. case 6: // SDK类型
  105. info.SDKType = text
  106. //case 7: // SDK版本
  107. // info.SDKVersion = text
  108. case 8: // 系统
  109. info.System = text
  110. case 9: // 附加信息
  111. // 对于附加信息列,提取JSON内容
  112. info.ExtraInfo = extractJSONFromExtraInfo(text)
  113. }
  114. })
  115. // 只有当规则ID不为空时才添加记录
  116. if info.RuleID != "" {
  117. sdkInfos = append(sdkInfos, info)
  118. }
  119. })
  120. return sdkInfos, nil
  121. }
  122. // extractJSONFromExtraInfo 从附加信息字符串中提取JSON内容
  123. func extractJSONFromExtraInfo(text string) string {
  124. text = strings.TrimSpace(text)
  125. // 尝试直接解析
  126. if result := tryParseJSON(text); result != "" {
  127. return result
  128. }
  129. // 尝试解析JSON字符串(去掉外层引号)
  130. if strings.HasPrefix(text, `"`) && strings.HasSuffix(text, `"`) {
  131. var jsonContent string
  132. if json.Unmarshal([]byte(text), &jsonContent) == nil {
  133. if result := tryParseJSON(jsonContent); result != "" {
  134. return result
  135. }
  136. }
  137. }
  138. // 从复杂文本中提取JSON
  139. return extractFromComplexText(text)
  140. }
  141. // 统一的JSON解析和格式化函数
  142. func tryParseJSON(text string) string {
  143. var temp interface{}
  144. if json.Unmarshal([]byte(text), &temp) == nil {
  145. if formatted, err := json.Marshal(temp); err == nil {
  146. return string(formatted)
  147. }
  148. return text
  149. }
  150. return ""
  151. }
  152. // 简化的复杂文本JSON提取
  153. func extractFromComplexText(text string) string {
  154. // 找到最后一个完整的JSON对象
  155. for end := strings.LastIndex(text, "}"); end != -1; end = strings.LastIndex(text[:end], "}") {
  156. // 向前查找匹配的开始大括号
  157. braceCount := 1
  158. for start := end - 1; start >= 0; start-- {
  159. switch text[start] {
  160. case '}':
  161. braceCount++
  162. case '{':
  163. braceCount--
  164. if braceCount == 0 {
  165. candidate := text[start : end+1]
  166. if result := tryParseJSON(candidate); result != "" {
  167. return result
  168. }
  169. break // 跳出内层循环,继续寻找下一个'}'
  170. }
  171. }
  172. }
  173. }
  174. return "查看"
  175. }
  176. // CheckSDKKeyStatus 检查SDKKey是否存在且过期
  177. func (s *parserService) CheckSDKKeyStatus(htmlData string, sdkKeyToFind string) error {
  178. // 使用 strings.NewReader 将字符串转换为一个 io.Reader,这是 go-query 所需的输入格式。
  179. // go-query 会加载并解析这个HTML,返回一个可供查询的文档对象(doc)。
  180. doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlData))
  181. if err != nil {
  182. // 如果go-query无法加载或解析HTML,则返回错误。
  183. return fmt.Errorf("无法解析HTML: %w", err)
  184. }
  185. // 定义两个变量用于在循环结束后判断状态
  186. var keyFound bool = false // 标记是否找到了Key
  187. var resultErr error = nil // 用于存储找到Key后的最终错误状态(如果是过期的话)
  188. // 使用go-query的选择器找到class为 "table" 的表格(table.table)的主体(tbody)中的所有行(tr)。
  189. // .EachWithBreak 方法会遍历每一行,并允许我们在满足特定条件时提前中断循环。
  190. doc.Find("table.table tbody tr").EachWithBreak(func(i int, row *goquery.Selection) bool {
  191. // 在当前行(row)中,查找第7个单元格(td:nth-of-type(7)),这是“SDK启动KEY”所在的列。
  192. // Key本身被隐藏在一个 <pre> 标签内,我们直接定位它。
  193. keyCell := row.Find("td:nth-of-type(7) pre")
  194. fullKeyText := keyCell.Text() // 获取 <pre> 标签内的所有文本内容。
  195. // 原始文本的格式是固定的,我们需要从中提取出真正的KEY。
  196. // 格式: "原始内容:... >>> SDK启动KEY如下,复制后启动SDK使用 <<< [THE_ACTUAL_KEY]"
  197. parts := strings.Split(fullKeyText, ">>> SDK启动KEY如下,复制后启动SDK使用 <<<")
  198. if len(parts) < 2 {
  199. // 如果当前行不符合这个格式,跳到下一行处理。
  200. return true // `true` 在 EachWithBreak 中表示继续循环
  201. }
  202. // 提取并清理KEY字符串,去掉它前后的所有空格和换行符。
  203. extractedKey := strings.TrimSpace(parts[1])
  204. // 检查从页面提取出的KEY是否与我们要找的KEY相匹配。
  205. if extractedKey == sdkKeyToFind {
  206. keyFound = true // 首先,标记我们已经找到了Key
  207. // 接着,在同一行中查找过期状态。
  208. // 第11个单元格(td:nth-of-type(11))包含“过期时间”信息。
  209. expirationCell := row.Find("td:nth-of-type(11)")
  210. expirationText := expirationCell.Text() // 获取该单元格的文本内容。
  211. // 检查过期时间文本中是否包含`(已过期)`
  212. if strings.Contains(expirationText, "(已过期)") {
  213. // 如果包含,我们将错误信息赋值给外部的 resultErr 变量
  214. resultErr = fmt.Errorf("该KEY已过期")
  215. }
  216. // 注意:即使未过期,resultErr 仍然是 nil,这正是我们想要的结果。
  217. // 我们已经找到了目标并处理完毕,没有必要再检查剩下的行了。
  218. return false // `false` 在 EachWithBreak 中表示中断循环
  219. }
  220. // 如果当前行的KEY不匹配,继续下一行的查找。
  221. return true
  222. })
  223. // --- 循环结束后的最终判断 ---
  224. // 如果 keyFound 标志位仍然是 false,说明遍历了所有行都没有找到匹配的Key。
  225. if !keyFound {
  226. return fmt.Errorf("未找到指定的Key")
  227. }
  228. // 如果 keyFound 是 true,说明找到了Key。
  229. // 此时,我们返回在循环中确定的 resultErr。
  230. // - 如果Key未过期,resultErr 就是它初始的 nil 值。
  231. // - 如果Key已过期,resultErr 就是我们在循环里设置的那个 error。
  232. return resultErr
  233. }