Implement sensitive word filtering feature with configurable database and Redis support. Add security enhancements including WebSocket origin validation, improved password strength requirements, timing attack prevention, and production JWT secret validation. Add batch operation validation limits and optimize user blocking queries with subqueries. BREAKING CHANGE: Password validation now requires minimum 8 characters with uppercase, lowercase, and digit (was 6-50 characters without complexity requirements)
147 lines
4.8 KiB
Go
147 lines
4.8 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
|
|
"carrot_bbs/internal/dto"
|
|
"carrot_bbs/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// OperationLogRepository 操作日志仓储接口
|
|
type OperationLogRepository interface {
|
|
CreateOperationLog(ctx context.Context, log *model.OperationLog) error
|
|
BatchCreateOperationLogs(ctx context.Context, logs []*model.OperationLog) error
|
|
GetOperationLogs(ctx context.Context, filters dto.LogFilter, page, pageSize int) ([]*model.OperationLog, int64, error)
|
|
GetOperationLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.OperationLog, int64, error)
|
|
GetOperationLogsByTimeRange(ctx context.Context, startTime, endTime string, page, pageSize int) ([]*model.OperationLog, int64, error)
|
|
DeleteOldLogs(ctx context.Context, beforeDate string) error
|
|
GetStatistics(ctx context.Context, startTime, endTime string) (*OperationLogStatistics, error)
|
|
}
|
|
|
|
// operationLogRepository 操作日志仓储实现
|
|
type operationLogRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewOperationLogRepository 创建操作日志仓储
|
|
func NewOperationLogRepository(db *gorm.DB) OperationLogRepository {
|
|
return &operationLogRepository{db: db}
|
|
}
|
|
|
|
// CreateOperationLog 创建单条操作日志
|
|
func (r *operationLogRepository) CreateOperationLog(ctx context.Context, log *model.OperationLog) error {
|
|
return r.db.WithContext(ctx).Create(log).Error
|
|
}
|
|
|
|
// BatchCreateOperationLogs 批量创建操作日志
|
|
func (r *operationLogRepository) BatchCreateOperationLogs(ctx context.Context, logs []*model.OperationLog) error {
|
|
if len(logs) == 0 {
|
|
return nil
|
|
}
|
|
return r.db.WithContext(ctx).Create(logs).Error
|
|
}
|
|
|
|
// GetOperationLogs 获取操作日志列表(分页)
|
|
func (r *operationLogRepository) GetOperationLogs(ctx context.Context, filters dto.LogFilter, page, pageSize int) ([]*model.OperationLog, int64, error) {
|
|
query := r.db.WithContext(ctx).Model(&model.OperationLog{})
|
|
|
|
if filters.UserID != "" {
|
|
query = query.Where("user_id = ?", filters.UserID)
|
|
}
|
|
if filters.Operation != "" {
|
|
query = query.Where("operation = ?", filters.Operation)
|
|
}
|
|
if filters.TargetType != "" {
|
|
query = query.Where("target_type = ?", filters.TargetType)
|
|
}
|
|
if filters.TargetID != "" {
|
|
query = query.Where("target_id = ?", filters.TargetID)
|
|
}
|
|
if filters.Status != "" {
|
|
query = query.Where("status = ?", filters.Status)
|
|
}
|
|
if filters.StartTime != "" {
|
|
query = query.Where("occurred_at >= ?", filters.StartTime)
|
|
}
|
|
if filters.EndTime != "" {
|
|
query = query.Where("occurred_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.OperationLog
|
|
offset := (page - 1) * pageSize
|
|
if err := query.Order("occurred_at DESC").Offset(offset).Limit(pageSize).Find(&logs).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return logs, total, nil
|
|
}
|
|
|
|
// GetOperationLogsByUser 获取指定用户的操作日志
|
|
func (r *operationLogRepository) GetOperationLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.OperationLog, int64, error) {
|
|
return r.GetOperationLogs(ctx, dto.LogFilter{UserID: userID}, page, pageSize)
|
|
}
|
|
|
|
// GetOperationLogsByTimeRange 按时间范围获取操作日志
|
|
func (r *operationLogRepository) GetOperationLogsByTimeRange(ctx context.Context, startTime, endTime string, page, pageSize int) ([]*model.OperationLog, int64, error) {
|
|
return r.GetOperationLogs(ctx, dto.LogFilter{StartTime: startTime, EndTime: endTime}, page, pageSize)
|
|
}
|
|
|
|
// DeleteOldLogs 删除旧的操作日志(用于定时清理)
|
|
func (r *operationLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error {
|
|
return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.OperationLog{}).Error
|
|
}
|
|
|
|
// GetStatistics 获取操作日志统计(使用单次 GROUP BY 查询,避免多次 COUNT)
|
|
func (r *operationLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*OperationLogStatistics, error) {
|
|
stats := &OperationLogStatistics{}
|
|
|
|
query := r.db.WithContext(ctx).Model(&model.OperationLog{})
|
|
if startTime != "" {
|
|
query = query.Where("created_at >= ?", startTime)
|
|
}
|
|
if endTime != "" {
|
|
query = query.Where("created_at <= ?", endTime)
|
|
}
|
|
|
|
// 使用单次 GROUP BY 查询获取各状态计数
|
|
type statusCount struct {
|
|
Status string
|
|
Count int64
|
|
}
|
|
var results []statusCount
|
|
err := query.Select("status, count(*) as count").Group("status").Scan(&results).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 汇总结果
|
|
for _, r := range results {
|
|
stats.TotalCount += r.Count
|
|
switch r.Status {
|
|
case "success":
|
|
stats.SuccessCount = r.Count
|
|
case "fail":
|
|
stats.FailCount = r.Count
|
|
}
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// OperationLogStatistics 操作日志统计
|
|
type OperationLogStatistics struct {
|
|
TotalCount int64
|
|
SuccessCount int64
|
|
FailCount int64
|
|
}
|