Login
网站首页 > 文章中心 > 其它

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

作者:小编 更新时间:2023-10-02 14:32:16 浏览量:335人看过

背景

日常开发中,经常需要对一些响应不是很快的关键业务接口增加防重功能,即短时间内收到的多个相同的请求,只处理一个,其余不处理,避免产生脏数据.这和幂等性(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可以用于实现各种功能,例如身份验证、日志记录、异常处理、性能监控等.

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

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

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

编码实现

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

PreventDuplicateRequestsActionFilter


public class PreventDuplicateRequestsActionFilter : IAsyncActionFilter
{
public string[] FactorNames { get; set; }
public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; }

private readonly IDistributedCache _cache;
private readonly ILogger _logger;

public PreventDuplicateRequestsActionFilter(IDistributedCache cache, ILogger 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();
    }
}
}


PreventDuplicateRequestsAttribute

防重组件的全部逻辑在PreventDuplicateRequestsActionFilter中已经实现,由于它需要注入 IDistributedCache和ILogger对象,我们使用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();
    filter.FactorNames = _factorNames;
    filter.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(_expiredMinutes);
    return filter;
}
public bool IsReusable => false;
}


注册

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


builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "12⑦0.0.1:6379,DefaultDatabase=1";
});
builder.Services.AddScoped();


使用

假设我们有一个接口CancelOrder,我们指定入参中的OrderId为防重因子.


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

public class CancelOrderRequest
{
    public Guid OrderId { get; set; }
    public string reason { get; set; }
}
}


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

参考链接

以上就是土嘎嘎小编为大家整理的ASP.NET_Core使用filter和redis实现接口防重相关主题介绍,如果您觉得小编更新的文章只要能对粉丝们有用,就是我们最大的鼓励和动力,不要忘记讲本站分享给您身边的朋友哦!!

版权声明:倡导尊重与保护知识产权。未经许可,任何人不得复制、转载、或以其他方式使用本站《原创》内容,违者将追究其法律责任。本站文章内容,部分图片来源于网络,如有侵权,请联系我们修改或者删除处理。

编辑推荐

热门文章