waflogdataclean.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. package admin
  2. import (
  3. "encoding/json"
  4. "github.com/go-nunu/nunu-layout-advanced/internal/service"
  5. "github.com/tidwall/gjson"
  6. "go.uber.org/zap"
  7. )
  8. type WafLogDataCleanService interface {
  9. ParseWafLogExtraData(extraDataBytes json.RawMessage, apiName string) CleanedExtraData
  10. }
  11. func NewWafLogDataCleanService(
  12. service *service.Service,
  13. ) WafLogDataCleanService {
  14. return &wafLogDataCleanService{
  15. Service: service,
  16. }
  17. }
  18. type wafLogDataCleanService struct {
  19. *service.Service
  20. }
  21. // BackendInfo 后端服务器信息
  22. type BackendInfo struct {
  23. Addr string `json:"addr,omitempty"` // 后端地址
  24. CustomHost string `json:"customHost,omitempty"` // 自定义Host头
  25. }
  26. // CleanedExtraData 使用动态结构存储解析后的数据
  27. type CleanedExtraData struct {
  28. // 核心字段 - 新的数组结构
  29. BackendList []BackendInfo `json:"backendList,omitempty"` // 完整的后端信息数组
  30. // 向后兼容字段
  31. AddrBackendList []string `json:"addrBackendList,omitempty"` // 只包含地址的数组
  32. CustomHostList []string `json:"customHostList,omitempty"` // 只包含customHost的数组
  33. CustomHost []string `json:"customHost,omitempty"` // customHost数组,与CustomHostList相同
  34. // 基础字段
  35. Port string `json:"port,omitempty"`
  36. Domain string `json:"domain,omitempty"`
  37. Comment string `json:"comment,omitempty"`
  38. // 扩展字段
  39. UID int64 `json:"uid,omitempty"`
  40. HostID int64 `json:"hostId,omitempty"`
  41. Proxy bool `json:"proxy,omitempty"`
  42. IsHttps int `json:"isHttps,omitempty"`
  43. // 动态字段存储,用于存储任意其他字段
  44. DynamicFields map[string]interface{} `json:"dynamicFields,omitempty"`
  45. // 原始数据备份,用于调试和回溯
  46. RawData map[string]interface{} `json:"rawData,omitempty"`
  47. }
  48. // backendInfo 用于解析 "web" 类型中 backendList JSON 字符串的内部结构
  49. type backendInfo struct {
  50. Addr string `json:"addr"`
  51. CustomHost string `json:"customHost"`
  52. }
  53. // parseWafLogExtraData 使用gjson解析动态JSON结构,简洁高效
  54. func (s *wafLogDataCleanService) ParseWafLogExtraData(extraDataBytes json.RawMessage, apiName string) CleanedExtraData {
  55. var result CleanedExtraData
  56. result.DynamicFields = make(map[string]interface{})
  57. if len(extraDataBytes) == 0 {
  58. return result
  59. }
  60. jsonStr := string(extraDataBytes)
  61. if !gjson.Valid(jsonStr) {
  62. s.Logger.Warn("ExtraData 不是有效的JSON", zap.String("raw_data", jsonStr))
  63. return result
  64. }
  65. // 解析并保存原始数据
  66. var rawData map[string]interface{}
  67. json.Unmarshal(extraDataBytes, &rawData)
  68. result.RawData = rawData
  69. // 使用gjson进行智能字段提取
  70. s.extractWithGjson(jsonStr, apiName, &result)
  71. return result
  72. }
  73. // extractWithGjson 使用gjson进行智能字段提取
  74. func (s *wafLogDataCleanService) extractWithGjson(jsonStr, apiName string, result *CleanedExtraData) {
  75. // 提取顶层字段
  76. if uid := gjson.Get(jsonStr, "uid"); uid.Exists() {
  77. result.UID = uid.Int()
  78. }
  79. if hostId := gjson.Get(jsonStr, "hostId"); hostId.Exists() {
  80. result.HostID = hostId.Int()
  81. }
  82. // 定义常见字段路径的优先级列表
  83. fieldPaths := map[string][]string{
  84. "comment": {"comment", "data.comment", "desc", "description", "remark", "note"},
  85. "port": {"port", "data.port", "config.port", "server.port"},
  86. "domain": {"domain", "data.domain", "host", "data.host", "hostname"},
  87. "proxy": {"proxy", "data.proxy"},
  88. "isHttps": {"isHttps", "data.isHttps"},
  89. }
  90. // 提取基础字段
  91. for fieldName, paths := range fieldPaths {
  92. value := s.getFirstValidPath(jsonStr, paths)
  93. if value != "" {
  94. switch fieldName {
  95. case "comment":
  96. result.Comment = value
  97. case "port":
  98. result.Port = value
  99. case "domain":
  100. result.Domain = value
  101. case "proxy":
  102. result.Proxy = gjson.Get(jsonStr, s.getFirstValidPathName(jsonStr, paths)).Bool()
  103. case "isHttps":
  104. result.IsHttps = int(gjson.Get(jsonStr, s.getFirstValidPathName(jsonStr, paths)).Int())
  105. }
  106. }
  107. }
  108. // 智能提取 backendList
  109. s.extractBackendListWithGjson(jsonStr, apiName, result)
  110. // 提取所有其他动态字段
  111. s.extractDynamicFields(jsonStr, result)
  112. }
  113. // getFirstValidPath 从多个路径中获取第一个有效值
  114. func (s *wafLogDataCleanService) getFirstValidPath(jsonStr string, paths []string) string {
  115. for _, path := range paths {
  116. if value := gjson.Get(jsonStr, path); value.Exists() && value.String() != "" {
  117. return value.String()
  118. }
  119. }
  120. return ""
  121. }
  122. // getFirstValidPathName 获取第一个有效路径的名称
  123. func (s *wafLogDataCleanService) getFirstValidPathName(jsonStr string, paths []string) string {
  124. for _, path := range paths {
  125. if gjson.Get(jsonStr, path).Exists() {
  126. return path
  127. }
  128. }
  129. return ""
  130. }
  131. // extractBackendListWithGjson 使用gjson智能提取后端列表
  132. func (s *wafLogDataCleanService) extractBackendListWithGjson(jsonStr, apiName string, result *CleanedExtraData) {
  133. // 定义可能的后端列表字段路径
  134. backendPaths := []string{
  135. "backendList", "data.backendList", "backends", "data.backends",
  136. "backend_list", "data.backend_list", "servers", "data.servers",
  137. "upstreams", "data.upstreams", "targets", "data.targets",
  138. }
  139. for _, path := range backendPaths {
  140. backendResult := gjson.Get(jsonStr, path)
  141. if !backendResult.Exists() {
  142. continue
  143. }
  144. // 根据数据类型进行处理
  145. switch {
  146. case backendResult.IsArray():
  147. s.processArrayBackends(backendResult, result)
  148. case backendResult.IsObject():
  149. s.processObjectBackends(backendResult, result)
  150. default:
  151. // 字符串类型,可能是JSON字符串或单个地址
  152. s.processStringBackends(backendResult.String(), result)
  153. }
  154. // 找到有效数据就退出
  155. if len(result.AddrBackendList) > 0 || len(result.CustomHostList) > 0 {
  156. break
  157. }
  158. }
  159. }
  160. // processArrayBackends 处理数组格式的后端列表
  161. func (s *wafLogDataCleanService) processArrayBackends(backendResult gjson.Result, result *CleanedExtraData) {
  162. backendResult.ForEach(func(key, value gjson.Result) bool {
  163. if value.IsObject() {
  164. // 创建BackendInfo结构
  165. backend := BackendInfo{}
  166. // 尝试提取地址字段
  167. addr := s.getFirstValidPath(value.Raw, []string{"addr", "address", "host", "server", "endpoint", "url"})
  168. if addr != "" {
  169. backend.Addr = addr
  170. result.AddrBackendList = append(result.AddrBackendList, addr)
  171. }
  172. // 提取customHost
  173. if customHost := gjson.Get(value.Raw, "customHost").String(); customHost != "" {
  174. backend.CustomHost = customHost
  175. result.CustomHostList = append(result.CustomHostList, customHost)
  176. result.CustomHost = append(result.CustomHost, customHost)
  177. }
  178. // 只有当有有效数据时才添加到BackendList
  179. if backend.Addr != "" || backend.CustomHost != "" {
  180. result.BackendList = append(result.BackendList, backend)
  181. }
  182. } else {
  183. // 直接作为地址处理
  184. if addr := value.String(); addr != "" {
  185. result.AddrBackendList = append(result.AddrBackendList, addr)
  186. result.BackendList = append(result.BackendList, BackendInfo{Addr: addr})
  187. }
  188. }
  189. return true
  190. })
  191. }
  192. // processObjectBackends 处理对象格式的后端列表
  193. func (s *wafLogDataCleanService) processObjectBackends(backendResult gjson.Result, result *CleanedExtraData) {
  194. // 创建BackendInfo结构
  195. backend := BackendInfo{}
  196. addr := s.getFirstValidPath(backendResult.Raw, []string{"addr", "address", "host", "server", "endpoint", "url"})
  197. if addr != "" {
  198. backend.Addr = addr
  199. result.AddrBackendList = append(result.AddrBackendList, addr)
  200. }
  201. if customHost := gjson.Get(backendResult.Raw, "customHost").String(); customHost != "" {
  202. backend.CustomHost = customHost
  203. result.CustomHostList = append(result.CustomHostList, customHost)
  204. result.CustomHost = append(result.CustomHost, customHost)
  205. }
  206. // 只有当有有效数据时才添加到BackendList
  207. if backend.Addr != "" || backend.CustomHost != "" {
  208. result.BackendList = append(result.BackendList, backend)
  209. }
  210. }
  211. // processStringBackends 处理字符串格式的后端列表
  212. func (s *wafLogDataCleanService) processStringBackends(backendStr string, result *CleanedExtraData) {
  213. if backendStr == "" {
  214. return
  215. }
  216. // 尝试作为JSON解析
  217. if gjson.Valid(backendStr) {
  218. parsed := gjson.Parse(backendStr)
  219. if parsed.IsArray() {
  220. s.processArrayBackends(parsed, result)
  221. return
  222. } else if parsed.IsObject() {
  223. s.processObjectBackends(parsed, result)
  224. return
  225. }
  226. }
  227. // 作为单个地址处理
  228. result.AddrBackendList = append(result.AddrBackendList, backendStr)
  229. result.BackendList = append(result.BackendList, BackendInfo{Addr: backendStr})
  230. }
  231. // extractDynamicFields 提取所有动态字段到DynamicFields中
  232. func (s *wafLogDataCleanService) extractDynamicFields(jsonStr string, result *CleanedExtraData) {
  233. // 已知的核心字段,不放入动态字段中
  234. knownFields := map[string]bool{
  235. "comment": true, "port": true, "domain": true,
  236. "backendList": true, "backends": true, "backend_list": true,
  237. "data": true, // data字段的内容会被单独处理
  238. }
  239. // 遍历顶层字段
  240. gjson.Parse(jsonStr).ForEach(func(key, value gjson.Result) bool {
  241. fieldName := key.String()
  242. if !knownFields[fieldName] {
  243. // 将未知字段存储到动态字段中
  244. result.DynamicFields[fieldName] = value.Value()
  245. }
  246. return true
  247. })
  248. // 特殊处理data字段中的未知字段
  249. dataResult := gjson.Get(jsonStr, "data")
  250. if dataResult.Exists() && dataResult.IsObject() {
  251. dataKnownFields := map[string]bool{
  252. "port": true, "domain": true, "backendList": true,
  253. "backends": true, "backend_list": true,
  254. }
  255. dataResult.ForEach(func(key, value gjson.Result) bool {
  256. fieldName := key.String()
  257. if !dataKnownFields[fieldName] {
  258. // 使用data.前缀避免冲突
  259. result.DynamicFields["data."+fieldName] = value.Value()
  260. }
  261. return true
  262. })
  263. }
  264. }