一个代号 发表于 2023-2-14 20:08:47

白嫖一个WebAPI限流解决方案

什么是API限流:
API 限流是限制用户在一定时间内 API 请求数量的过程。应用程序编程接口 (API) 充当用户和软件应用程序之间的网关。例如,当用户单击社交媒体上的发布按钮时,点击该按钮会触发 API 调用。此 API 与社交媒体应用程序的网络服务器进行交互,并执行发布操作。此用户可以是人,也可以是其他软件应用程序。
为什么要限流:
API 是组织最大的资产之一。API 可帮助网站或移动应用程序的用户完成任务。随着用户数量的增加,网站或移动应用程序开始出现性能下降的迹象。因此,拥有更好连接或更快界面的用户可能会获得比其他用户更好的体验。API 限流是一种巧妙的解决方案,可帮助组织确保其 API 的合理使用。
API 限流还有助于抵御拒绝服务 (DoS) 攻击,在 DoS 攻击中,恶意用户发送大量请求以使网站或移动应用程序崩溃。随着在线用户数量的增加,企业需要实施 API 限流机制,以确保公平使用、数据安全并防止恶意攻击。
API限流的原理:
虽然 API 限流有多种算法,但以下是所有 API 限流算法的基本步骤:
1.客户端/用户调用与网络服务或应用程序交互的 API。
2.API 限流逻辑会检查当前请求是否超过允许的 API 调用次数。
3.如果请求在限制范围内,API 将照常执行并完成用户的任务。
4.如果请求超出限制,API 会向用户返回错误响应。
5.用户必须等待预先约定的时间段,或者付费才能进行更多的 API 调用。
这里有篇文章介绍很全面,可以看一看《API 限流技术探索与实践》
这个限流方案也是在百度收集整理而来,我这里采取的是滑动算法:
我们需要准备几个类:
1.ApiAuthorize类
ApiAuthorize继承于IAuthorizationFilter(授权过滤器),和IAuthorizationFilter相同的还有其他三种过滤器,合起来称为四大过滤器,
另外三个分别是IResourceFilter资源过滤器(缓存接口的数据),IActionFilter动作过滤器(记录操作日志),IExceptionFilter(错误过滤器)
IAuthorizationFilter
public class CtmAuthorizationFilterAttribute : Attribute, IAuthorizationFilter
{
   public void OnAuthorization(AuthorizationFilterContext context)
   {
         // context.HttpContext.User.Claims
         context.HttpContext.Items["User"] = "HuangMing";
         System.Console.WriteLine("OnAuthorization");
   }
}View CodeIResourceFilter
//Program.cs中注册缓存:
builder.Services.AddSingleton<IMemoryCache,MemoryCache>();
builder.Services.AddSingleton<IDistributedCache, MemoryDistributedCache>();
var app = builder.Build();


public class CtmResourceFilterAttribute : Attribute, IResourceFilter
{
    private readonly IMemoryCache _cache;

    public CtmResourceFilterAttribute(IMemoryCache cache)
    {
      this._cache = cache;
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
      var path = context.HttpContext.Request.Path.ToString();
      if (context.Result != null)
      {
            var value =(context.Result as ObjectResult).Value.ToString();
            _cache.Set(path, value,TimeSpan.FromHours(1));
      }
    }

    public void OnResourceExecuting(ResourceExecutingContext context)
    {
      var path = context.HttpContext.Request.Path.ToString();
      var hasValue = _cache.TryGetValue(path, out object value);
      if (hasValue)
      {
            context.Result = new ContentResult
            {
                Content = value.ToString()
            };
      }
    }
}View CodeIActionFilter
public class CtmActionFilterAttribute : Attribute, IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context)
    {
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
      //从serviceProvider中获取Logger服务
      var logger =context.HttpContext.RequestServices.GetService<ILogger<CtmActionFilterAttribute>>();
      //获取路由地址
      var path = context.HttpContext.Request.Path;
      //从RouteData字典中获取控制器名称
      var controller = context.RouteData.Values["controller"];
      //从RouteData字典中获取动作名称
      var action = context.RouteData.Values["action"];
      //从ActionArguments中获取接口参数
      var arguments = string.Join(",", context.ActionArguments);
      logger.LogInformation($"访问的路由:{path},控制器是{controller},行为是{action},参数是{arguments}");
    }
}

