Files
backend/internal/repository/operation_log_repo.go
lafay 98b8abd26a
All checks were successful
Build Backend / build (push) Successful in 12m54s
Build Backend / build-docker (push) Successful in 1m41s
feat(config): add sensitive word filtering system with database support
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)
2026-04-07 00:07:40 +08:00

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
}