|
@@ -2,14 +2,13 @@ package admin
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
- "fmt"
|
|
|
v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
|
|
|
adminApi "github.com/go-nunu/nunu-layout-advanced/api/v1/admin"
|
|
|
"github.com/go-nunu/nunu-layout-advanced/internal/model"
|
|
|
"github.com/go-nunu/nunu-layout-advanced/internal/repository"
|
|
|
+ "gorm.io/gorm"
|
|
|
"math"
|
|
|
"strings"
|
|
|
- "time"
|
|
|
)
|
|
|
|
|
|
type WafLogRepository interface {
|
|
@@ -17,16 +16,9 @@ type WafLogRepository interface {
|
|
|
GetWafLogList(ctx context.Context, req adminApi.SearchWafLogParams) (*v1.PaginatedResponse[model.WafLog], error)
|
|
|
AddWafLog(ctx context.Context, log *model.WafLog) error
|
|
|
BatchAddWafLog(ctx context.Context, logs []*model.WafLog) error
|
|
|
- // 导出日志
|
|
|
- ExportWafLog(ctx context.Context, req adminApi.ExportWafLog) ([]model.WafLog, error)
|
|
|
- // 支持分页的导出方法
|
|
|
- ExportWafLogWithPagination(ctx context.Context, req adminApi.ExportWafLog, page, pageSize int) ([]model.WafLog, error)
|
|
|
- // 获取导出数据总数
|
|
|
+ ExportWafLog(ctx context.Context, req adminApi.ExportWafLog) ([]model.WafLogWithGatewayIP, error)
|
|
|
+ ExportWafLogWithPagination(ctx context.Context, req adminApi.ExportWafLog, page, pageSize int) ([]model.WafLogWithGatewayIP, error)
|
|
|
GetWafLogExportCount(ctx context.Context, req adminApi.ExportWafLog) (int, error)
|
|
|
- // 获取网关组记录
|
|
|
- GetWafLogGateWayIp(ctx context.Context, hostId int64, Uid int64,createdAt time.Time) (model.WafLog, error)
|
|
|
- // 批量获取网关组记录
|
|
|
- BatchGetWafLogGateWayIps(ctx context.Context, hostIds []int64, uids []int64, maxCreatedAt time.Time) (map[string]model.WafLog, error)
|
|
|
}
|
|
|
|
|
|
func NewWafLogRepository(
|
|
@@ -41,6 +33,49 @@ type wafLogRepository struct {
|
|
|
*repository.Repository
|
|
|
}
|
|
|
|
|
|
+// buildExportQuery 是一个辅助函数,用于构建导出日志的公共查询条件
|
|
|
+func (r *wafLogRepository) buildExportQuery(ctx context.Context, req adminApi.ExportWafLog) *gorm.DB {
|
|
|
+ // 使用 Table("waf_log as wl") 是为了给主表起一个别名,方便子查询中引用
|
|
|
+ query := r.DBWithName(ctx, "admin").Model(&model.WafLog{}).Table("waf_log as wl")
|
|
|
+
|
|
|
+ if req.RequestIp != "" {
|
|
|
+ query = query.Where("wl.request_ip = ?", strings.TrimSpace(req.RequestIp))
|
|
|
+ }
|
|
|
+ if req.Uid != 0 {
|
|
|
+ query = query.Where("wl.uid = ?", req.Uid)
|
|
|
+ }
|
|
|
+ if req.Api != "" {
|
|
|
+ query = query.Where("wl.api = ?", strings.TrimSpace(req.Api))
|
|
|
+ }
|
|
|
+ if req.Name != "" {
|
|
|
+ query = query.Where("wl.name = ?", strings.TrimSpace(req.Name))
|
|
|
+ }
|
|
|
+ if req.RuleId != 0 {
|
|
|
+ query = query.Where("wl.rule_id = ?", req.RuleId)
|
|
|
+ }
|
|
|
+ if len(req.HostIds) > 0 {
|
|
|
+ query = query.Where("wl.host_id IN ?", req.HostIds)
|
|
|
+ }
|
|
|
+ if req.UserAgent != "" {
|
|
|
+ query = query.Where("wl.user_agent = ?", strings.TrimSpace(req.UserAgent))
|
|
|
+ }
|
|
|
+ if len(req.ApiNames) > 0 {
|
|
|
+ query = query.Where("wl.api_name IN ?", req.ApiNames)
|
|
|
+ }
|
|
|
+ if len(req.ApiTypes) > 0 {
|
|
|
+ query = query.Where("wl.api_type IN ?", req.ApiTypes)
|
|
|
+ }
|
|
|
+ if req.StartTime != "" {
|
|
|
+ query = query.Where("wl.created_at > ?", strings.TrimSpace(req.StartTime))
|
|
|
+ }
|
|
|
+ if req.EndTime != "" {
|
|
|
+ query = query.Where("wl.created_at < ?", strings.TrimSpace(req.EndTime))
|
|
|
+ }
|
|
|
+
|
|
|
+ return query
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
func (r *wafLogRepository) GetWafLog(ctx context.Context, id int64) (*model.WafLog, error) {
|
|
|
var res model.WafLog
|
|
|
return &res, r.DBWithName(ctx,"admin").Where("id = ?", id).First(&res).Error
|
|
@@ -53,7 +88,6 @@ func (r *wafLogRepository) GetWafLogList(ctx context.Context, req adminApi.Searc
|
|
|
query := r.DBWithName(ctx,"admin").Model(&model.WafLog{})
|
|
|
if req.RequestIp != "" {
|
|
|
trimmedName := strings.TrimSpace(req.RequestIp)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
query = query.Where("request_ip LIKE CONCAT('%', ?, '%')", trimmedName)
|
|
|
}
|
|
|
|
|
@@ -64,13 +98,11 @@ func (r *wafLogRepository) GetWafLogList(ctx context.Context, req adminApi.Searc
|
|
|
|
|
|
if req.Api != "" {
|
|
|
trimmedName := strings.TrimSpace(req.Api)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
query = query.Where("api LIKE CONCAT('%', ?, '%')", trimmedName)
|
|
|
}
|
|
|
|
|
|
if req.Name != "" {
|
|
|
trimmedName := strings.TrimSpace(req.Name)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
query = query.Where("name LIKE CONCAT('%', ?, '%')", trimmedName)
|
|
|
}
|
|
|
|
|
@@ -85,19 +117,16 @@ func (r *wafLogRepository) GetWafLogList(ctx context.Context, req adminApi.Searc
|
|
|
|
|
|
if req.Api != "" {
|
|
|
trimmedName := strings.TrimSpace(req.Api)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
query = query.Where("api LIKE CONCAT('%', ?, '%')", trimmedName)
|
|
|
}
|
|
|
|
|
|
if req.UserAgent != "" {
|
|
|
trimmedName := strings.TrimSpace(req.UserAgent)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
query = query.Where("user_agent LIKE CONCAT('%', ?, '%')", trimmedName)
|
|
|
}
|
|
|
|
|
|
if req.ApiName != "" {
|
|
|
trimmedName := strings.TrimSpace(req.ApiName)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
query = query.Where("api_name LIKE CONCAT('%', ?, '%')", trimmedName)
|
|
|
}
|
|
|
|
|
@@ -111,7 +140,6 @@ func (r *wafLogRepository) GetWafLogList(ctx context.Context, req adminApi.Searc
|
|
|
}
|
|
|
|
|
|
if err := query.Count(&total).Error; err != nil {
|
|
|
- // 如果连计数都失败了,直接返回错误
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
@@ -124,20 +152,14 @@ func (r *wafLogRepository) GetWafLogList(ctx context.Context, req adminApi.Searc
|
|
|
}
|
|
|
|
|
|
if pageSize <= 0 {
|
|
|
- pageSize = 10 // 默认每页 10 条
|
|
|
+ pageSize = 10
|
|
|
} else if pageSize > 100 {
|
|
|
- pageSize = 100 // 每页最多 100 条
|
|
|
+ pageSize = 100
|
|
|
}
|
|
|
|
|
|
- // 计算 offset (偏移量)
|
|
|
- // 例如,第 1 页,offset = (1-1)*10 = 0 (从第0条开始)
|
|
|
- // 第 2 页,offset = (2-1)*10 = 10 (从第10条开始)
|
|
|
offset := (page - 1) * pageSize
|
|
|
- // 3. 执行最终的查询
|
|
|
- // 在所有条件都添加完毕后,再执行 .Find()
|
|
|
result := query.Offset(offset).Limit(pageSize).Find(&res)
|
|
|
if result.Error != nil {
|
|
|
- // 这里的错误可能是数据库连接问题等,而不是“未找到记录”
|
|
|
return nil, result.Error
|
|
|
}
|
|
|
return &v1.PaginatedResponse[model.WafLog]{
|
|
@@ -161,202 +183,56 @@ func (r *wafLogRepository) BatchAddWafLog(ctx context.Context, logs []*model.Waf
|
|
|
return r.DBWithName(ctx, "admin").CreateInBatches(logs, len(logs)).Error
|
|
|
}
|
|
|
|
|
|
-func (r *wafLogRepository) ExportWafLog(ctx context.Context, req adminApi.ExportWafLog) ([]model.WafLog, error) {
|
|
|
+func (r *wafLogRepository) ExportWafLog(ctx context.Context, req adminApi.ExportWafLog) ([]model.WafLogWithGatewayIP, error) {
|
|
|
return r.ExportWafLogWithPagination(ctx, req, 0, 0)
|
|
|
}
|
|
|
|
|
|
-// ExportWafLogWithPagination 支持分页的导出方法
|
|
|
-func (r *wafLogRepository) ExportWafLogWithPagination(ctx context.Context, req adminApi.ExportWafLog, page, pageSize int) ([]model.WafLog, error) {
|
|
|
- var res []model.WafLog
|
|
|
- query := r.DBWithName(ctx,"admin").Model(&model.WafLog{})
|
|
|
- if req.RequestIp != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.RequestIp)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("request_ip = ?", trimmedName)
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- if req.Uid != 0 {
|
|
|
- query = query.Where("uid = ?", req.Uid)
|
|
|
- }
|
|
|
-
|
|
|
- if req.Api != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.Api)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("api = ?", trimmedName)
|
|
|
- }
|
|
|
-
|
|
|
- if req.Name != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.Name)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("name = ?", trimmedName)
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- if req.RuleId != 0 {
|
|
|
- query = query.Where("rule_id = ?", req.RuleId)
|
|
|
- }
|
|
|
-
|
|
|
- if len(req.HostIds) > 0 {
|
|
|
- query = query.Where("host_id IN ?", req.HostIds)
|
|
|
- }
|
|
|
-
|
|
|
- if req.Api != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.Api)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("api = ?", trimmedName)
|
|
|
- }
|
|
|
-
|
|
|
- if req.UserAgent != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.UserAgent)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("user_agent = ?", trimmedName)
|
|
|
- }
|
|
|
-
|
|
|
- if len(req.ApiNames) > 0 {
|
|
|
- trimmedNames := make([]string, len(req.ApiNames))
|
|
|
- for _, apiName := range req.ApiNames {
|
|
|
- trimmedNames = append(trimmedNames, strings.TrimSpace(apiName))
|
|
|
- }
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("api_name IN ?", trimmedNames)
|
|
|
- }
|
|
|
-
|
|
|
- if len(req.ApiTypes) > 0 {
|
|
|
- query = query.Where("api_type IN ?", req.ApiTypes)
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- if req.StartTime != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.StartTime)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("created_at > ?", trimmedName)
|
|
|
- }
|
|
|
-
|
|
|
- if req.EndTime != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.EndTime)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("created_at < ?", trimmedName)
|
|
|
- }
|
|
|
+// ExportWafLogWithPagination 使用子查询获取每条日志在当时时间点的正确网关组IP
|
|
|
+func (r *wafLogRepository) ExportWafLogWithPagination(ctx context.Context, req adminApi.ExportWafLog, page, pageSize int) ([]model.WafLogWithGatewayIP, error) {
|
|
|
+ var res []model.WafLogWithGatewayIP
|
|
|
+
|
|
|
+ // 1. 使用辅助函数构建基础查询
|
|
|
+ query := r.buildExportQuery(ctx, req)
|
|
|
|
|
|
- // 添加分页逻辑
|
|
|
+ // 2. 构建子查询
|
|
|
+ subQuery := r.DBWithName(ctx, "admin").Model(&model.WafLog{}).
|
|
|
+ Select("extra_data").
|
|
|
+ Where("api_name = ?", "分配网关组").
|
|
|
+ Where("host_id = wl.host_id").
|
|
|
+ Where("uid = wl.uid").
|
|
|
+ Where("created_at <= wl.created_at").
|
|
|
+ Order("created_at DESC").
|
|
|
+ Limit(1)
|
|
|
+
|
|
|
+ // 3. 添加 Select 和分页
|
|
|
+ query = query.Select("wl.*, (?) as gateway_ip_data", subQuery)
|
|
|
if page > 0 && pageSize > 0 {
|
|
|
offset := (page - 1) * pageSize
|
|
|
query = query.Offset(offset).Limit(pageSize)
|
|
|
}
|
|
|
|
|
|
- result := query.Find(&res)
|
|
|
- if result.Error != nil {
|
|
|
- return nil, result.Error
|
|
|
- }
|
|
|
- return res, nil
|
|
|
-}
|
|
|
-
|
|
|
-// GetWafLogExportCount 获取导出数据总数
|
|
|
-func (r *wafLogRepository) GetWafLogExportCount(ctx context.Context, req adminApi.ExportWafLog) (int, error) {
|
|
|
- var count int64
|
|
|
- query := r.DBWithName(ctx,"admin").Model(&model.WafLog{})
|
|
|
-
|
|
|
- // 复用ExportWafLog的查询条件
|
|
|
- if req.RequestIp != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.RequestIp)
|
|
|
- query = query.Where("request_ip = ?", trimmedName)
|
|
|
- }
|
|
|
-
|
|
|
- if req.Uid != 0 {
|
|
|
- query = query.Where("uid = ?", req.Uid)
|
|
|
- }
|
|
|
-
|
|
|
- if req.Api != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.Api)
|
|
|
- query = query.Where("api = ?", trimmedName)
|
|
|
- }
|
|
|
-
|
|
|
- if req.Name != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.Name)
|
|
|
- query = query.Where("name = ?", trimmedName)
|
|
|
- }
|
|
|
-
|
|
|
- if req.RuleId != 0 {
|
|
|
- query = query.Where("rule_id = ?", req.RuleId)
|
|
|
- }
|
|
|
-
|
|
|
- if len(req.HostIds) > 0 {
|
|
|
- query = query.Where("host_id IN ?", req.HostIds)
|
|
|
- }
|
|
|
-
|
|
|
- if req.UserAgent != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.UserAgent)
|
|
|
- query = query.Where("user_agent = ?", trimmedName)
|
|
|
- }
|
|
|
-
|
|
|
- if len(req.ApiNames) > 0 {
|
|
|
- trimmedNames := make([]string, len(req.ApiNames))
|
|
|
- for _, apiName := range req.ApiNames {
|
|
|
- trimmedNames = append(trimmedNames, strings.TrimSpace(apiName))
|
|
|
+ // 4. 执行查询
|
|
|
+ if err := query.Find(&res).Error; err != nil {
|
|
|
+ if err == gorm.ErrRecordNotFound {
|
|
|
+ return []model.WafLogWithGatewayIP{}, nil
|
|
|
}
|
|
|
- query = query.Where("api_name IN ?", trimmedNames)
|
|
|
- }
|
|
|
-
|
|
|
- if len(req.ApiTypes) > 0 {
|
|
|
- query = query.Where("api_type IN ?", req.ApiTypes)
|
|
|
- }
|
|
|
-
|
|
|
- if req.StartTime != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.StartTime)
|
|
|
- query = query.Where("created_at > ?", trimmedName)
|
|
|
- }
|
|
|
-
|
|
|
- if req.EndTime != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.EndTime)
|
|
|
- query = query.Where("created_at < ?", trimmedName)
|
|
|
- }
|
|
|
-
|
|
|
- result := query.Count(&count)
|
|
|
- if result.Error != nil {
|
|
|
- return 0, result.Error
|
|
|
+ return nil, err
|
|
|
}
|
|
|
- return int(count), nil
|
|
|
-}
|
|
|
|
|
|
-func (r *wafLogRepository) GetWafLogGateWayIp(ctx context.Context, hostId int64, Uid int64,createdAt time.Time) (model.WafLog, error) {
|
|
|
- var res model.WafLog
|
|
|
- return res, r.DBWithName(ctx,"admin").Where("host_id = ? and uid = ? and api_name = ? and created_at < ? ", hostId, Uid, "分配网关组", createdAt).First(&res).Error
|
|
|
+ return res, nil
|
|
|
}
|
|
|
|
|
|
-// BatchGetWafLogGateWayIps 批量获取网关组记录,避免N+1查询问题
|
|
|
-func (r *wafLogRepository) BatchGetWafLogGateWayIps(ctx context.Context, hostIds []int64, uids []int64, maxCreatedAt time.Time) (map[string]model.WafLog, error) {
|
|
|
- if len(hostIds) == 0 || len(uids) == 0 {
|
|
|
- return make(map[string]model.WafLog), nil
|
|
|
- }
|
|
|
|
|
|
- var gateWayLogs []model.WafLog
|
|
|
+// GetWafLogExportCount 获取导出数据总数(已优化)
|
|
|
+func (r *wafLogRepository) GetWafLogExportCount(ctx context.Context, req adminApi.ExportWafLog) (int, error) {
|
|
|
+ var count int64
|
|
|
|
|
|
- // 构建查询条件,获取所有相关的网关组记录
|
|
|
- query := r.DBWithName(ctx, "admin").
|
|
|
- Where("api_name = ?", "分配网关组").
|
|
|
- Where("created_at < ?", maxCreatedAt)
|
|
|
+ // 直接复用 buildExportQuery 来构建查询
|
|
|
+ query := r.buildExportQuery(ctx, req)
|
|
|
|
|
|
- // 如果hostIds和uids数量较少,使用IN查询
|
|
|
- if len(hostIds) <= 1000 && len(uids) <= 1000 {
|
|
|
- query = query.Where("host_id IN ? AND uid IN ?", hostIds, uids)
|
|
|
+ if err := query.Count(&count).Error; err != nil {
|
|
|
+ return 0, err
|
|
|
}
|
|
|
|
|
|
- // 按创建时间倒序,确保获取最新的网关组配置
|
|
|
- err := query.Order("created_at DESC").Find(&gateWayLogs).Error
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
-
|
|
|
- // 构建映射表,key为"hostId_uid",value为最新的网关组记录
|
|
|
- result := make(map[string]model.WafLog)
|
|
|
- for _, log := range gateWayLogs {
|
|
|
- key := fmt.Sprintf("%d_%d", log.HostId, log.Uid)
|
|
|
- // 由于已经按创建时间倒序排列,第一次遇到的就是最新的记录
|
|
|
- if _, exists := result[key]; !exists {
|
|
|
- result[key] = log
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return result, nil
|
|
|
+ return int(count), nil
|
|
|
}
|