mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-08-28 11:37:04 +08:00
审计日志新增可配置保留期:AuditLogRepository.DeleteBefore + AuditService 保留期监控(每 6h 读取 audit_retention_days,0/缺省=永久保留);审计页新增管理员保留天数配置控件。后端 go test、前端 tsc+vite 通过。
108 lines
3.1 KiB
Go
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
|
|
}
|