像 AspNetCoreRateLimit 這種現成套件我之前有跟大家介紹過,今天就不提了,我們來聊聊背後的原理,歡迎各位大神指正!
像我們常看到的一些 API 請求介面網站:

拿請求國外主要城市的七日介面舉例,非 VIP 只能使用 2000 次,VIP 用戶一天最多請求 10000 次,請求該介面時,必須註冊帳號取得 appid 和密鑰。
那我們根據這個需求,設計一個取得天氣的限流介面。
第一步
驗證登入帳號是否存在,如果不存在,我們拋出不存在錯誤
[HttpPost("GetWeather")]
public IActionResult ApiLimit(WeatherInfor weatherInfor)
{
if (!_userService.IsValid(weatherInfor.Appid, weatherInfor.Appsecret))
{
throw new Exception("帳號或密碼錯誤");
}
}
第二步
判斷該帳戶是否為 VIP 用戶,如果是 VIP 用戶,則沒有總調用次數限制,只有單日限制次數。像這種單日限制次數、隔天清空的資料,我們肯定用快取來處理比較合理。我們設定每天的 23 點 59 分 59 秒所有請求快取是否有效,這就是快取的絕對過期時間。
那具體業務邏輯就是這樣的:由於用戶的 appid 是唯一的,我們可以把它當作 key 值,調用次數當作 value 值。如果快取不存在我們就新增快取,如果快取存在我們就取得調用次數,如果大於 2000 我們就告訴呼叫端調用次數已用完;如果沒有,我們就從快取中取得調用次數,並將其 +1。
快取類別
public class MemoryCacheHelper
{
public static MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
/// <summary>
/// 驗證快取項目是否存在
/// </summary>
/// <param name="key">快取Key</param>
/// <returns></returns>
public static bool Exists(string key)
{
if (key == null)
{
return false;
}
return _cache.TryGetValue(key, out _);
}
/// <summary>
/// 取得快取
/// </summary>
/// <param name="key">快取Key</param>
/// <returns></returns>
public static object Get(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (!Exists(key))
throw new ArgumentNullException(nameof(key));
return _cache.Get(key);
}
/// <summary>
/// 新增快取
/// </summary>
/// <param name="key">快取Key</param>
/// <param name="value">快取Value</param>
/// <param name="expiresSliding">滑動過期時間(如果在過期時間內有操作,則以當前時間點延長過期時間)</param>
/// <param name="expiressAbsoulte">絕對過期時間</param>
/// <returns></returns>
public static bool AddMemoryCache(string key, object value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
DateTime time = Convert.ToDateTime(DateTime.Now.AddDays(1).ToString("D").ToString()).AddSeconds(-1);
_cache.Set(key, value, time);
return Exists(key);
}
}
業務邏輯
public bool IsLimit(string appId)
{
if (MemoryCacheHelper.Exists(appId))
{
int value = int.Parse(MemoryCacheHelper.Get(appId).ToString());
if (value > 2000)
return false;
else
value = value + 1;
MemoryCacheHelper.AddMemoryCache(appId, value);
}
else
{
MemoryCacheHelper.AddMemoryCache(appId, 1);
}
return true;
}
最後
去查詢天氣介面,回傳資料
[HttpPost("GetWeather")]
public IActionResult ApiLimit(WeatherInfor weatherInfor)
{
if (!_userService.IsValid(weatherInfor.Appid, weatherInfor.Appsecret))
{
throw new Exception("帳號或密碼錯誤");
}
bool isLimit = false;
if (_userService.IsVIP(weatherInfor.Appid))
{
isLimit= _sqlServices.IsLimit(weatherInfor.Appid);
}
else
{
isLimit = _memoryCacheServices.IsLimit(weatherInfor.Appid);
}
if (isLimit)
{
//查詢天氣介面 回傳資料
}
else
{
throw new Exception("調用次數已用完");
}
return Ok("");
}
最後如果大家喜歡我的文章,還麻煩請關注並點個讚,希望 .NET 生態圈越來越好!