ASP.NET Core 请求限速的ActionFilter

文章目录


前言

以下是一个基于内存缓存实现的自定义限流Action Filter。

一、实现步骤

1)创建自定义Action Filter

示例1:

  1. MyRateLimitAttribute.cs

    bash 复制代码
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.Extensions.Caching.Memory;
    
     public class MyRateLimitAttribute : TypeFilterAttribute
     {
         public MyRateLimitAttribute() 
             :base(typeof(MyRateLimitFilter))
         {
         }
         public class MyRateLimitFilter : IAsyncActionFilter
         {
             private readonly IMemoryCache _memoryCache;
    
             public MyRateLimitFilter(IMemoryCache memoryCache)
             {
                 _memoryCache = memoryCache;
             }
    
             public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
             {
                 string ip = context.HttpContext.Connection.RemoteIpAddress.ToString();
                 if (string.IsNullOrEmpty(ip))
                 {
                     context.Result = new BadRequestObjectResult("Invalid Client IP");
                     return;
                 }
                 string cacheKey = $"MyRateLimit_{ip}";
                 _memoryCache.TryGetValue<long?>(cacheKey, out long? lastVisit);
                 if (lastVisit == null || Environment.TickCount64 - lastVisit > 1000)
                 {
                     _memoryCache.Set(cacheKey, Environment.TickCount64, TimeSpan.FromSeconds(10));
                     await next();
                 }
                 else
                 {
                     context.Result = new ObjectResult("访问太频繁") { StatusCode=429};                    
                 }
             }
         }
     }

示例2:

  1. RateLimitAttribute.cs

    bash 复制代码
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.Extensions.Caching.Memory;
    
    public class RateLimitAttribute : TypeFilterAttribute
    {
        public RateLimitAttribute(int maxRequests,int secondsWindow) 
            : base(typeof(RateLimitFilter))
        {
            Arguments = new object[] { maxRequests,secondsWindow};
        }
    
        public class RateLimitFilter : IAsyncActionFilter
        {
            private readonly IMemoryCache _memoryCache;
            private readonly int _maxRequests;
            private readonly int _secondsWindow;
    
            public RateLimitFilter(IMemoryCache memoryCache, int maxRequests, int secondsWindow)
            {
                _memoryCache = memoryCache;
                _maxRequests = maxRequests;
                _secondsWindow = secondsWindow;
            }
    
            public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
            {
                var ip=context.HttpContext.Connection.RemoteIpAddress?.ToString();
                if (string.IsNullOrEmpty(ip))
                {
                    context.Result = new BadRequestObjectResult("Invalid client IP");
                    return;
                }
                var cacheKey = $"RateLimit_{ip}";
                var windowStart = DateTime.UtcNow.AddSeconds(-DateTime.UtcNow.Second % _secondsWindow);
                if (!_memoryCache.TryGetValue(cacheKey, out RateLimitCounter counter) ||
                    windowStart > counter.WindowStart)
                {
                    counter = new RateLimitCounter
                    {
                        Count = 1,
                        WindowStart = windowStart,
                    };
                }
                else
                {
                    counter.Count++;
                }
    
                if (counter.Count > _maxRequests)
                {
                    context.Result = new ObjectResult("Too many requests")
                    {
                        StatusCode = 429
                    };
                    return;
                }
                _memoryCache.Set(cacheKey, counter, counter.WindowStart.AddSeconds(_secondsWindow));
                await next();
            }
            private class RateLimitCounter
            {
                public int Count { get; set; }
                public DateTime WindowStart { get; set; }
            }
        }
    }

2)注册服务

  1. 内存缓存服务

    bash 复制代码
    builder.Services.AddMemoryCache();

3)使用

  1. 示例:

    bash 复制代码
    [HttpGet]
    //[RateLimit(maxRequests: 5, secondsWindow: 60)] // 每分钟最多5次请求
    [MyRateLimit]
    public async Task<ActionResult<Book>> GetAllBookAsync()
    {
        var res=await _bookRepository.GetAllAsync();
        return Ok(res);
    }

二、实现说明

  1. 使用IP地址识别客户端(需考虑代理场景)
  2. 基于固定时间窗口算法(每分钟/小时重置计数器)(示例2)
  3. 使用IMemoryCache存储计数器
  4. 返回429状态码(Too Many Requests)时阻止请求

总结

  1. 内存缓存方案仅适用于单实例部署
  2. 高并发场景建议使用Interlocked类处理计数器原子操作
  3. 生产环境推荐使用分布式缓存(如Redis
  4. 建议使用成熟的限流库(如AspNetCoreRateLimit
相关推荐
hui函数7 小时前
Flask电影投票系统全解析
后端·python·flask
小厂永远得不到的男人9 小时前
基于 Spring Validation 实现全局参数校验异常处理
java·后端·架构
毅航13 小时前
从原理到实践,讲透 MyBatis 内部池化思想的核心逻辑
后端·面试·mybatis
展信佳_daydayup13 小时前
02 基础篇-OpenHarmony 的编译工具
后端·面试·编译器
Always_Passion13 小时前
二、开发一个简单的MCP Server
后端
用户7215220787713 小时前
基于LD_PRELOAD的命令行参数安全混淆技术
后端
笃行35013 小时前
开源大模型实战:GPT-OSS本地部署与全面测评
后端
知其然亦知其所以然13 小时前
SpringAI:Mistral AI 聊天?一文带你跑通!
后端·spring·openai
庚云13 小时前
🔒 前后端 AES 加密解密实战(Vue3 + Node.js)
前端·后端
超级小忍14 小时前
使用 GraalVM Native Image 将 Spring Boot 应用编译为跨平台原生镜像:完整指南
java·spring boot·后端