229 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package mid
 | ||
| 
 | ||
| import (
 | ||
| 	"context"
 | ||
| 	"errors"
 | ||
| 	"net/http"
 | ||
| 	"sync"
 | ||
| 	"time"
 | ||
| 
 | ||
| 	systemmodel "management/internal/erpserver/model/system"
 | ||
| 	v1 "management/internal/erpserver/service/v1"
 | ||
| 	"management/internal/pkg/know"
 | ||
| 	"management/internal/pkg/session"
 | ||
| 
 | ||
| 	"github.com/drhin/logger"
 | ||
| 	"go.uber.org/zap"
 | ||
| )
 | ||
| 
 | ||
| // AuditBuffer 审计日志缓冲器
 | ||
| type AuditBuffer struct {
 | ||
| 	auditLogService v1.AuditLogService
 | ||
| 	log             *logger.Logger
 | ||
| 	buffer          chan *systemmodel.AuditLog
 | ||
| 	stopCh          chan struct{}
 | ||
| 	wg              sync.WaitGroup
 | ||
| 	batchSize       int
 | ||
| 	flushInterval   time.Duration
 | ||
| }
 | ||
| 
 | ||
| // NewAuditBuffer 创建审计日志缓冲器
 | ||
| func NewAuditBuffer(auditLogService v1.AuditLogService, log *logger.Logger) *AuditBuffer {
 | ||
| 	return &AuditBuffer{
 | ||
| 		auditLogService: auditLogService,
 | ||
| 		log:             log,
 | ||
| 		buffer:          make(chan *systemmodel.AuditLog, 10000), // 缓冲区大小
 | ||
| 		stopCh:          make(chan struct{}),
 | ||
| 		batchSize:       50,              // 批量大小
 | ||
| 		flushInterval:   3 * time.Second, // 刷新间隔
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // Start 启动缓冲器
 | ||
| func (ab *AuditBuffer) Start() {
 | ||
| 	ab.wg.Add(1)
 | ||
| 	go ab.processBuffer()
 | ||
| }
 | ||
| 
 | ||
| // Stop 停止缓冲器
 | ||
| func (ab *AuditBuffer) Stop() {
 | ||
| 	close(ab.stopCh)
 | ||
| 	ab.wg.Wait()
 | ||
| 	close(ab.buffer)
 | ||
| }
 | ||
| 
 | ||
| // Add 添加审计日志到缓冲区
 | ||
| func (ab *AuditBuffer) Add(auditLog *systemmodel.AuditLog) {
 | ||
| 	select {
 | ||
| 	case ab.buffer <- auditLog:
 | ||
| 		// 成功添加到缓冲区
 | ||
| 	default:
 | ||
| 		// 缓冲区满,记录警告但不阻塞
 | ||
| 		ab.log.Warn("审计日志缓冲区已满,丢弃日志")
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // processBuffer 处理缓冲区中的日志
 | ||
| func (ab *AuditBuffer) processBuffer() {
 | ||
| 	defer ab.wg.Done()
 | ||
| 
 | ||
| 	ticker := time.NewTicker(ab.flushInterval)
 | ||
| 	defer ticker.Stop()
 | ||
| 
 | ||
| 	batch := make([]*systemmodel.AuditLog, 0, ab.batchSize)
 | ||
| 
 | ||
| 	flushBatch := func() {
 | ||
| 		if len(batch) == 0 {
 | ||
| 			return
 | ||
| 		}
 | ||
| 
 | ||
| 		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | ||
| 		defer cancel()
 | ||
| 
 | ||
| 		// 批量插入
 | ||
| 		if err := ab.batchInsert(ctx, batch); err != nil {
 | ||
| 			ab.log.Error("批量插入审计日志失败", err, zap.Int("count", len(batch)))
 | ||
| 		} else {
 | ||
| 			ab.log.Debug("批量插入审计日志成功", zap.Int("count", len(batch)))
 | ||
| 		}
 | ||
| 
 | ||
| 		// 清空批次
 | ||
| 		batch = batch[:0]
 | ||
| 	}
 | ||
| 
 | ||
| 	for {
 | ||
| 		select {
 | ||
| 		case <-ab.stopCh:
 | ||
| 			// 停止信号,处理剩余的日志
 | ||
| 			for len(ab.buffer) > 0 {
 | ||
| 				select {
 | ||
| 				case auditLog := <-ab.buffer:
 | ||
| 					batch = append(batch, auditLog)
 | ||
| 					if len(batch) >= ab.batchSize {
 | ||
| 						flushBatch()
 | ||
| 					}
 | ||
| 				default:
 | ||
| 					break
 | ||
| 				}
 | ||
| 			}
 | ||
| 			flushBatch() // 处理最后一批
 | ||
| 			return
 | ||
| 
 | ||
| 		case <-ticker.C:
 | ||
| 			// 定时刷新
 | ||
| 			flushBatch()
 | ||
| 
 | ||
| 		case auditLog := <-ab.buffer:
 | ||
| 			// 收到新的审计日志
 | ||
| 			batch = append(batch, auditLog)
 | ||
| 			if len(batch) >= ab.batchSize {
 | ||
| 				flushBatch()
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // batchInsert 批量插入数据库
 | ||
| func (ab *AuditBuffer) batchInsert(ctx context.Context, auditLogs []*systemmodel.AuditLog) error {
 | ||
| 	maxRetries := 3
 | ||
| 	for i := 0; i < maxRetries; i++ {
 | ||
| 		// 假设你的服务有批量创建方法,如果没有,需要添加
 | ||
| 		if err := ab.auditLogService.BatchCreate(ctx, auditLogs); err != nil {
 | ||
| 			if i == maxRetries-1 {
 | ||
| 				return err
 | ||
| 			}
 | ||
| 			ab.log.Error("批量插入失败,准备重试", err, zap.Int("retry", i+1))
 | ||
| 			time.Sleep(time.Duration(i+1) * time.Second)
 | ||
| 			continue
 | ||
| 		}
 | ||
| 		return nil
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 全局缓冲器实例
 | ||
| var globalAuditBuffer *AuditBuffer
 | ||
| 
 | ||
| // InitAuditBuffer 初始化全局缓冲器
 | ||
| func InitAuditBuffer(auditLogService v1.AuditLogService, log *logger.Logger) {
 | ||
| 	globalAuditBuffer = NewAuditBuffer(auditLogService, log)
 | ||
| 	globalAuditBuffer.Start()
 | ||
| }
 | ||
| 
 | ||
| // StopAuditBuffer 停止全局缓冲器
 | ||
| func StopAuditBuffer() {
 | ||
| 	if globalAuditBuffer != nil {
 | ||
| 		globalAuditBuffer.Stop()
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // Audit 优化后的中间件
 | ||
| func Audit(sess session.Manager, log *logger.Logger) func(http.Handler) http.Handler {
 | ||
| 	return func(next http.Handler) http.Handler {
 | ||
| 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | ||
| 			start := time.Now()
 | ||
| 
 | ||
| 			// 提前获取用户信息
 | ||
| 			user, err := sess.GetUser(r.Context(), know.StoreName)
 | ||
| 			if err != nil {
 | ||
| 				log.Error("获取用户会话失败", err)
 | ||
| 				next.ServeHTTP(w, r)
 | ||
| 				return
 | ||
| 			}
 | ||
| 
 | ||
| 			// 处理请求
 | ||
| 			next.ServeHTTP(w, r)
 | ||
| 
 | ||
| 			// 异步添加到缓冲区
 | ||
| 			go func() {
 | ||
| 				if user.ID == 0 {
 | ||
| 					log.Error("用户信息为空", errors.New("user is empty"))
 | ||
| 					return
 | ||
| 				}
 | ||
| 
 | ||
| 				auditLog := systemmodel.NewAuditLog(r, user.Email, user.OS, user.Browser, start, time.Now())
 | ||
| 
 | ||
| 				// 添加到缓冲区,不会阻塞
 | ||
| 				if globalAuditBuffer != nil {
 | ||
| 					globalAuditBuffer.Add(auditLog)
 | ||
| 				}
 | ||
| 			}()
 | ||
| 		})
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // 如果你的AuditLogService没有BatchCreate方法,需要添加这个接口
 | ||
| // 在你的service接口中添加:
 | ||
| /*
 | ||
| type AuditLogService interface {
 | ||
| 	Create(ctx context.Context, auditLog *systemmodel.AuditLog) error
 | ||
| 	BatchCreate(ctx context.Context, auditLogs []*systemmodel.AuditLog) error
 | ||
| 	// ... 其他方法
 | ||
| }
 | ||
| */
 | ||
| 
 | ||
| // 以及对应的实现(PostgreSQL批量插入示例)
 | ||
| /*
 | ||
| func (s *auditLogService) BatchCreate(ctx context.Context, auditLogs []*systemmodel.AuditLog) error {
 | ||
| 	if len(auditLogs) == 0 {
 | ||
| 		return nil
 | ||
| 	}
 | ||
| 
 | ||
| 	// 构建批量插入SQL
 | ||
| 	query := `INSERT INTO audit_logs (user_id, email, ip, os, browser, method, url, start_time, end_time, duration) VALUES `
 | ||
| 	values := make([]interface{}, 0, len(auditLogs)*10)
 | ||
| 
 | ||
| 	for i, log := range auditLogs {
 | ||
| 		if i > 0 {
 | ||
| 			query += ", "
 | ||
| 		}
 | ||
| 		query += "($" + strconv.Itoa(i*10+1) + ", $" + strconv.Itoa(i*10+2) + ", $" + strconv.Itoa(i*10+3) + ", $" + strconv.Itoa(i*10+4) + ", $" + strconv.Itoa(i*10+5) + ", $" + strconv.Itoa(i*10+6) + ", $" + strconv.Itoa(i*10+7) + ", $" + strconv.Itoa(i*10+8) + ", $" + strconv.Itoa(i*10+9) + ", $" + strconv.Itoa(i*10+10) + ")"
 | ||
| 
 | ||
| 		values = append(values, log.UserID, log.Email, log.Ip, log.Os, log.Browser, log.Method, log.Url, log.StartTime, log.EndTime, log.Duration)
 | ||
| 	}
 | ||
| 
 | ||
| 	_, err := s.db.ExecContext(ctx, query, values...)
 | ||
| 	return err
 | ||
| }
 | ||
| */
 |