waflog.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. package admin
  2. import (
  3. "context"
  4. v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
  5. adminApi "github.com/go-nunu/nunu-layout-advanced/api/v1/admin"
  6. "github.com/go-nunu/nunu-layout-advanced/internal/model"
  7. "github.com/go-nunu/nunu-layout-advanced/internal/repository"
  8. "gorm.io/gorm"
  9. "math"
  10. "strings"
  11. )
  12. type WafLogRepository interface {
  13. GetWafLog(ctx context.Context, id int64) (*model.WafLog, error)
  14. GetWafLogList(ctx context.Context, req adminApi.SearchWafLogParams) (*v1.PaginatedResponse[model.WafLog], error)
  15. AddWafLog(ctx context.Context, log *model.WafLog) error
  16. BatchAddWafLog(ctx context.Context, logs []*model.WafLog) error
  17. // 导出日志(已更新)
  18. ExportWafLog(ctx context.Context, req adminApi.ExportWafLog) ([]model.WafLogWithGatewayIP, error)
  19. // 支持分页的导出方法(已更新)
  20. ExportWafLogWithPagination(ctx context.Context, req adminApi.ExportWafLog, page, pageSize int) ([]model.WafLogWithGatewayIP, error)
  21. // 获取导出数据总数
  22. GetWafLogExportCount(ctx context.Context, req adminApi.ExportWafLog) (int, error)
  23. }
  24. func NewWafLogRepository(
  25. repository *repository.Repository,
  26. ) WafLogRepository {
  27. return &wafLogRepository{
  28. Repository: repository,
  29. }
  30. }
  31. type wafLogRepository struct {
  32. *repository.Repository
  33. }
  34. func (r *wafLogRepository) GetWafLog(ctx context.Context, id int64) (*model.WafLog, error) {
  35. var res model.WafLog
  36. return &res, r.DBWithName(ctx,"admin").Where("id = ?", id).First(&res).Error
  37. }
  38. func (r *wafLogRepository) GetWafLogList(ctx context.Context, req adminApi.SearchWafLogParams) (*v1.PaginatedResponse[model.WafLog], error) {
  39. var res []model.WafLog
  40. var total int64
  41. query := r.DBWithName(ctx,"admin").Model(&model.WafLog{})
  42. if req.RequestIp != "" {
  43. trimmedName := strings.TrimSpace(req.RequestIp)
  44. query = query.Where("request_ip LIKE CONCAT('%', ?, '%')", trimmedName)
  45. }
  46. if req.Uid != 0 {
  47. query = query.Where("uid = ?", req.Uid)
  48. }
  49. if req.Api != "" {
  50. trimmedName := strings.TrimSpace(req.Api)
  51. query = query.Where("api LIKE CONCAT('%', ?, '%')", trimmedName)
  52. }
  53. if req.Name != "" {
  54. trimmedName := strings.TrimSpace(req.Name)
  55. query = query.Where("name LIKE CONCAT('%', ?, '%')", trimmedName)
  56. }
  57. if req.RuleId != 0 {
  58. query = query.Where("rule_id = ?", req.RuleId)
  59. }
  60. if req.HostId != 0 {
  61. query = query.Where("host_id = ?", req.HostId)
  62. }
  63. if req.Api != "" {
  64. trimmedName := strings.TrimSpace(req.Api)
  65. query = query.Where("api LIKE CONCAT('%', ?, '%')", trimmedName)
  66. }
  67. if req.UserAgent != "" {
  68. trimmedName := strings.TrimSpace(req.UserAgent)
  69. query = query.Where("user_agent LIKE CONCAT('%', ?, '%')", trimmedName)
  70. }
  71. if req.ApiName != "" {
  72. trimmedName := strings.TrimSpace(req.ApiName)
  73. query = query.Where("api_name LIKE CONCAT('%', ?, '%')", trimmedName)
  74. }
  75. if req.ApiType != "" {
  76. query = query.Where("api_type = ?", req.ApiType)
  77. }
  78. if req.Column != "" {
  79. query = query.Order(req.Column + " " + req.Order)
  80. }
  81. if err := query.Count(&total).Error; err != nil {
  82. return nil, err
  83. }
  84. page := req.Current
  85. pageSize := req.PageSize
  86. if page <= 0 {
  87. page = 1
  88. }
  89. if pageSize <= 0 {
  90. pageSize = 10
  91. } else if pageSize > 100 {
  92. pageSize = 100
  93. }
  94. offset := (page - 1) * pageSize
  95. result := query.Offset(offset).Limit(pageSize).Find(&res)
  96. if result.Error != nil {
  97. return nil, result.Error
  98. }
  99. return &v1.PaginatedResponse[model.WafLog]{
  100. Records: res,
  101. Page: page,
  102. PageSize: pageSize,
  103. Total: total,
  104. TotalPages: int(math.Ceil(float64(total) / float64(pageSize))),
  105. }, nil
  106. }
  107. func (r *wafLogRepository) AddWafLog(ctx context.Context, log *model.WafLog) error {
  108. return r.DBWithName(ctx,"admin").Create(log).Error
  109. }
  110. func (r *wafLogRepository) BatchAddWafLog(ctx context.Context, logs []*model.WafLog) error {
  111. if len(logs) == 0 {
  112. return nil
  113. }
  114. return r.DBWithName(ctx, "admin").CreateInBatches(logs, len(logs)).Error
  115. }
  116. func (r *wafLogRepository) ExportWafLog(ctx context.Context, req adminApi.ExportWafLog) ([]model.WafLogWithGatewayIP, error) {
  117. return r.ExportWafLogWithPagination(ctx, req, 0, 0)
  118. }
  119. // ExportWafLogWithPagination 使用子查询获取每条日志在当时时间点的正确网关组IP
  120. func (r *wafLogRepository) ExportWafLogWithPagination(ctx context.Context, req adminApi.ExportWafLog, page, pageSize int) ([]model.WafLogWithGatewayIP, error) {
  121. var res []model.WafLogWithGatewayIP
  122. // 主查询,我们将其命名为 "wl" 以便在子查询中引用
  123. query := r.DBWithName(ctx, "admin").Model(&model.WafLog{}).Table("waf_log as wl")
  124. // --- 构建子查询 ---
  125. // 这个子查询的目标是:对于 "wl" 表中的每一行,找到在它创建时间点之前(或同时)的、
  126. // host_id 和 uid 都匹配的、最新的那条 "分配网关组" 的日志,并返回其 extra_data。
  127. subQuery := r.DBWithName(ctx, "admin").Model(&model.WafLog{}).
  128. Select("extra_data").
  129. Where("api_name = ?", "分配网关组").
  130. Where("host_id = wl.host_id"). // 关联主查询的 host_id
  131. Where("uid = wl.uid"). // 关联主查询的 uid
  132. Where("created_at <= wl.created_at"). // 时间条件:必须是历史或当前记录
  133. Order("created_at DESC"). // 按时间降序,保证第一条是最新
  134. Limit(1) // 只取最新的一条
  135. // --- 构建主查询的选择列表 ---
  136. // "wl.*" 选择 waf_log 表的所有字段
  137. // 第二个参数是使用子查询作为 "gateway_ip_data" 字段的值
  138. query = query.Select("wl.*, (?) as gateway_ip_data", subQuery)
  139. // --- 应用过滤条件 (与原函数保持一致) ---
  140. if req.RequestIp != "" {
  141. query = query.Where("wl.request_ip = ?", strings.TrimSpace(req.RequestIp))
  142. }
  143. if req.Uid != 0 {
  144. query = query.Where("wl.uid = ?", req.Uid)
  145. }
  146. if req.Api != "" {
  147. query = query.Where("wl.api = ?", strings.TrimSpace(req.Api))
  148. }
  149. if req.Name != "" {
  150. query = query.Where("wl.name = ?", strings.TrimSpace(req.Name))
  151. }
  152. if req.RuleId != 0 {
  153. query = query.Where("wl.rule_id = ?", req.RuleId)
  154. }
  155. if len(req.HostIds) > 0 {
  156. query = query.Where("wl.host_id IN ?", req.HostIds)
  157. }
  158. if req.UserAgent != "" {
  159. query = query.Where("wl.user_agent = ?", strings.TrimSpace(req.UserAgent))
  160. }
  161. if len(req.ApiNames) > 0 {
  162. query = query.Where("wl.api_name IN ?", req.ApiNames)
  163. }
  164. if len(req.ApiTypes) > 0 {
  165. query = query.Where("wl.api_type IN ?", req.ApiTypes)
  166. }
  167. if req.StartTime != "" {
  168. query = query.Where("wl.created_at > ?", strings.TrimSpace(req.StartTime))
  169. }
  170. if req.EndTime != "" {
  171. query = query.Where("wl.created_at < ?", strings.TrimSpace(req.EndTime))
  172. }
  173. // --- 应用分页 ---
  174. if page > 0 && pageSize > 0 {
  175. offset := (page - 1) * pageSize
  176. query = query.Offset(offset).Limit(pageSize)
  177. }
  178. // 执行查询
  179. if err := query.Find(&res).Error; err != nil {
  180. // 如果是记录未找到的错误,我们希望返回一个空切片而不是错误
  181. if err == gorm.ErrRecordNotFound {
  182. return []model.WafLogWithGatewayIP{}, nil
  183. }
  184. return nil, err
  185. }
  186. return res, nil
  187. }
  188. // GetWafLogExportCount 获取导出数据总数
  189. func (r *wafLogRepository) GetWafLogExportCount(ctx context.Context, req adminApi.ExportWafLog) (int, error) {
  190. var count int64
  191. query := r.DBWithName(ctx,"admin").Model(&model.WafLog{})
  192. // 复用ExportWafLog的查询条件
  193. if req.RequestIp != "" {
  194. trimmedName := strings.TrimSpace(req.RequestIp)
  195. query = query.Where("request_ip = ?", trimmedName)
  196. }
  197. if req.Uid != 0 {
  198. query = query.Where("uid = ?", req.Uid)
  199. }
  200. if req.Api != "" {
  201. trimmedName := strings.TrimSpace(req.Api)
  202. query = query.Where("api = ?", trimmedName)
  203. }
  204. if req.Name != "" {
  205. trimmedName := strings.TrimSpace(req.Name)
  206. query = query.Where("name = ?", trimmedName)
  207. }
  208. if req.RuleId != 0 {
  209. query = query.Where("rule_id = ?", req.RuleId)
  210. }
  211. if len(req.HostIds) > 0 {
  212. query = query.Where("host_id IN ?", req.HostIds)
  213. }
  214. if req.UserAgent != "" {
  215. trimmedName := strings.TrimSpace(req.UserAgent)
  216. query = query.Where("user_agent = ?", trimmedName)
  217. }
  218. if len(req.ApiNames) > 0 {
  219. trimmedNames := make([]string, len(req.ApiNames))
  220. for i, apiName := range req.ApiNames {
  221. trimmedNames[i] = strings.TrimSpace(apiName)
  222. }
  223. query = query.Where("api_name IN ?", trimmedNames)
  224. }
  225. if len(req.ApiTypes) > 0 {
  226. query = query.Where("api_type IN ?", req.ApiTypes)
  227. }
  228. if req.StartTime != "" {
  229. trimmedName := strings.TrimSpace(req.StartTime)
  230. query = query.Where("created_at > ?", trimmedName)
  231. }
  232. if req.EndTime != "" {
  233. trimmedName := strings.TrimSpace(req.EndTime)
  234. query = query.Where("created_at < ?", trimmedName)
  235. }
  236. result := query.Count(&count)
  237. if result.Error != nil {
  238. return 0, result.Error
  239. }
  240. return int(count), nil
  241. }