|
@@ -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,12 @@ 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(
|
|
@@ -53,7 +48,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 +58,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 +77,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 +100,6 @@ func (r *wafLogRepository) GetWafLogList(ctx context.Context, req adminApi.Searc
|
|
|
}
|
|
|
|
|
|
if err := query.Count(&total).Error; err != nil {
|
|
|
- // 如果连计数都失败了,直接返回错误
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
@@ -124,20 +112,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,97 +143,88 @@ 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)
|
|
|
+// ExportWafLogWithPagination 使用子查询获取每条日志在当时时间点的正确网关组IP
|
|
|
+func (r *wafLogRepository) ExportWafLogWithPagination(ctx context.Context, req adminApi.ExportWafLog, page, pageSize int) ([]model.WafLogWithGatewayIP, error) {
|
|
|
+ var res []model.WafLogWithGatewayIP
|
|
|
+
|
|
|
+ // 主查询,我们将其命名为 "wl" 以便在子查询中引用
|
|
|
+ query := r.DBWithName(ctx, "admin").Model(&model.WafLog{}).Table("waf_log as wl")
|
|
|
+
|
|
|
+ // --- 构建子查询 ---
|
|
|
+ // 这个子查询的目标是:对于 "wl" 表中的每一行,找到在它创建时间点之前(或同时)的、
|
|
|
+ // host_id 和 uid 都匹配的、最新的那条 "分配网关组" 的日志,并返回其 extra_data。
|
|
|
+ subQuery := r.DBWithName(ctx, "admin").Model(&model.WafLog{}).
|
|
|
+ Select("extra_data").
|
|
|
+ Where("api_name = ?", "分配网关组").
|
|
|
+ Where("host_id = wl.host_id"). // 关联主查询的 host_id
|
|
|
+ Where("uid = wl.uid"). // 关联主查询的 uid
|
|
|
+ Where("created_at <= wl.created_at"). // 时间条件:必须是历史或当前记录
|
|
|
+ Order("created_at DESC"). // 按时间降序,保证第一条是最新
|
|
|
+ Limit(1) // 只取最新的一条
|
|
|
+
|
|
|
+ // --- 构建主查询的选择列表 ---
|
|
|
+ // "wl.*" 选择 waf_log 表的所有字段
|
|
|
+ // 第二个参数是使用子查询作为 "gateway_ip_data" 字段的值
|
|
|
+ query = query.Select("wl.*, (?) as gateway_ip_data", subQuery)
|
|
|
+
|
|
|
+ // --- 应用过滤条件 (与原函数保持一致) ---
|
|
|
+ if req.RequestIp != "" {
|
|
|
+ query = query.Where("wl.request_ip = ?", strings.TrimSpace(req.RequestIp))
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
if req.Uid != 0 {
|
|
|
- query = query.Where("uid = ?", req.Uid)
|
|
|
+ query = query.Where("wl.uid = ?", req.Uid)
|
|
|
}
|
|
|
-
|
|
|
if req.Api != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.Api)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("api = ?", trimmedName)
|
|
|
+ query = query.Where("wl.api = ?", strings.TrimSpace(req.Api))
|
|
|
}
|
|
|
-
|
|
|
if req.Name != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.Name)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("name = ?", trimmedName)
|
|
|
+ query = query.Where("wl.name = ?", strings.TrimSpace(req.Name))
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
if req.RuleId != 0 {
|
|
|
- query = query.Where("rule_id = ?", req.RuleId)
|
|
|
+ query = query.Where("wl.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 len(req.HostIds) > 0 {
|
|
|
+ query = query.Where("wl.host_id IN ?", req.HostIds)
|
|
|
}
|
|
|
-
|
|
|
if req.UserAgent != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.UserAgent)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("user_agent = ?", trimmedName)
|
|
|
+ query = query.Where("wl.user_agent = ?", strings.TrimSpace(req.UserAgent))
|
|
|
}
|
|
|
-
|
|
|
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)
|
|
|
+ query = query.Where("wl.api_name IN ?", req.ApiNames)
|
|
|
}
|
|
|
-
|
|
|
if len(req.ApiTypes) > 0 {
|
|
|
- query = query.Where("api_type IN ?", req.ApiTypes)
|
|
|
+ query = query.Where("wl.api_type IN ?", req.ApiTypes)
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
if req.StartTime != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.StartTime)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("created_at > ?", trimmedName)
|
|
|
+ query = query.Where("wl.created_at > ?", strings.TrimSpace(req.StartTime))
|
|
|
}
|
|
|
-
|
|
|
if req.EndTime != "" {
|
|
|
- trimmedName := strings.TrimSpace(req.EndTime)
|
|
|
- // 使用 LIKE 进行模糊匹配
|
|
|
- query = query.Where("created_at < ?", trimmedName)
|
|
|
+ query = query.Where("wl.created_at < ?", strings.TrimSpace(req.EndTime))
|
|
|
}
|
|
|
|
|
|
- // 添加分页逻辑
|
|
|
+ // --- 应用分页 ---
|
|
|
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
|
|
|
+ // 执行查询
|
|
|
+ if err := query.Find(&res).Error; err != nil {
|
|
|
+ // 如果是记录未找到的错误,我们希望返回一个空切片而不是错误
|
|
|
+ if err == gorm.ErrRecordNotFound {
|
|
|
+ return []model.WafLogWithGatewayIP{}, nil
|
|
|
+ }
|
|
|
+ return nil, err
|
|
|
}
|
|
|
+
|
|
|
return res, nil
|
|
|
}
|
|
|
|
|
|
+
|
|
|
// GetWafLogExportCount 获取导出数据总数
|
|
|
func (r *wafLogRepository) GetWafLogExportCount(ctx context.Context, req adminApi.ExportWafLog) (int, error) {
|
|
|
var count int64
|
|
@@ -292,8 +265,8 @@ func (r *wafLogRepository) GetWafLogExportCount(ctx context.Context, req adminAp
|
|
|
|
|
|
if len(req.ApiNames) > 0 {
|
|
|
trimmedNames := make([]string, len(req.ApiNames))
|
|
|
- for _, apiName := range req.ApiNames {
|
|
|
- trimmedNames = append(trimmedNames, strings.TrimSpace(apiName))
|
|
|
+ for i, apiName := range req.ApiNames {
|
|
|
+ trimmedNames[i] = strings.TrimSpace(apiName)
|
|
|
}
|
|
|
query = query.Where("api_name IN ?", trimmedNames)
|
|
|
}
|
|
@@ -318,45 +291,3 @@ func (r *wafLogRepository) GetWafLogExportCount(ctx context.Context, req adminAp
|
|
|
}
|
|
|
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
|
|
|
-}
|
|
|
-
|
|
|
-// 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
|
|
|
-
|
|
|
- // 构建查询条件,获取所有相关的网关组记录
|
|
|
- query := r.DBWithName(ctx, "admin").
|
|
|
- Where("api_name = ?", "分配网关组").
|
|
|
- Where("created_at < ?", maxCreatedAt)
|
|
|
-
|
|
|
- // 如果hostIds和uids数量较少,使用IN查询
|
|
|
- if len(hostIds) <= 1000 && len(uids) <= 1000 {
|
|
|
- query = query.Where("host_id IN ? AND uid IN ?", hostIds, uids)
|
|
|
- }
|
|
|
-
|
|
|
- // 按创建时间倒序,确保获取最新的网关组配置
|
|
|
- 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
|
|
|
-}
|