waflog.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. package admin
  2. import (
  3. "context"
  4. "fmt"
  5. v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
  6. adminApi "github.com/go-nunu/nunu-layout-advanced/api/v1/admin"
  7. "github.com/go-nunu/nunu-layout-advanced/internal/model"
  8. "github.com/go-nunu/nunu-layout-advanced/internal/repository"
  9. "math"
  10. "strings"
  11. "time"
  12. )
  13. type WafLogRepository interface {
  14. GetWafLog(ctx context.Context, id int64) (*model.WafLog, error)
  15. GetWafLogList(ctx context.Context, req adminApi.SearchWafLogParams) (*v1.PaginatedResponse[model.WafLog], error)
  16. AddWafLog(ctx context.Context, log *model.WafLog) error
  17. BatchAddWafLog(ctx context.Context, logs []*model.WafLog) error
  18. // 导出日志
  19. ExportWafLog(ctx context.Context, req adminApi.ExportWafLog) ([]model.WafLog, error)
  20. // 支持分页的导出方法
  21. ExportWafLogWithPagination(ctx context.Context, req adminApi.ExportWafLog, page, pageSize int) ([]model.WafLog, error)
  22. // 获取导出数据总数
  23. GetWafLogExportCount(ctx context.Context, req adminApi.ExportWafLog) (int, error)
  24. // 获取网关组记录
  25. GetWafLogGateWayIp(ctx context.Context, hostId int64, Uid int64,createdAt time.Time) (model.WafLog, error)
  26. // 批量获取网关组记录
  27. BatchGetWafLogGateWayIps(ctx context.Context, hostIds []int64, uids []int64, maxCreatedAt time.Time) (map[string]model.WafLog, error)
  28. }
  29. func NewWafLogRepository(
  30. repository *repository.Repository,
  31. ) WafLogRepository {
  32. return &wafLogRepository{
  33. Repository: repository,
  34. }
  35. }
  36. type wafLogRepository struct {
  37. *repository.Repository
  38. }
  39. func (r *wafLogRepository) GetWafLog(ctx context.Context, id int64) (*model.WafLog, error) {
  40. var res model.WafLog
  41. return &res, r.DBWithName(ctx,"admin").Where("id = ?", id).First(&res).Error
  42. }
  43. func (r *wafLogRepository) GetWafLogList(ctx context.Context, req adminApi.SearchWafLogParams) (*v1.PaginatedResponse[model.WafLog], error) {
  44. var res []model.WafLog
  45. var total int64
  46. query := r.DBWithName(ctx,"admin").Model(&model.WafLog{})
  47. if req.RequestIp != "" {
  48. trimmedName := strings.TrimSpace(req.RequestIp)
  49. // 使用 LIKE 进行模糊匹配
  50. query = query.Where("request_ip LIKE CONCAT('%', ?, '%')", trimmedName)
  51. }
  52. if req.Uid != 0 {
  53. query = query.Where("uid = ?", req.Uid)
  54. }
  55. if req.Api != "" {
  56. trimmedName := strings.TrimSpace(req.Api)
  57. // 使用 LIKE 进行模糊匹配
  58. query = query.Where("api LIKE CONCAT('%', ?, '%')", trimmedName)
  59. }
  60. if req.Name != "" {
  61. trimmedName := strings.TrimSpace(req.Name)
  62. // 使用 LIKE 进行模糊匹配
  63. query = query.Where("name LIKE CONCAT('%', ?, '%')", trimmedName)
  64. }
  65. if req.RuleId != 0 {
  66. query = query.Where("rule_id = ?", req.RuleId)
  67. }
  68. if req.HostId != 0 {
  69. query = query.Where("host_id = ?", req.HostId)
  70. }
  71. if req.Api != "" {
  72. trimmedName := strings.TrimSpace(req.Api)
  73. // 使用 LIKE 进行模糊匹配
  74. query = query.Where("api LIKE CONCAT('%', ?, '%')", trimmedName)
  75. }
  76. if req.UserAgent != "" {
  77. trimmedName := strings.TrimSpace(req.UserAgent)
  78. // 使用 LIKE 进行模糊匹配
  79. query = query.Where("user_agent LIKE CONCAT('%', ?, '%')", trimmedName)
  80. }
  81. if req.ApiName != "" {
  82. trimmedName := strings.TrimSpace(req.ApiName)
  83. // 使用 LIKE 进行模糊匹配
  84. query = query.Where("api_name LIKE CONCAT('%', ?, '%')", trimmedName)
  85. }
  86. if req.ApiType != "" {
  87. query = query.Where("api_type = ?", req.ApiType)
  88. }
  89. if req.Column != "" {
  90. query = query.Order(req.Column + " " + req.Order)
  91. }
  92. if err := query.Count(&total).Error; err != nil {
  93. // 如果连计数都失败了,直接返回错误
  94. return nil, err
  95. }
  96. page := req.Current
  97. pageSize := req.PageSize
  98. if page <= 0 {
  99. page = 1
  100. }
  101. if pageSize <= 0 {
  102. pageSize = 10 // 默认每页 10 条
  103. } else if pageSize > 100 {
  104. pageSize = 100 // 每页最多 100 条
  105. }
  106. // 计算 offset (偏移量)
  107. // 例如,第 1 页,offset = (1-1)*10 = 0 (从第0条开始)
  108. // 第 2 页,offset = (2-1)*10 = 10 (从第10条开始)
  109. offset := (page - 1) * pageSize
  110. // 3. 执行最终的查询
  111. // 在所有条件都添加完毕后,再执行 .Find()
  112. result := query.Offset(offset).Limit(pageSize).Find(&res)
  113. if result.Error != nil {
  114. // 这里的错误可能是数据库连接问题等,而不是“未找到记录”
  115. return nil, result.Error
  116. }
  117. return &v1.PaginatedResponse[model.WafLog]{
  118. Records: res,
  119. Page: page,
  120. PageSize: pageSize,
  121. Total: total,
  122. TotalPages: int(math.Ceil(float64(total) / float64(pageSize))),
  123. }, nil
  124. }
  125. func (r *wafLogRepository) AddWafLog(ctx context.Context, log *model.WafLog) error {
  126. return r.DBWithName(ctx,"admin").Create(log).Error
  127. }
  128. func (r *wafLogRepository) BatchAddWafLog(ctx context.Context, logs []*model.WafLog) error {
  129. if len(logs) == 0 {
  130. return nil
  131. }
  132. return r.DBWithName(ctx, "admin").CreateInBatches(logs, len(logs)).Error
  133. }
  134. func (r *wafLogRepository) ExportWafLog(ctx context.Context, req adminApi.ExportWafLog) ([]model.WafLog, error) {
  135. return r.ExportWafLogWithPagination(ctx, req, 0, 0)
  136. }
  137. // ExportWafLogWithPagination 支持分页的导出方法
  138. func (r *wafLogRepository) ExportWafLogWithPagination(ctx context.Context, req adminApi.ExportWafLog, page, pageSize int) ([]model.WafLog, error) {
  139. var res []model.WafLog
  140. query := r.DBWithName(ctx,"admin").Model(&model.WafLog{})
  141. if req.RequestIp != "" {
  142. trimmedName := strings.TrimSpace(req.RequestIp)
  143. // 使用 LIKE 进行模糊匹配
  144. query = query.Where("request_ip = ?", trimmedName)
  145. }
  146. if req.Uid != 0 {
  147. query = query.Where("uid = ?", req.Uid)
  148. }
  149. if req.Api != "" {
  150. trimmedName := strings.TrimSpace(req.Api)
  151. // 使用 LIKE 进行模糊匹配
  152. query = query.Where("api = ?", trimmedName)
  153. }
  154. if req.Name != "" {
  155. trimmedName := strings.TrimSpace(req.Name)
  156. // 使用 LIKE 进行模糊匹配
  157. query = query.Where("name = ?", trimmedName)
  158. }
  159. if req.RuleId != 0 {
  160. query = query.Where("rule_id = ?", req.RuleId)
  161. }
  162. if len(req.HostIds) > 0 {
  163. query = query.Where("host_id IN ?", req.HostIds)
  164. }
  165. if req.Api != "" {
  166. trimmedName := strings.TrimSpace(req.Api)
  167. // 使用 LIKE 进行模糊匹配
  168. query = query.Where("api = ?", trimmedName)
  169. }
  170. if req.UserAgent != "" {
  171. trimmedName := strings.TrimSpace(req.UserAgent)
  172. // 使用 LIKE 进行模糊匹配
  173. query = query.Where("user_agent = ?", trimmedName)
  174. }
  175. if len(req.ApiNames) > 0 {
  176. trimmedNames := make([]string, len(req.ApiNames))
  177. for _, apiName := range req.ApiNames {
  178. trimmedNames = append(trimmedNames, strings.TrimSpace(apiName))
  179. }
  180. // 使用 LIKE 进行模糊匹配
  181. query = query.Where("api_name IN ?", trimmedNames)
  182. }
  183. if len(req.ApiTypes) > 0 {
  184. query = query.Where("api_type IN ?", req.ApiTypes)
  185. }
  186. if req.StartTime != "" {
  187. trimmedName := strings.TrimSpace(req.StartTime)
  188. // 使用 LIKE 进行模糊匹配
  189. query = query.Where("created_at > ?", trimmedName)
  190. }
  191. if req.EndTime != "" {
  192. trimmedName := strings.TrimSpace(req.EndTime)
  193. // 使用 LIKE 进行模糊匹配
  194. query = query.Where("created_at < ?", trimmedName)
  195. }
  196. // 添加分页逻辑
  197. if page > 0 && pageSize > 0 {
  198. offset := (page - 1) * pageSize
  199. query = query.Offset(offset).Limit(pageSize)
  200. }
  201. result := query.Find(&res)
  202. if result.Error != nil {
  203. return nil, result.Error
  204. }
  205. return res, nil
  206. }
  207. // GetWafLogExportCount 获取导出数据总数
  208. func (r *wafLogRepository) GetWafLogExportCount(ctx context.Context, req adminApi.ExportWafLog) (int, error) {
  209. var count int64
  210. query := r.DBWithName(ctx,"admin").Model(&model.WafLog{})
  211. // 复用ExportWafLog的查询条件
  212. if req.RequestIp != "" {
  213. trimmedName := strings.TrimSpace(req.RequestIp)
  214. query = query.Where("request_ip = ?", trimmedName)
  215. }
  216. if req.Uid != 0 {
  217. query = query.Where("uid = ?", req.Uid)
  218. }
  219. if req.Api != "" {
  220. trimmedName := strings.TrimSpace(req.Api)
  221. query = query.Where("api = ?", trimmedName)
  222. }
  223. if req.Name != "" {
  224. trimmedName := strings.TrimSpace(req.Name)
  225. query = query.Where("name = ?", trimmedName)
  226. }
  227. if req.RuleId != 0 {
  228. query = query.Where("rule_id = ?", req.RuleId)
  229. }
  230. if len(req.HostIds) > 0 {
  231. query = query.Where("host_id IN ?", req.HostIds)
  232. }
  233. if req.UserAgent != "" {
  234. trimmedName := strings.TrimSpace(req.UserAgent)
  235. query = query.Where("user_agent = ?", trimmedName)
  236. }
  237. if len(req.ApiNames) > 0 {
  238. trimmedNames := make([]string, len(req.ApiNames))
  239. for _, apiName := range req.ApiNames {
  240. trimmedNames = append(trimmedNames, strings.TrimSpace(apiName))
  241. }
  242. query = query.Where("api_name IN ?", trimmedNames)
  243. }
  244. if len(req.ApiTypes) > 0 {
  245. query = query.Where("api_type IN ?", req.ApiTypes)
  246. }
  247. if req.StartTime != "" {
  248. trimmedName := strings.TrimSpace(req.StartTime)
  249. query = query.Where("created_at > ?", trimmedName)
  250. }
  251. if req.EndTime != "" {
  252. trimmedName := strings.TrimSpace(req.EndTime)
  253. query = query.Where("created_at < ?", trimmedName)
  254. }
  255. result := query.Count(&count)
  256. if result.Error != nil {
  257. return 0, result.Error
  258. }
  259. return int(count), nil
  260. }
  261. func (r *wafLogRepository) GetWafLogGateWayIp(ctx context.Context, hostId int64, Uid int64,createdAt time.Time) (model.WafLog, error) {
  262. var res model.WafLog
  263. return res, r.DBWithName(ctx,"admin").Where("host_id = ? and uid = ? and api_name = ? and created_at < ? ", hostId, Uid, "分配网关组", createdAt).First(&res).Error
  264. }
  265. // BatchGetWafLogGateWayIps 批量获取网关组记录,避免N+1查询问题
  266. func (r *wafLogRepository) BatchGetWafLogGateWayIps(ctx context.Context, hostIds []int64, uids []int64, maxCreatedAt time.Time) (map[string]model.WafLog, error) {
  267. if len(hostIds) == 0 || len(uids) == 0 {
  268. return make(map[string]model.WafLog), nil
  269. }
  270. var gateWayLogs []model.WafLog
  271. // 构建查询条件,获取所有相关的网关组记录
  272. query := r.DBWithName(ctx, "admin").
  273. Where("api_name = ?", "分配网关组").
  274. Where("created_at < ?", maxCreatedAt)
  275. // 如果hostIds和uids数量较少,使用IN查询
  276. if len(hostIds) <= 1000 && len(uids) <= 1000 {
  277. query = query.Where("host_id IN ? AND uid IN ?", hostIds, uids)
  278. }
  279. // 按创建时间倒序,确保获取最新的网关组配置
  280. err := query.Order("created_at DESC").Find(&gateWayLogs).Error
  281. if err != nil {
  282. return nil, err
  283. }
  284. // 构建映射表,key为"hostId_uid",value为最新的网关组记录
  285. result := make(map[string]model.WafLog)
  286. for _, log := range gateWayLogs {
  287. key := fmt.Sprintf("%d_%d", log.HostId, log.Uid)
  288. // 由于已经按创建时间倒序排列,第一次遇到的就是最新的记录
  289. if _, exists := result[key]; !exists {
  290. result[key] = log
  291. }
  292. }
  293. return result, nil
  294. }