//当过滤器中需要使用依赖注入时,在使用属性标注时,需要使用如下方式:
1.属性标注


2.从容器中获取服务
var logger =context.HttpContext.RequestServices.GetService<ILogger<CtmActionFilterAttribute>>();View CodeIActionFilter
public class CtmExceptionFilterAttribute : Attribute, IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
      context.Result = new ContentResult{
            Content =context.Exception.Message
      };
    }
}View Code现在编写自己的项目代码
ApiAuthorize
public class ApiAuthorize : IAuthorizationFilter
    {
      public async void OnAuthorization(AuthorizationFilterContext context)
         {
            if (context.Filters.Contains(new MyNoAuthentication()))
            {
                return;
            }

         

            #region 用户请求限流
            {
                string ip = context.HttpContext.Connection.RemoteIpAddress.ToString();
                var cotrollaction = context.ActionDescriptor;
                string action = cotrollaction.RouteValues["action"].ToString();
                string controller = cotrollaction.RouteValues["controller"].ToString();
                if (string.IsNullOrWhiteSpace(ip) || string.IsNullOrWhiteSpace(controller) || string.IsNullOrWhiteSpace(action))
                {
                  context.Result = new JsonResult("系统正忙,请稍微再试!");
                  return;
                }
                ip = ip + ":" + controller + ":" + action;
                IPCacheInfoModel ipModel = IPCacheHelper.GetIPLimitInfo(ip);
                if (!ipModel.IsVisit)
                {
                  context.Result = new JsonResult("系统正忙,请稍微再试!");
                  return;
                }
                string ACting = controller + ":" + action;
                IPCacheInfoModel ipModel2 = IPCacheHelper.GetIPLimitInfo(ACting);


            }
            #endregion


            
            #endregion
      }

    }View Code然后编写 MyAuthentication类
MyAuthentication
/// <summary>
/// 构造引用
/// </summary>
    public class MyAuthentication : Attribute, IFilterMetadata
    {
    }
    public class MyNoAuthentication : Attribute, IFilterMetadata
    {
    }View Code以上两个可以做限流也能做鉴权,数据签名认证等
如果需要限流,我们还需要三个类:
IPActionFilterAttribute 信息返回类
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace EvaluationSystem.XLAction
{
    /// <summary>
    /// 限制单个IP短时间内访问次数
    /// </summary>
   
    public class IPActionFilterAttribute : ActionFilterAttribute
    {
      /// <summary>
      /// 限制单个IP短时间内访问次数
      /// </summary>
      /// <param name="actionContext"></param>
      public override void OnActionExecuting(HttpActionContext actionContext)
      {
            string ip = actionContext.Request.ToString();
            IPCacheInfoModel ipModel = IPCacheHelper.GetIPLimitInfo(ip);
            if (!ipModel.IsVisit)
            {
                // Logger.Warn(string.Format("IP【{0}】被限制了【{1}】次数", ipModel.IP, ipModel.Limit));
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, "系统正忙,请稍微再试。");
                return;
            }
            base.OnActionExecuting(actionContext);
      }

    }
}View CodeIPCacheHelper 请求记录类
using EvaluationSystem.HelpTool;
using EvaluationSystem.HelpTool.GetSYSValue;
using System;
using System.Collections.Generic;

namespace EvaluationSystem.XLAction
{
    /// <summary>
    /// 限制单个IP访问次数
    /// </summary>
    public class IPCacheHelper
    {
      /// <summary>         
      /// IP缓存集合         
      /// </summary>      
      private static List<IPCacheInfoModel> dataList = new List<IPCacheInfoModel>();

