接口防重複調用
緩存使用文檔請看這篇博客。
ASP.NET Core Filter文檔請看這篇博客
添加Nuget包添加緩存
--Memory
Install-Package Microsoft.Extensions.Caching.Memory
--Redis
Install-Package Microsoft.Extensions.Caching.StackExchangeRedis
Program.cs
添加配置
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var builder = WebApplication.CreateBuilder(args);
//Memory
builder.Services.AddDistributedMemoryCache();
//redis
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "192.168.1.5:6379,password=123456,ssl=False,abortConnect=False";
options.InstanceName = "App-Instance-Keys";
});
去重RequestRestrictionsAttribute
代碼
注意這裏沒有區分Url QueryString和Request Header邏輯
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Distributed;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace WebApplication1
{
/// <summary>
/// API限重Filter
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public sealed class RequestRestrictionsAttribute : ActionFilterAttribute
{
/// <summary>
/// 單次請求最小鎖定時間(毫秒)
/// </summary>
private readonly int _requestLocktime;
/// <summary>
/// 請求控制是否包含Body參數
/// </summary>
private readonly bool _includeArguments;
/// <summary>
/// Message
/// </summary>
private readonly string _message;
/// <summary>
/// 緩存Key
/// </summary>
private static readonly string KEY_PRIFIX_RequestLock = "myapp:apilock:path:{0}:arguments:{1}";
/// <summary>
/// 請求限制
/// </summary>
/// <param name="requestLocktime">鎖定時間(毫秒),默認五秒</param>
/// <param name="includeArguments">請求控制是否包含Body參數</param>
/// <param name="message">錯誤提示</param>
public RequestRestrictionsAttribute(int requestLocktime = 5000, bool includeArguments = true, string message = "頻繁請求, 請稍後重試")
{
_requestLocktime = requestLocktime;
_includeArguments = includeArguments;
_message = message;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context == null)
{
return;
}
var storage = context.HttpContext.RequestServices.GetService<IDistributedCache>();
if (storage == null)
{
base.OnActionExecuting(context);
return;
}
string requestPath = context.HttpContext.Request.Path;
string actionArguments = _includeArguments ? JsonSerializer.Serialize(context.ActionArguments) : "arguments";
//*****此處key最好加上context.HttpContext.Request.QueryString和請求頭用戶信息進行判斷*****
string RequestLockCacheKey = string.Format(KEY_PRIFIX_RequestLock, requestPath, HashEncrypt(actionArguments));
if (storage.GetString(RequestLockCacheKey) != null) throw new Exception(_message);
storage.SetString(RequestLockCacheKey, "1", new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMilliseconds(_requestLocktime) });
//轉入用戶Action操作
base.OnActionExecuting(context);
}
private static string HashEncrypt(string str)
{
byte[] hashedDataBytes = MD5.HashData(Encoding.GetEncoding("gb2312").GetBytes(str));
//.net 5+
return Convert.ToHexString(hashedDataBytes);
//.net 5以下版本
//StringBuilder sb = new();
//foreach (byte i in hashedDataBytes) sb.Append(i.ToString("x2"));
//return sb.ToString();
//其他加密方式
//byte[] hashedDataBytes = Encoding.GetEncoding("gb2312").GetBytes(str);
//byte[] md5hashed32 = MD5.HashData(hashedDataBytes);
//byte[] hashed40 = SHA1.HashData(hashedDataBytes);
//byte[] hashed64 = SHA256.HashData(hashedDataBytes);
//byte[] hashed128 = SHA512.HashData(hashedDataBytes);
}
}
}
在Controller
中使用
using Microsoft.AspNetCore.Mvc;
namespace WebApplication1.Controllers;
[ApiController]
[Route("[controller]")]
[RequestRestrictions(1000 * 5)]
public class WeatherForecastController : ControllerBase { }
在方法中使用
[HttpGet]
[RequestRestrictions(1000 * 5)]
public IEnumerable<int> Get()
{
return Enumerable.Range(1, 5);
}
ApiRateLimit限流文檔
- AspNetCoreRateLimit
- Microsoft.AspNetCore.RateLimiting