ASP.NET Core Filter通过缓存实现接口防重
接口防重复调用
缓存使用文档请看这篇博客。
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 requestPathAndQuery = context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
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);
}
/// <summary>
/// 用MD5加密字符串,可选择生成16位或者32位的加密字符串(.net 4)
/// </summary>
/// <param name="password">待加密的字符串</param>
/// <returns>返回的加密后的字符串</returns>
private string MD5Encrypt32(string str)
{
HashAlgorithm md5Hasher = new SHA1Managed();
byte[] hashedDataBytes;
hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(str));
StringBuilder tmp = new StringBuilder();
foreach (byte i in hashedDataBytes)
{
tmp.Append(i.ToString("x2"));
}
return tmp.ToString();//默认情况
}
}
}
在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);
}
.net framework 4.5 接口防重复调用
using InsCES3.Business.Inspecting;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace iDam2.DataCollection.Inspecting.Attributes
{
/// <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 = "app:apilock:path:{0}:arguments:{1}";
private CacheBus cacheBus = CacheBus.Instance;
/// <summary>
/// 请求限制
/// </summary>
/// <param name="requestLocktime">锁定时间(毫秒),默认三秒</param>
/// <param name="includeArguments">请求控制是否包含Body参数</param>
/// <param name="message">错误提示</param>
public RequestRestrictionsAttribute(int requestLocktime = 3000, bool includeArguments = true, string message = "频繁请求, 请稍后重试")
{
_requestLocktime = requestLocktime;
_includeArguments = includeArguments;
_message = message;
}
public override void OnActionExecuting(HttpActionContext context)
{
if (context == null)
{
return;
}
//string requestPath = context.Request.RequestUri.AbsolutePath;
string requestPathAndQuery = context.Request.RequestUri.PathAndQuery;
string actionArguments = GetArguments(context);
//*****此处key最好加上context.HttpContext.Request.QueryString和请求头用户信息进行判断*****
string RequestLockCacheKey = string.Format(KEY_PRIFIX_RequestLock, requestPathAndQuery, MD5Encrypt32(actionArguments));
if (cacheBus.Exists<string>(RequestLockCacheKey)) throw new Exception(_message);
cacheBus.SetCache(RequestLockCacheKey, "1", _requestLocktime);
//转入用户Action操作
base.OnActionExecuting(context);
}
/// <summary>
/// 用MD5加密字符串,可选择生成16位或者32位的加密字符串
/// </summary>
/// <param name="password">待加密的字符串</param>
/// <returns>返回的加密后的字符串</returns>
private string MD5Encrypt32(string str)
{
HashAlgorithm md5Hasher = new SHA1Managed();
byte[] hashedDataBytes;
hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(str));
StringBuilder tmp = new StringBuilder();
foreach (byte i in hashedDataBytes)
{
tmp.Append(i.ToString("x2"));
}
return tmp.ToString();//默认情况
}
private string GetArguments(HttpActionContext context)
{
var argumentsEncrypt = "arguments";
if (this._includeArguments && context.ActionArguments != null)
{
return JsonConvert.SerializeObject(context.ActionArguments);
}
return argumentsEncrypt;
}
}
}
ApiRateLimit限流文档
- AspNetCoreRateLimit
- Microsoft.AspNetCore.RateLimiting