- A+
什么是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 Code
IResourceFilter
//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 Code
IActionFilter
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.属性标注 [TypeFilter(typeof(CtmActionFilterAttribute))] 2.从容器中获取服务 var logger = context.HttpContext.RequestServices.GetService<ILogger<CtmActionFilterAttribute>>();
View Code
IActionFilter
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> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] 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 Code
IPCacheHelper 请求记录类
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 string maxTimes1 = 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[0].AddSeconds(partSecond) > nowTime) { return false; } else { return true; } } else { return true; } #endregion } else { #region 请求被拒绝就不需要加入当次请求了 if (item.ReqTime.Count >= maxTimes) { if (item.ReqTime[0].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[0].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[0].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 Code
IPCacheInfoModel 实体类
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
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[configKey];//获取直级参数值 } //youdianwenti w xiangxiang } }
View Code
以上工作准备完全后,在我们的Startup里面修改加入以下代码
如果有ConfigureServices类,添加如下
//注册guolv
services.AddControllers(o =>
{
o.Filters.Add<ApiAuthorize>();
o.Filters.Add<MyAuthentication>();
//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<ApiAuthorize>();
o.Filters.Add<MyAuthentication>();
});
然后就大功告成
现在直接看结果
接着频繁操作
该方案来自网络加以修改,如有侵权,请联系删除