      private static object lockObj = new object();
      //SQLHelp ht = new SQLHelp();
      public static stringmaxTimes1 = GetConfig.GetConfiguration("XLAction:maxTimes");
      public static string partSecond1 = GetConfig.GetConfiguration("XLAction:partSecond");

      /// <summary>      
      /// 一段时间内,最大请求次数,必须大于等于1
      ///</summary>
      private static int maxTimes = Convert.ToInt32(string.IsNullOrWhiteSpace(maxTimes1)? "0":maxTimes1);

      /// <summary>
      /// 一段时间长度(单位秒),必须大于等于1   
      /// </summary>
      private static int partSecond = Convert.ToInt32(string.IsNullOrWhiteSpace(partSecond1) ? "0" : partSecond1);

      /// <summary>
      /// 请求被拒绝是否加入请求次数   
      /// </summary>
      private static bool isFailAddIn = false;

      static IPCacheHelper()
      {

      }
      /// <summary>      
      /// 设置时间,默认maxTimes=3, partSecond=30         
      /// </summary>      
      /// <param name="_maxTimes">最大请求次数</param>      
      /// <param name="_partSecond">请求单位时间</param>         
      public static void SetTime(int _maxTimes, int _partSecond)
      {
            maxTimes = _maxTimes;
            partSecond = _partSecond;
      }

      /// <summary>   
      /// 检测一段时间内,IP的请求次数是否可以继续请求和使用
      /// </summary>      
      /// <param name="ip">ip</param>   
      /// <returns></returns>      
      public static bool CheckIsAble(string ip)
      {
            lock (lockObj)
            {
                var item = dataList.Find(p => p.IP == ip);
                if (item == null)
                {
                  item = new IPCacheInfoModel();
                  item.IP = ip;
                  item.ReqTime.Add(DateTime.Now);
                  dataList.Add(item);
                  return true;
                }
                else
                {
                  if (item.ReqTime.Count > maxTimes)
                  {
                        item.ReqTime.RemoveAt(0);
                  }
                  var nowTime = DateTime.Now;
                  if (isFailAddIn)
                  {
                        #region 请求被拒绝也需要加入当次请求
                        item.ReqTime.Add(nowTime);
                        if (item.ReqTime.Count >= maxTimes)
                        {
                            if (item.ReqTime.AddSeconds(partSecond) > nowTime)
                            {
                              return false;
                            }
                            else
                            {
                              return true;
                            }
                        }
                        else
                        {
                            return true;
                        }
                        #endregion
                  }
                  else
                  {
                        #region 请求被拒绝就不需要加入当次请求了
                        if (item.ReqTime.Count >= maxTimes)
                        {
                            if (item.ReqTime.AddSeconds(partSecond) > nowTime)
                            {
                              return false;
                            }
                            else
                            {
                              item.ReqTime.Add(nowTime);
                              return true;
                            }
                        }
                        else
                        {
                            item.ReqTime.Add(nowTime);
                            return true;
                        }
                        #endregion
                  }
                }
            }
      }


