Files
backend/internal/repository/login_log_repo.go
lan d9aa4b46c3
All checks were successful
Build Backend / build (push) Successful in 4m55s
Build Backend / build-docker (push) Successful in 10m34s
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00

178 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package repository
import (
"context"
"time"
"with_you/internal/model"
"with_you/internal/query"
"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 query.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 query.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, query.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
}