You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于EF Core、Swagger与C#的HTTP请求拦截及日志管理需求

实现HTTP请求拦截记录与定时清理日志

一、创建请求日志实体与EF Core配置

首先定义请求日志实体类,包含核心记录字段:

public class RequestLog
{
    public int Id { get; set; }
    public string Path { get; set; }
    public string Method { get; set; }
    public string QueryString { get; set; }
    public string RequestBody { get; set; }
    public DateTime CreatedAt { get; set; }
    public string ClientIp { get; set; }
}

在DbContext中配置日志表的映射:

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }

    public DbSet<RequestLog> RequestLogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 给CreatedAt字段加索引,提升清理时的查询效率
        modelBuilder.Entity<RequestLog>()
            .HasIndex(l => l.CreatedAt);
    }
}

执行EF迁移命令生成数据库表:

Add-Migration AddRequestLogsTable
Update-Database

二、编写请求拦截中间件(解决RequestDelegate存储失败问题)

之前用RequestDelegate无法存库的核心原因是中间件生命周期与DbContext不匹配(中间件是单例,DbContext是作用域服务),需通过IServiceScopeFactory创建作用域获取DbContext:

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<RequestLoggingMiddleware> _logger;

    public RequestLoggingMiddleware(RequestDelegate next, IServiceScopeFactory scopeFactory, ILogger<RequestLoggingMiddleware> logger)
    {
        _next = next;
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // 排除Swagger相关请求,避免日志冗余
        var path = context.Request.Path.ToString();
        if (path.StartsWith("/swagger") || path.StartsWith("/swagger-ui"))
        {
            await _next(context);
            return;
        }

        // 启用请求体缓冲,保证读取后后续中间件仍能使用
        context.Request.EnableBuffering();
        var requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync();
        context.Request.Body.Position = 0;

        // 创建作用域获取DbContext
        using var scope = _scopeFactory.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var log = new RequestLog
        {
            Path = context.Request.Path,
            Method = context.Request.Method,
            QueryString = context.Request.QueryString.ToString(),
            RequestBody = requestBody,
            CreatedAt = DateTime.UtcNow,
            ClientIp = context.Connection.RemoteIpAddress?.ToString()
        };

        try
        {
            dbContext.RequestLogs.Add(log);
            await dbContext.SaveChangesAsync();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "保存请求日志失败");
            // 记录失败不中断主请求流程
        }

        await _next(context);
    }
}

在Program.cs中注册中间件(放在Swagger中间件之后,避免记录Swagger请求):

var builder = WebApplication.CreateBuilder(args);

// 注册DbContext
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// 注册日志服务
builder.Services.AddLogging();

// 注册Swagger
builder.Services.AddSwaggerGen();

var app = builder.Build();

// 注册请求日志中间件
app.UseMiddleware<RequestLoggingMiddleware>();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

三、实现7天一次的日志清理定时任务

使用.NET自带的BackgroundService实现定时清理逻辑:

public class LogCleanupService : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<LogCleanupService> _logger;
    private readonly TimeSpan _cleanupInterval = TimeSpan.FromDays(7);

    public LogCleanupService(IServiceScopeFactory scopeFactory, ILogger<LogCleanupService> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("日志清理服务已启动");

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                using var scope = _scopeFactory.CreateScope();
                var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

                // 删除7天前的日志
                var cutoffDate = DateTime.UtcNow.AddDays(-7);
                var oldLogs = await dbContext.RequestLogs
                    .Where(l => l.CreatedAt < cutoffDate)
                    .ToListAsync(stoppingToken);

                dbContext.RequestLogs.RemoveRange(oldLogs);
                await dbContext.SaveChangesAsync(stoppingToken);

                _logger.LogInformation("已清理{Count}条过期请求日志", oldLogs.Count);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "清理过期日志失败");
            }

            // 等待7天后再次执行
            await Task.Delay(_cleanupInterval, stoppingToken);
        }

        _logger.LogInformation("日志清理服务已停止");
    }
}

在Program.cs中注册后台服务:

builder.Services.AddHostedService<LogCleanupService>();

四、关键注意事项

  • 请求体读取:必须调用context.Request.EnableBuffering(),否则流只能读取一次;读取后要重置流位置context.Request.Body.Position = 0,保证后续中间件正常获取请求体。
  • DbContext生命周期:中间件是单例,不能直接注入DbContext,必须通过IServiceScopeFactory创建作用域,避免DbContext被长时间持有引发并发问题。
  • 异常隔离:日志存储和清理的异常要单独捕获,不能影响主业务请求的执行。
  • Swagger过滤:根据实际需求决定是否排除Swagger请求,减少无效日志。

内容的提问来源于stack exchange,提问作者Jacob Modiba

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 08:55:23