ソースを参照

refactor(waflog): 重构 WAF 日志导出功能- 新增 WafLogWithGatewayIP 结构体,用于存储 WAF 日志和网关 IP 数据
- 重构 ExportWafLog 和 ExportWafLogWithPagination 方法,使用子查询获取网关 IP 数据
- 移除 GetWafLogGateWayIp 和 BatchGetWafLogGateWayIps 方法,简化代码结构
- 优化数据转换逻辑,直接使用子查询结果解析网关 IP
-调整分页和导出相关逻辑,适应新的数据结构

fusu 3 日 前
コミット
702309519f
3 ファイル変更79 行追加219 行削除
  1. 6 0
      internal/model/waflog.go
  2. 58 127
      internal/repository/admin/waflog.go
  3. 15 92
      internal/service/admin/waflog.go

+ 6 - 0
internal/model/waflog.go

@@ -24,3 +24,9 @@ type WafLog struct {
 func (m *WafLog) TableName() string {
     return "waf_log"
 }
+
+// WafLogWithGatewayIP 包含WAF日志及其在特定时间点的网关IP数据
+type WafLogWithGatewayIP struct {
+	WafLog
+	GatewayIpData json.RawMessage `json:"gatewayIpData" gorm:"column:gateway_ip_data"`
+}

+ 58 - 127
internal/repository/admin/waflog.go

@@ -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
-}

+ 15 - 92
internal/service/admin/waflog.go

@@ -89,25 +89,14 @@ type wafLogService struct {
 	wafLogDataCleanService WafLogDataCleanService
 }
 func (s *wafLogService) getFirstPathSegment(path string) (segment []string, ok bool) {
-	// 1. 为了统一处理,先去掉路径最前面的 "/"
-	// 这样 "/v1/admin" 会变成 "v1/admin",而 "v1/admin" 保持不变
 	trimmedPath := strings.TrimPrefix(path, "/")
-
-	// 如果去掉 "/" 后字符串为空(比如原路径是 "/" 或 ""),则无法提取
 	if trimmedPath == "" {
 		return nil, false
 	}
-
-	// 2. 使用 "/" 作为分隔符来切割字符串
-	// "v1/admin/menus" 会被切割成一个字符串切片 (slice): ["v1", "admin", "menus"]
 	parts := strings.Split(trimmedPath, "/")
-
-	// 3. 只要切片不为空,第一个元素就是我们需要的值
-	// len(parts) > 0 这个检查可以保证程序不会因为空切片而出错
 	if len(parts) > 0 {
 		return parts, true
 	}
-
 	return nil, false
 }
 
@@ -208,7 +197,6 @@ func (s *wafLogService) BatchAddWafLog(ctx context.Context, reqs []*adminApi.Waf
 		})
 	}
 	
-	// 调用repository层的批量插入方法
 	return s.wafLogRepository.BatchAddWafLog(ctx, wafLogs)
 }
 