      /// <summary>   
      /// 检测一段时间内,IP的请求次数是否可以继续请求和使用
      /// </summary>      
      /// <param name="ip">ip</param>   
      /// <returns></returns>      
      public static IPCacheInfoModel GetIPLimitInfo(string ip)
      {
            lock (lockObj)
            {
                var item = dataList.Find(p => p.IP == ip);
                if (item == null) //IP开始访问
                {
                  item = new IPCacheInfoModel();
                  item.IP = ip;
                  item.ReqTime.Add(DateTime.Now);
                  dataList.Add(item);
                  item.IsVisit = true; //可以继续访问

                  return item;
                }
                else
                {
                  if (item.ReqTime.Count > maxTimes)
                  {
                        item.ReqTime.RemoveAt(0);
                  }
                  var nowTime = DateTime.Now;
                  if (isFailAddIn)
                  {
                        #region 请求被拒绝也需要加入当次请求
                        item.ReqTime.Add(nowTime);

                        if (item.ReqTime.Count >= maxTimes)
                        {
                            if (item.ReqTime.AddSeconds(partSecond) > nowTime)
                            {
                              item.Limit++; //限制次数+1
                              item.IsVisit = false;//不能继续访问
                              return item;
                            }
                            else
                            {
                              item.IsVisit = true; //可以继续访问
                              return item; //单个IP30秒内 没有多次访问
                            }
                        }
                        else
                        {
                            item.IsVisit = true; //可以继续访问
                            return item; //单个IP访问次数没有达到max次数
                        }
                        #endregion
                  }
                  else
                  {
                        #region 请求被拒绝就不需要加入当次请求了
                        if (item.ReqTime.Count >= maxTimes)
                        {
                            if (item.ReqTime.AddSeconds(partSecond) > nowTime)
                            {
                              item.Limit++; //限制次数+1
                              item.IsVisit = false;//不能继续访问

                              return item;
                            }
                            else
                            {
                              item.ReqTime.Add(nowTime);

                              item.IsVisit = true; //可以继续访问
                              return item;
                            }
                        }
                        else
                        {
                            item.ReqTime.Add(nowTime);
                            item.IsVisit = true; //可以继续访问

                            return item;
                        }
                        #endregion
                  }
                }
            }
      }
    }
}View CodeIPCacheInfoModel 实体类
using System;
using System.Collections.Generic;

namespace EvaluationSystem.XLAction
{
    public class IPCacheInfoModel
    {
      /// <summary>
      /// IP
      /// </summary>
      public string IP { get; set; }

      /// <summary>
      /// 限制次数
      /// </summary>
      public int Limit { get; set; }

      /// <summary>
      /// 是否可以访问
      /// </summary>
      public bool IsVisit { get; set; }

      /// <summary>
      /// 访问时间
      /// </summary>
      private List<DateTime> reqTime = new List<DateTime>();

      /// <summary>
      /// 访问时间
      /// </summary>
      public List<DateTime> ReqTime
      {
            get { return this.reqTime; }
            set { this.reqTime = value; }
      }
    }
}View Code时间按秒算
        private static int maxTimes ;
请求次数
        private static int partSecond ;
为了方便控制,不去修改我们的API程序,可以将这两个信息配置进appsettings.json文件里面
  "XLAction": {//请求限流 秒钟一次
    "maxTimes": "1",
    "partSecond": "1"
  }
为了获取appsettings.json来买你的信息,我们需要一个方法拿到json里面的信息
GetConfiguration
https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif    public class GetConfig
    {
      public static string GetConfiguration(string configKey)
      {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json");
            var config = builder.Build();
            if (configKey.Contains(":"))
            {
                return config.GetSection(configKey).Value;//获取分级参数值
            }
            else
            {
                return config;//获取直级参数值
            }
            //youdianwenti w xiangxiang
      }
    }View Code以上工作准备完全后,在我们的Startup里面修改加入以下代码
如果有ConfigureServices类,添加如下
            //注册guolv
            services.AddControllers(o =>
            {
                o.Filters.Add();
                o.Filters.Add();
                //o.Filters.Add(typeof(BasicAuthAttribute));
                //services.AddJwtEx();//这里就是注入JWT
            });
 
如果不是 如下添加
builder.Services.AddMvc(options => options.Filters.Add(new AuthorizeFilter()));
//注册guolv
builder.Services.AddControllers(o =>
{
    o.Filters.Add();
    o.Filters.Add();
});
然后就大功告成
现在直接看结果

 
 
接着频繁操作

 
 该方案来自网络加以修改,如有侵权,请联系删除
 

来源:https://www.cnblogs.com/libo962464/archive/2023/02/14/17120659.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 白嫖一个WebAPI限流解决方案