首页 > 数据库 >ASP.NET Core使用filter和redis实现接口防重

ASP.NET Core使用filter和redis实现接口防重

时间:2023-03-27 18:55:30浏览次数:55  
标签:Core ASP string redis filter var NET public

背景

日常开发中,经常需要对一些响应不是很快的关键业务接口增加防重功能,即短时间内收到的多个相同的请求,只处理一个,其余不处理,避免产生脏数据。这和幂等性(idempotency)稍微有点区别,幂等性要求的是对重复请求有相同的效果结果,通常需要在接口内部执行业务操作前检查状态;而防重可以认为是一个业务无关的通用功能,在ASP.NET Core中我们可以借助Filter和redis实现。

关于Filter

Filter的由来可以追溯到ASP.NET MVC中的ActionFilter和ASP.NET Web API中的ActionFilterAttribute。ASP.NET Core将这些不同类型的Filter统一为一种类型,称为Filter,以简化API和提高灵活性。ASP.NET Core中Filter可以用于实现例如身份验证、日志记录、异常处理、性能监控等各种功能。

image

通过使用Filter,我们可以在请求处理管道的特定阶段之前或者之后运行自定义代码,达到AOP的效果。

image

编码实现

防重组件的思路很简单,将第一次请求的某些参数作为标识符存入redis中,并设置过期时间,下次请求过来,先检查redis相同的请求是否已被处理;
作为一个通用组件,我们需要能让使用者自定义作为标识符的字段以及过期时间,下面开始实现。

PreventDuplicateRequestsActionFilter

  public class PreventDuplicateRequestsActionFilter : IAsyncActionFilter
  {
  public string[] FactorNames { get; set; }
  public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; }
   
  private readonly IDistributedCache _cache;
  private readonly ILogger<PreventDuplicateRequestsActionFilter> _logger;
   
  public PreventDuplicateRequestsActionFilter(IDistributedCache cache, ILogger<PreventDuplicateRequestsActionFilter> logger)
  {
  _cache = cache;
  _logger = logger;
  }
   
  public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
  {
  var factorValues = new string?[FactorNames.Length];
   
  var isFromBody =
  context.ActionDescriptor.Parameters.Any(r => r.BindingInfo?.BindingSource == BindingSource.Body);
  if (isFromBody)
  {
  var parameterValue = context.ActionArguments.FirstOrDefault().Value;
  factorValues = FactorNames.Select(name =>
  parameterValue?.GetType().GetProperty(name)?.GetValue(parameterValue)?.ToString()).ToArray();
  }
  else
  {
  for (var index = 0; index < FactorNames.Length; index++)
  {
  if (context.ActionArguments.TryGetValue(FactorNames[index], out var factorValue))
  {
  factorValues[index] = factorValue?.ToString();
  }
  }
  }
   
  if (factorValues.All(string.IsNullOrEmpty))
  {
  _logger.LogWarning("Please config FactorNames.");
   
  await next();
  return;
  }
   
  var idempotentKey = $"{context.HttpContext.Request.Path.Value}:{string.Join("-", factorValues)}";
  var idempotentValue = await _cache.GetStringAsync(idempotentKey);
  if (idempotentValue != null)
  {
  _logger.LogWarning("Received duplicate request({},{}), short-circuiting...", idempotentKey, idempotentValue);
  context.Result = new AcceptedResult();
  }
  else
  {
  await _cache.SetStringAsync(idempotentKey, DateTimeOffset.UtcNow.ToString(),
  new DistributedCacheEntryOptions {AbsoluteExpirationRelativeToNow = AbsoluteExpirationRelativeToNow});
  await next();
  }
  }
  }

PreventDuplicateRequestsActionFilter里,我们首先通过反射从 ActionArguments拿到指定参数字段的值,由于从request body取值略有不同,我们需要分开处理;接下来开始拼接key并检查redis,如果key已经存在,我们需要短路请求,这里直接返回的是 Accepted (202)而不是Conflict (409)或者其它错误状态,是为了避免上游已经调用失败而继续重试。

PreventDuplicateRequestsAttribute

防重组件的全部逻辑在PreventDuplicateRequestsActionFilter中已经实现,由于它需要注入IDistributedCacheILogger对象,我们使用IFilterFactory实现一个自定义属性,方便使用。

  [AttributeUsage(AttributeTargets.Method)]
  public class PreventDuplicateRequestsAttribute : Attribute, IFilterFactory
  {
  private readonly string[] _factorNames;
  private readonly int _expiredMinutes;
   
  public PreventDuplicateRequestsAttribute(int expiredMinutes, params string[] factorNames)
  {
  _expiredMinutes = expiredMinutes;
  _factorNames = factorNames;
  }
   
  public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
  {
  var filter = serviceProvider.GetService<PreventDuplicateRequestsActionFilter>();
  filter.FactorNames = _factorNames;
  filter.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(_expiredMinutes);
  return filter;
  }
  public bool IsReusable => false;
  }