@@ -216,31 +204,26 @@ func (s *wafLogService) PublishIpWafLogTask(ctx context.Context, req adminApi.Wa
 
 	payload := &req
 
-	// Serialize the message
 	msgBody, err := json.Marshal(payload)
 	if err != nil {
 		s.Logger.Error("序列化 WafLog 任务消息失败", zap.Error(err), zap.Int("hostId", payload.HostId), zap.Int("uid", payload.Uid), zap.Any("req", req))
 		return
 	}
 
-	// Get task configuration
 	taskCfg, ok := s.mq.GetTaskConfig("waf_log")
 	if !ok {
 		s.Logger.Error("无法获取“waf_Log”任务配置")
 		return
 	}
 
-	// Construct the routing key dynamically based on the action
 	routingKey := fmt.Sprintf("wafLog.%s", "add")
 
-	// Construct the amqp.Publishing message
 	publishingMsg := amqp.Publishing{
 		ContentType:  "application/json",
 		Body:         msgBody,
-		DeliveryMode: amqp.Persistent, // Persistent message
+		DeliveryMode: amqp.Persistent,
 	}
 
-	// Publish the message
 	err = s.mq.PublishWithCh(taskCfg.Exchange, routingKey, publishingMsg)
 	if err != nil {
 		s.Logger.Error("发布 WafLog 任务消息失败", zap.Error(err), zap.Int("hostId", payload.HostId), zap.Int("uid", payload.Uid), zap.Any("req", req))
@@ -250,29 +233,21 @@ func (s *wafLogService) PublishIpWafLogTask(ctx context.Context, req adminApi.Wa
 }
 
 func (s *wafLogService) ExPortWafLog(ctx context.Context, req adminApi.ExportWafLog) ([]adminApi.ExportWafLogRes, error) {
-	// 获取原始数据
 	data, err := s.wafLogRepository.ExportWafLog(ctx, req)
 	if err != nil {
 		return nil, err
 	}
-
-	// 使用优化后的转换方法,避免N+1查询
 	return s.convertRawDataToExportResults(ctx, data)
 }
 
-// SmartExportWafLog 智能导出WAF日志为Excel
 func (s *wafLogService) SmartExportWafLog(ctx context.Context, req adminApi.ExportWafLog, w http.ResponseWriter) error {
-	// 1. 先获取总数量用于智能选择传输方式
 	count, err := s.wafLogRepository.GetWafLogExportCount(ctx, req)
 	if err != nil {
 		return fmt.Errorf("获取导出数据总数失败: %w", err)
 	}
 
-	// 2. 智能选择导出方式
-	// 估算每行数据大小约200字节(包含用户名、IP、API名称、域名等字段)
 	exportType := excel.SmartExport(count, 200)
 	
-	// 3. 设置Excel表头映射
 	headers := []string{"name", "request_ip", "host_id", "rule_id", "api_name", "addr_backend_list", "allow_and_deny_ips", "domain", "custom_host", "expose_addr", "comment", "created_at"}
 	headerMap := map[string]string{
 		"name":             "用户名",
@@ -289,13 +264,11 @@ func (s *wafLogService) SmartExportWafLog(ctx context.Context, req adminApi.Expo
 		"created_at":       "操作时间",
 	}
 
-	// 4. 创建Excel生成器
 	generator := excel.NewExcelGenerator("WAF日志", headers, headerMap)
 	if err := generator.WriteHeaders(); err != nil {
 		return fmt.Errorf("写入Excel表头失败: %w", err)
 	}
 
-	// 5. 根据导出类型选择不同的处理方式
 	switch exportType {
 	case excel.ExportTypeNormal:
 		return s.normalExportWafLog(ctx, req, generator, w)
@@ -308,15 +281,12 @@ func (s *wafLogService) SmartExportWafLog(ctx context.Context, req adminApi.Expo
 	}
 }
 
-// normalExportWafLog 普通导出(小文件)
 func (s *wafLogService) normalExportWafLog(ctx context.Context, req adminApi.ExportWafLog, generator *excel.ExcelGenerator, w http.ResponseWriter) error {
-	// 获取所有数据(已经优化了批量查询)
 	exportData, err := s.ExPortWafLog(ctx, req)
 	if err != nil {
 		return fmt.Errorf("获取导出数据失败: %w", err)
 	}
 
-	// 转换数据格式
 	data := make([]map[string]interface{}, 0, len(exportData))
 	for _, item := range exportData {
 		row := map[string]interface{}{
@@ -336,12 +306,10 @@ func (s *wafLogService) normalExportWafLog(ctx context.Context, req adminApi.Exp
 		data = append(data, row)
 	}
 
-	// 写入数据
 	if err := generator.WriteRows(data); err != nil {
 		return fmt.Errorf("写入Excel数据失败: %w", err)
 	}
 
-	// 普通导出
 	fileName := fmt.Sprintf("waf_logs_%s.xlsx", time.Now().Format("20060102_150405"))
 	return excel.NormalExport(generator, w, excel.TransferOption{
 		FileName:    fileName,
@@ -349,37 +317,31 @@ func (s *wafLogService) normalExportWafLog(ctx context.Context, req adminApi.Exp
 	})
 }
 
-// streamExportWafLog 流式导出(大文件)
 func (s *wafLogService) streamExportWafLog(ctx context.Context, req adminApi.ExportWafLog, generator *excel.ExcelGenerator, w http.ResponseWriter) error {
 	fileName := fmt.Sprintf("waf_logs_%s.xlsx", time.Now().Format("20060102_150405"))
 	
-	// 设置响应头
 	w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
 	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName))
 	w.Header().Set("Transfer-Encoding", "chunked")
 
-	// 分批处理数据,每批1000条
 	pageSize := 1000
 	page := 1
 
 	for {
-		// 使用分页导出方法
 		exportData, err := s.wafLogRepository.ExportWafLogWithPagination(ctx, req, page, pageSize)
 		if err != nil {
 			return fmt.Errorf("获取第%d页数据失败: %w", page, err)
 		}
 
-		// 转换为导出格式(复用原有的ExPortWafLog逻辑)
 		exportResults, err := s.convertRawDataToExportResults(ctx, exportData)
 		if err != nil {
 			return fmt.Errorf("转换导出数据失败: %w", err)
 		}
 
 		if len(exportResults) == 0 {
-			break // 没有更多数据
+			break 
 		}
 
-		// 转换并写入当前批次数据
 		for _, item := range exportResults {
 			row := map[string]interface{}{
 				"name":             item.Name,
@@ -401,7 +363,6 @@ func (s *wafLogService) streamExportWafLog(ctx context.Context, req adminApi.Exp
 			}
 		}
 
-		// 如果当前批次数据少于页大小,说明已经是最后一页
 		if len(exportResults) < pageSize {
 			break
 		}
@@ -409,90 +370,52 @@ func (s *wafLogService) streamExportWafLog(ctx context.Context, req adminApi.Exp
 		page++
 	}
 
-	// 流式导出
 	return excel.StreamExport(generator, w, excel.TransferOption{
 		FileName:    fileName,
 		ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 	})
 }
 
-// chunkExportWafLog 分块导出(超大文件)
 func (s *wafLogService) chunkExportWafLog(ctx context.Context, req adminApi.ExportWafLog, w http.ResponseWriter, totalRecords int) error {
 	fileName := fmt.Sprintf("waf_logs_%s.xlsx", time.Now().Format("20060102_150405"))
-	pageSize := 5000 // 每个分块5000条记录
+	pageSize := 5000 
 	
-	// 分块导出需要前端配合实现
 	excel.ChunkExport(w, excel.TransferOption{
 		FileName:    fileName,
-		ContentType: "application/json", // 返回分块信息
+		ContentType: "application/json", 
 	}, totalRecords, pageSize)
 	
 	return nil
 }
 
-
-
-
-
-
-
-
-
-// convertRawDataToExportResults 将原始数据转换为导出结果(复用原有的ExPortWafLog逻辑)
-func (s *wafLogService) convertRawDataToExportResults(ctx context.Context, rawData []model.WafLog) ([]adminApi.ExportWafLogRes, error) {
+// convertRawDataToExportResults 将从数据库获取的带有关联IP的数据转换为最终导出格式
+func (s *wafLogService) convertRawDataToExportResults(ctx context.Context, rawData []model.WafLogWithGatewayIP) ([]adminApi.ExportWafLogRes, error) {
 	if len(rawData) == 0 {
 		return []adminApi.ExportWafLogRes{}, nil
 	}
 
-	// 批量准备逻辑保持不变...
-	hostIds := make([]int64, 0, len(rawData))
-	uids := make([]int64, 0, len(rawData))
-	maxCreatedAt := time.Time{}
-	for _, v := range rawData {
-		hostIds = append(hostIds, int64(v.HostId))
-		uids = append(uids, int64(v.Uid))
-		if v.CreatedAt.After(maxCreatedAt) {
-			maxCreatedAt = v.CreatedAt
-		}
-	}
-	gatewayMap, err := s.wafLogRepository.BatchGetWafLogGateWayIps(ctx, hostIds, uids, maxCreatedAt)
-	if err != nil {
-		s.Logger.Warn("批量获取网关组失败,降级为单个查询", zap.Error(err))
-		gatewayMap = make(map[string]model.WafLog)
-	}
-
 	var res []adminApi.ExportWafLogRes
 	for _, v := range rawData {
-		// --- 核心改动:一行代码完成所有数据清洗 ---
+		// --- 数据清洗 ---
 		cleanedData := s.wafLogDataCleanService.ParseWafLogExtraData(v.ExtraData, v.ApiName)
 
-		// 网关 IP 获取逻辑保持不变,但使用清洗后的 port
+		// --- 网关 IP 处理 ---
 		var exposeAddr []string
-		key := fmt.Sprintf("%d_%d", v.HostId, v.Uid)
-		if gatewayModel, exists := gatewayMap[key]; exists {
+		// 直接使用查询结果中附带的 gateway_ip_data
+		if len(v.GatewayIpData) > 0 && string(v.GatewayIpData) != "null" {
 			var gateWayIps []string
-			if err := json.Unmarshal(gatewayModel.ExtraData, &gateWayIps); err == nil && len(gateWayIps) > 0 && cleanedData.Port != "" {
+			// 解析从数据库子查询得到的JSON数据
+			if err := json.Unmarshal(v.GatewayIpData, &gateWayIps); err == nil && len(gateWayIps) > 0 && cleanedData.Port != "" {
 				for _, ip := range gateWayIps {
 					exposeAddr = append(exposeAddr, ip+":"+cleanedData.Port)
 				}
 			}
-		} else {
-			// 降级查询逻辑...
-			gateWayIpModel, err := s.wafLogRepository.GetWafLogGateWayIp(ctx, int64(v.HostId), int64(v.Uid), v.CreatedAt)
-			if err == nil {
-				var gateWayIps []string
-				if err := json.Unmarshal(gateWayIpModel.ExtraData, &gateWayIps); err == nil && len(gateWayIps) > 0 && cleanedData.Port != "" {
-					for _, ip := range gateWayIps {
-						exposeAddr = append(exposeAddr, ip+":"+cleanedData.Port)
-					}
-				}
-			}
 		}
 
 		var ruleIds []int64
 		if len(cleanedData.RuleID) > 0 {
 			ruleIds = cleanedData.RuleID
-		}else {
+		} else {
 			ruleIds = []int64{int64(v.RuleId)}
 		}
 
@@ -502,7 +425,7 @@ func (s *wafLogService) convertRawDataToExportResults(ctx context.Context, rawDa
 			}
 		}
 
-		// 构造结果,代码更清晰
+		// --- 构造结果 ---
 		res = append(res, adminApi.ExportWafLogRes{
 			Name:            v.Name,
 			RequestIp:       v.RequestIp,
@@ -525,4 +448,4 @@ func (s *wafLogService) convertRawDataToExportResults(ctx context.Context, rawDa
 // GetApiDescriptions 获取API描述映射
 func (s *wafLogService) GetApiDescriptions(ctx context.Context) map[string]string {
 	return ApiDescriptionMap
-}
+}