Files
BackupX/server/internal/repository/audit_log_repository.go
T
Wu Qing f807ce10e6 feat(audit): 审计日志保留期清理(后端自动清理 + 审计页配置) (#83)
审计日志新增可配置保留期:AuditLogRepository.DeleteBefore + AuditService 保留期监控(每 6h 读取 audit_retention_days,0/缺省=永久保留);审计页新增管理员保留天数配置控件。后端 go test、前端 tsc+vite 通过。
2026-05-27 08:37:34 +08:00

108 lines
3.1 KiB
Go

package repository
import (
"context"
"time"
"backupx/server/internal/model"
"gorm.io/gorm"
)
type AuditLogListOptions struct {
Category string
Action string
Username string
TargetID string
Keyword string // 模糊匹配 detail / target_name
DateFrom *time.Time
DateTo *time.Time
Limit int
Offset int
}
type AuditLogListResult struct {
Items []model.AuditLog `json:"items"`
Total int64 `json:"total"`
}
type AuditLogRepository interface {
Create(ctx context.Context, log *model.AuditLog) error
List(ctx context.Context, opts AuditLogListOptions) (*AuditLogListResult, error)
ListAll(ctx context.Context, opts AuditLogListOptions) ([]model.AuditLog, error)
// DeleteBefore 删除 created_at 早于 cutoff 的审计日志,返回删除行数。用于保留期清理。
DeleteBefore(ctx context.Context, cutoff time.Time) (int64, error)
}
type gormAuditLogRepository struct {
db *gorm.DB
}
func NewAuditLogRepository(db *gorm.DB) AuditLogRepository {
return &gormAuditLogRepository{db: db}
}
func (r *gormAuditLogRepository) Create(_ context.Context, log *model.AuditLog) error {
return r.db.Create(log).Error
}
func (r *gormAuditLogRepository) DeleteBefore(ctx context.Context, cutoff time.Time) (int64, error) {
result := r.db.WithContext(ctx).Where("created_at < ?", cutoff).Delete(&model.AuditLog{})
return result.RowsAffected, result.Error
}
func (r *gormAuditLogRepository) List(_ context.Context, opts AuditLogListOptions) (*AuditLogListResult, error) {
query := r.buildQuery(opts)
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, err
}
limit := opts.Limit
if limit <= 0 {
limit = 50
}
var items []model.AuditLog
if err := query.Order("created_at DESC").Offset(opts.Offset).Limit(limit).Find(&items).Error; err != nil {
return nil, err
}
return &AuditLogListResult{Items: items, Total: total}, nil
}
// ListAll 导出专用:不分页返回所有匹配记录(上限 10k 防爆)。
func (r *gormAuditLogRepository) ListAll(_ context.Context, opts AuditLogListOptions) ([]model.AuditLog, error) {
query := r.buildQuery(opts)
const maxExportRows = 10000
var items []model.AuditLog
if err := query.Order("created_at DESC").Limit(maxExportRows).Find(&items).Error; err != nil {
return nil, err
}
return items, nil
}
// buildQuery 统一构造带筛选条件的查询。
func (r *gormAuditLogRepository) buildQuery(opts AuditLogListOptions) *gorm.DB {
query := r.db.Model(&model.AuditLog{})
if opts.Category != "" {
query = query.Where("category = ?", opts.Category)
}
if opts.Action != "" {
query = query.Where("action = ?", opts.Action)
}
if opts.Username != "" {
query = query.Where("username = ?", opts.Username)
}
if opts.TargetID != "" {
query = query.Where("target_id = ?", opts.TargetID)
}
if opts.Keyword != "" {
pattern := "%" + opts.Keyword + "%"
query = query.Where("detail LIKE ? OR target_name LIKE ?", pattern, pattern)
}
if opts.DateFrom != nil {
query = query.Where("created_at >= ?", opts.DateFrom.UTC())
}
if opts.DateTo != nil {
query = query.Where("created_at <= ?", opts.DateTo.UTC())
}
return query
}