注册

为了简单,操作redis,直接使用微软Microsoft.Extensions.Caching.StackExchangeRedis包;注册PreventDuplicateRequestsActionFilterPreventDuplicateRequestsAttribute无需注册。

  builder.Services.AddStackExchangeRedisCache(options =>
  {
  options.Configuration = "127.0.0.1:6379,DefaultDatabase=1";
  });
  builder.Services.AddScoped<PreventDuplicateRequestsActionFilter>();
   

使用

假设我们有一个接口CancelOrder,我们指定入参中的OrderId和Reason为因子。

  namespace PreventDuplicateRequestDemo.Controllers
  {
  [Route("api/[controller]")]
  [ApiController]
  public class OrderController : ControllerBase
  {
  [HttpPost(nameof(CancelOrder))]
  [PreventDuplicateRequests(5, "OrderId", "Reason")]
  public async Task<IActionResult> CancelOrder([FromBody] CancelOrderRequest request)
  {
  await Task.Delay(1000);
  return new OkResult();
  }
  }
   
  public class CancelOrderRequest
  {
  public Guid OrderId { get; set; }
  public string Reason { get; set; }
  }
  }

启动程序,多次调用api,除第一次调用成功,其余请求皆被短路
image

查看redis,已有记录
image

参考链接

https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-7.0
https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-7.0

作者: Clivox

出处: https://www.cnblogs.com/netry/p/aspnetcore-prevent-duplicate-requests-filter-redis.html

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

  转 https://www.cnblogs.com/netry/p/aspnetcore-prevent-duplicate-requests-filter-redis.html

标签:Core,ASP,string,redis,filter,var,NET,public
From: https://www.cnblogs.com/wl-blog/p/17262526.html

相关文章

  • OWASP ZAP安全测试工具--安装
    OWASPZAP是世界上最受欢迎的免费安全工具之一。ZAP可以帮助我们在开发和测试应用程序过程中,自动发现Web应用程序中的安全漏洞。另外,它也是一款提供给具备丰富经验的渗透测......
  • Redis 列表(List)
    Redis列表(List)Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)一个列表最多可以包含232-1个元素(4294967295,......
  • Spring AOP、AspectJ、CGLIB
     静态代理和动态代理AOP代理则可分为静态代理和动态代理两大类,其中静态代理是指使用AOP框架提供的命令进行编译,从而在编译阶段就可生成AOP代理类,因此也称为编译时......
  • Redis 哈希(Hash)
    Redis哈希(Hash)Redishash是一个string类型的field(字段)和value(值)的映射表,hash特别适合用于存储对象。Redis中每个hash可以存储232-1键值对(40多亿)。实......
  • ASP.NET Core - 选项系统之选项配置
    1.选项前面讲完了.NETCore下的配置系统,我们可以通过IConfiguration服务从各种来源的配置中读取到配置信息,但是每次要用的时候都通过Iconfiguration读取配置文件会......
  • C# StackExchange.Redis 用法总结
    阅读目录安装 StackExchange.Redis引用及初始化String(字符串)List(列表)Hash(哈希)发布订阅事务Batch批量操作Lock(分布式锁)StackExchange.Redis封装安装 St......
  • redis hash类型操作
    Redis-Hash前言hash在很多编程语言中都有着很广泛的应用,而在Redis中也是如此,在redis中,哈希类型是指Redis键值对中的值本身又是一个键值对结构,形如value=[{field1,value1},......
  • Redis - 对象结构
    简介Redis使用对象存储数据库中的键和值,每当在Redis中创建一个新的键值对时,都会创建两个对象:一个是键对象,另一个是值对象。其中,Redis的每种对象都由对象结构和对应......
  • 【转】NETCore下IConfiguration和IOptions的用法
    新建一个NETCoreWebAPI项目,在Startup.cs里就会开始使用IConfiguration和IOptions了,我们来看看如何使用。IConfiguration是用来加载配置值的,可以加载内存键值对、JSON或X......
  • ASP.NET Core - 配置系统之配置读取
    一个应用要运行起来,往往需要读取很多的预设好的配置信息,根据约定好的信息或方式执行一定的行为。配置的本质就是软件运行的参数,在一个软件实现中需要的参数非常多,如果我们......