什麼是API限流: API 限流是限制用戶在一定時間內 API 請求數量的過程。應用程式編程介面 (API) 充當用戶和軟體應用程式之間的網關。例如,當用戶單擊社交媒體上的發佈按鈕時,點擊該按鈕會觸發 API 調用。此 API 與社交媒體應用程式的網路伺服器進行交互,並執行發佈操作。此用戶可以是人, ...
什麼是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>();
});
然後就大功告成
現在直接看結果
接著頻繁操作
該方案來自網路加以修改,如有侵權,請聯繫刪除
本文來自博客園,作者:酒笙匿清梔,轉載請註明原文鏈接:https://www.cnblogs.com/libo962464/p/17120659.html