.net core 接口请求时间限制

发布于:2025-07-11 ⋅ 阅读:(22) ⋅ 点赞:(0)

需求:单IP接口请求间隔不能小于120S

实现方案:①应用程序内存中记录各IP访问时间  ②使用中间件Redis记录各IP访问时间

.net core 版本:.net 8

方案一代码:

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Concurrent;

namespace MESNetCoreWebApi.Attributes
{
    public class RequestIntervalAttribute : ActionFilterAttribute
    {
        private static readonly ConcurrentDictionary<string, DateTime> LastRequestTimes = new();
        private readonly int _intervalSeconds;

        public RequestIntervalAttribute(int intervalSeconds)
        {
            _intervalSeconds = intervalSeconds;
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var clientKey = GetClientKey(context);

            if (LastRequestTimes.TryGetValue(clientKey, out var lastRequestTime))
            {
                var elapsed = DateTime.UtcNow - lastRequestTime;
                if (elapsed.TotalSeconds < _intervalSeconds)
                {
                    var waitTime = _intervalSeconds - (int)elapsed.TotalSeconds;
                    context.Result = new ObjectResult(new
                    {

                        Code = 999,
                        Msg = $"请求过于频繁,请等待 {waitTime} 秒后再试",
                        //retryAfter = waitTime
                    })
                    {
                        StatusCode = StatusCodes.Status429TooManyRequests
                    };
                    return;
                }
            }

            LastRequestTimes[clientKey] = DateTime.UtcNow;
            base.OnActionExecuting(context);
        }

        private string GetClientKey(ActionExecutingContext context)
        {
            // 使用IP+UserAgent作为客户端标识
            var ip = context.HttpContext.Connection.RemoteIpAddress?.ToString();
            var userAgent = context.HttpContext.Request.Headers["User-Agent"].ToString();
            return $"{ip}_{userAgent}";
        }
    }
}

方案二代码:

    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.AspNetCore.Mvc;
    using StackExchange.Redis;
    using System;

    namespace MESNetCoreWebApi.Attributes
    {
        public class RedisRequestIntervalAttribute : ActionFilterAttribute
        {
            private readonly int _intervalSeconds;
            private readonly string _rateLimitPrefix = "RateLimit:";

            public RedisRequestIntervalAttribute(int intervalSeconds)
            {
                _intervalSeconds = intervalSeconds;
            }

            public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
            {
                // 获取 Redis 实例,通过依赖注入
                var redis = context.HttpContext.RequestServices.GetRequiredService<IConnectionMultiplexer>();
                var _redis = redis.GetDatabase();  // 获取默认数据库(0号数据库)

                var clientKey = GetClientKey(context);
                var redisKey = _rateLimitPrefix + clientKey;

                // 获取上次请求时间
                var lastRequestTimeStr = await _redis.StringGetAsync(redisKey);

                if (!lastRequestTimeStr.IsNullOrEmpty &&
                    DateTime.TryParse(lastRequestTimeStr, out var lastRequestTime))
                {
                    var elapsed = DateTime.Now - lastRequestTime;

                    if (elapsed.TotalSeconds < _intervalSeconds)
                    {
                        var waitTime = _intervalSeconds - (int)elapsed.TotalSeconds;
                        context.Result = new ObjectResult(new
                        {
                            Code = 999,
                            Msg = $"请求过于频繁,请等待 {waitTime} 秒后再试",
                            RetryAfter = waitTime
                        })
                        {
                            StatusCode = StatusCodes.Status429TooManyRequests
                        };
                        return;
                    }
                }

                // 更新最后请求时间并设置过期时间
                await _redis.StringSetAsync(
                    redisKey,
                    DateTime.Now.ToString(),
                    TimeSpan.FromSeconds(_intervalSeconds * 2)); // 设置稍长的过期时间

                await next();
            }

            private string GetClientKey(ActionExecutingContext context)
            {
                // 使用IP+UserAgent+请求路径作为客户端标识
                var ip = context.HttpContext.Connection.RemoteIpAddress?.ToString();
                var userAgent = context.HttpContext.Request.Headers.UserAgent.ToString();
                var path = context.HttpContext.Request.Path;
                return $"{ip}_{userAgent}_{path}";
            }
        }

    }

如果使用应用程序内存记录,重启服务会丢失记录,建议使用Redis


网站公告

今日签到

点亮在社区的每一天
去签到