package repository import ( "context" "time" "carrot_bbs/internal/dto" "carrot_bbs/internal/model" "gorm.io/gorm" ) // LoginLogRepository 登录日志仓储接口 type LoginLogRepository interface { CreateLoginLog(ctx context.Context, log *model.LoginLog) error BatchCreateLoginLogs(ctx context.Context, logs []*model.LoginLog) error GetLoginLogs(ctx context.Context, filters dto.LoginFilter, page, pageSize int) ([]*model.LoginLog, int64, error) GetLoginLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.LoginLog, int64, error) GetFailedLoginsByIP(ctx context.Context, ip string, timeWindow time.Duration) (int64, error) GetRecentSuccessLogins(ctx context.Context, userID string, limit int) ([]*model.LoginLog, error) DeleteOldLogs(ctx context.Context, beforeDate string) error GetStatistics(ctx context.Context, startTime, endTime string) (*LoginLogStatistics, error) } // loginLogRepository 登录日志仓储实现 type loginLogRepository struct { db *gorm.DB } // NewLoginLogRepository 创建登录日志仓储 func NewLoginLogRepository(db *gorm.DB) LoginLogRepository { return &loginLogRepository{db: db} } // CreateLoginLog 创建单条登录日志 func (r *loginLogRepository) CreateLoginLog(ctx context.Context, log *model.LoginLog) error { return r.db.WithContext(ctx).Create(log).Error } // BatchCreateLoginLogs 批量创建登录日志 func (r *loginLogRepository) BatchCreateLoginLogs(ctx context.Context, logs []*model.LoginLog) error { if len(logs) == 0 { return nil } return r.db.WithContext(ctx).Create(logs).Error } // GetLoginLogs 获取登录日志列表(分页) func (r *loginLogRepository) GetLoginLogs(ctx context.Context, filters dto.LoginFilter, page, pageSize int) ([]*model.LoginLog, int64, error) { query := r.db.WithContext(ctx).Model(&model.LoginLog{}) if filters.UserID != "" { query = query.Where("user_id = ?", filters.UserID) } if filters.Event != "" { query = query.Where("event = ?", filters.Event) } if filters.Result != "" { query = query.Where("result = ?", filters.Result) } if filters.FailReason != "" { query = query.Where("fail_reason = ?", filters.FailReason) } if filters.LoginType != "" { query = query.Where("login_type = ?", filters.LoginType) } if !filters.StartTime.IsZero() { query = query.Where("created_at >= ?", filters.StartTime) } if !filters.EndTime.IsZero() { query = query.Where("created_at <= ?", filters.EndTime) } if filters.IP != "" { query = query.Where("ip = ?", filters.IP) } var total int64 if err := query.Count(&total).Error; err != nil { return nil, 0, err } var logs []*model.LoginLog offset := (page - 1) * pageSize if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&logs).Error; err != nil { return nil, 0, err } return logs, total, nil } // GetLoginLogsByUser 获取指定用户的登录日志 func (r *loginLogRepository) GetLoginLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.LoginLog, int64, error) { return r.GetLoginLogs(ctx, dto.LoginFilter{UserID: userID}, page, pageSize) } // GetFailedLoginsByIP 获取指定IP的失败登录记录(用于风控) func (r *loginLogRepository) GetFailedLoginsByIP(ctx context.Context, ip string, timeWindow time.Duration) (int64, error) { startTime := time.Now().Add(-timeWindow) var count int64 err := r.db.WithContext(ctx). Model(&model.LoginLog{}). Where("ip = ? AND result = ? AND created_at >= ?", ip, string(model.LoginResultFail), startTime). Count(&count).Error return count, err } // GetRecentSuccessLogins 获取最近成功的登录记录 func (r *loginLogRepository) GetRecentSuccessLogins(ctx context.Context, userID string, limit int) ([]*model.LoginLog, error) { var logs []*model.LoginLog err := r.db.WithContext(ctx). Where("user_id = ? AND result = ? AND event = ?", userID, string(model.LoginResultSuccess), string(model.LoginEventLogin)). Order("created_at DESC"). Limit(limit). Find(&logs).Error return logs, err } // DeleteOldLogs 删除旧的登录日志(用于定时清理) func (r *loginLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error { return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.LoginLog{}).Error } // GetStatistics 获取登录日志统计(使用单次 GROUP BY 查询,避免多次 COUNT) func (r *loginLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*LoginLogStatistics, error) { stats := &LoginLogStatistics{} query := r.db.WithContext(ctx).Model(&model.LoginLog{}) if startTime != "" { query = query.Where("created_at >= ?", startTime) } if endTime != "" { query = query.Where("created_at <= ?", endTime) } // 使用单次 GROUP BY 查询获取 result 计数 type resultCount struct { Result string Count int64 } var results []resultCount err := query.Select("result, count(*) as count").Group("result").Scan(&results).Error if err != nil { return nil, err } // 汇总结果 for _, r := range results { stats.TotalCount += r.Count if r.Result == string(model.LoginResultSuccess) { stats.SuccessCount = r.Count } else if r.Result == string(model.LoginResultFail) { stats.FailCount = r.Count } } // 单独查询登录次数 if startTime != "" || endTime != "" { loginQuery := r.db.WithContext(ctx).Model(&model.LoginLog{}) if startTime != "" { loginQuery = loginQuery.Where("created_at >= ?", startTime) } if endTime != "" { loginQuery = loginQuery.Where("created_at <= ?", endTime) } loginQuery.Where("event = ?", string(model.LoginEventLogin)).Count(&stats.LoginCount) } return stats, nil } // LoginLogStatistics 登录日志统计 type LoginLogStatistics struct { TotalCount int64 SuccessCount int64 FailCount int64 LoginCount int64 }