ASP.NET Core 使用Filter和Redis实现接口防重

背景

日常开发中,经常需要对一些响应不是很快的关键业务接口增加防重功能,即短时间内收到的多个相同的请求,只处理一个,其余不处理,避免产生脏数据。

这和幂等性(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可以用于实现各种功能,例如身份验证、日志记录、异常处理、性能监控等。

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

编码实现

防重组件的思路很简单,将第一次请求的某些参数作为标识符存入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,除第一次调用成功,其余请求皆被短路

查看redis,已有记录

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/22976.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

每日一练 | 网络工程师软考真题 Day12

阅读以下说明&#xff0c;答复以下【问题1】至【问题3】 【说明】 某单位有1个总部和6个分部&#xff0c;各个部门都有自己的局域网。该单位申请了6个C类IP地址202.115.10.0/24~202.115.15.0/24&#xff0c;其中总部与分部4共用一个C类地址。现方案将这些部门用路由器互联&…

Mit6.006-problemSet03

3-1 哈希练习&#xff08;Hash Practice&#xff09; (a) 按顺序插入整数keys A[47, 61, 36, 52, 56, 33, 92]到尺寸为7的哈希表中&#xff0c;使用哈希函数 h ( k ) ( 10 k 4 ) m o d 7 h(k)(10k4)mod7 h(k)(10k4)mod7。哈希表的每个插槽&#xff0c;存储一个key&#xff…

字节真的是宇宙尽头吗?

身边在字节的朋友很多人抱怨很卷&#xff0c;但卷到何种程度?很多人没有直观感受。某乎上一个问题(在字节跳动工作是怎样的?)点赞排名第一的回答生动的解释了字节的卷。 租房的舍友在字节工作。 舍友主卧&#xff0c;我次卧。 合租两个月了&#xff0c;我没见过舍友长什么样。…

日语文法PPT截图31-45

31 形式名词 とき ところ 作为形式名词的话&#xff0c;一般是要写假名不写汉字的 相对时态 如果是一般时/将来时とき&#xff0c;就是先做后面的动作&#xff0c;在做前面的动作。 出教室的时候&#xff0c;关灯。 如果是过去时とき那么&#xff0c;是先做前面的动作&#…

【dfn序+DP】树

把一棵树转化成一个序列有三种方法&#xff1a; dfs序 dfn序&#xff08;时间戳&#xff09; 欧拉序 关于这三者的区别&#xff0c;参考这篇博客&#xff0c;讲的超级好&#xff01; 重谈DFS序、时间戳和欧拉序 - Seaway-Fu - 博客园 (cnblogs.com) 题意&#xff1a; 思路…

SVN 导出改动差异文件

文章目录 SVN 导出改动差异文件应用场景/背景介绍具体操作方法 SVN 导出改动差异文件 应用场景/背景介绍 当然下面的两个场景介绍可能用分支管理都会有不错的效果&#xff0c;或者更优&#xff0c;只是记录一下思路&#xff0c;用什么还是看大家个人爱好啦 在开发过程中偶尔会…

1. Ansible介绍,什么是Ansible?Ansible能用来做什么?

什么是Ansible&#xff1f;Ansible能用来做什么&#xff1f; 如果您是系统工程师或IT管理员,或者只是在IT部门工作的任何人,您可能会在环境中执行大量重复性任务, 无论是每天调整大小和创建新主机或虚拟机&#xff64; 在其上应用配置&#xff64; 修补数百台服务器&#xff6…

不用再找了,你要的国内好用的ChatGPT网站都在这里

&#x1f4a1; 大家好&#xff0c;我是可夫小子&#xff0c;关注AIGC、读书和自媒体。解锁更多ChatGPT、AI绘画玩法。加&#xff1a;keeepdance&#xff0c;备注&#xff1a;chatgpt&#xff0c;拉你进群。 目录 ChatGPT是什么 OpenAI与ChatGPT的发展历程 AI对话聊天 AI文档…

jdk13至15——文本块特性

文本块在jdk13中第一次预览&#xff0c;jdk14第二次预览&#xff0c;jdk15正式版&#xff1b; 终于不用在多行字符串中加一堆\n和一堆\"和一堆了&#xff1b; 之前需要这么麻烦&#xff1a; Testvoid test() {String s "testabcd\n" "aaa\n" "…

AI:Vue2和Vue3的对比

1. 什么是Vue.js以及Vue.js在前端开发中的重要性。 Vue.js是一个遵循MVVM&#xff08;Model-View-ViewModel&#xff09;模式的前端JavaScript框架&#xff0c;它采用了双向数据绑定和组件化的思想&#xff0c;使得前端开发变得更加简洁、高效、可维护。Vue.js由中国工程师尤雨…

JNDI学习笔记

最近在研究JNDI注入漏洞&#xff0c;就先浅浅的学习以下JNDI相关知识。 JNDI对各种目录服务的实现进行抽象和统一化。 在 Java 应用中除了以常规方式使用名称服务(比如使用 DNS 解析域名)&#xff0c;另一个常见的用法是使用目录服务作为对象存储的系统&#xff0c;即用目录服务…

领导下发紧急且风险大的任务,如何处理?

在遇到这种无法拒绝&#xff0c;明显很难按时交付的紧急任务时&#xff0c;项目经理处理的关键&#xff1a; 1、降低关键干系人期望值 降低关键干系人的期望值&#xff0c;是项目管理非常重要的一门艺术&#xff0c;也是让干系人满意&#xff0c;便于与关系人沟通的关键。 在项…

Centos8安装ffmpeg,使用mediamtx搭建RTSP流媒体服务器

文章目录 1、Centos安装ffmpeg2、使用mediamtx搭建媒体服务器 1、Centos安装ffmpeg 1、先安装epel-release yum install epel-release2、安装nux存储库 rpm -v --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/…

MySQL之触发器相关操作

1. 概念 触发器&#xff0c;就是⼀种特殊的存储过程。触发器和存储过程⼀样是⼀个能够完成特定功能、存储 在数据库服务器上的SQL⽚段&#xff0c;但是触发器⽆需调⽤&#xff0c;当对数据表中的数据执⾏DML操作时 ⾃动触发这个SQL⽚段的执⾏&#xff0c;⽆需⼿动调⽤。 在MyS…

30多家投递石沉大海,总算上岸了

大家好&#xff0c;我是帅地。 今年的行情&#xff0c;无论是暑假实习还是春招校招&#xff0c;都比往年要难一些&#xff0c;很多人在三月份要嘛简历石沉大海&#xff0c;要嘛面试一轮游&#xff0c;但也有部分人最后都拿到了不错的 Offer&#xff0c;包括我 训练营 里&#…

一款可以自动写代码的编辑器,解放你的双手

Cursor 是集成了 GPT-4 的 IDE 工具&#xff0c;目前免费并且无需 API Key&#xff0c;支持 Win、Mac、Linux 平台&#xff0c;可以按要求生成代码&#xff0c;或者让 AI 帮助优化代码&#xff0c;分析代码。Cursor目前已经集成了openai的GPT-4&#xff0c;它或将彻底改变我们写…

2023开放原子全球开源峰会分论坛即将来袭,Pick你最关注的峰会话题!

2023开放原子全球开源峰会即将开启 二十余场分论坛主题重磅首发 聚焦全球开源发展最新动向 前沿技术、行业实践、开源项目与治理等 多场知识盛宴等您来享 为更好地了解大家的参与意向 分论坛投票今天正式启动&#xff01; 投票时间&#xff1a;5月19-26日 长按识别二维码 …

109.(cesium篇)cesium椎体上下跳动+旋转

地图之家总目录(订阅之前请先查看该博客) 地图之家:cesium+leaflet+echart+地图数据+地图工具等相关内容的介绍 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <html lang="en…

小白漂流记(如何自学网络安全?)

一、前言&#xff08;关于我&#xff09; 我算是“入行”不久的一个新人安全工作者&#xff0c;为什么是引号呢&#xff0c;因为我是个“半个野路子”出身。早在13年的时候&#xff0c;我在初中时期就已经在90sec、wooyun等社区一直学习、报告漏洞。后来由于升学的压力&#xf…

Cisco® Catalyst® 8000V 边缘软件 (Catalyst 8000V) 17.11.1a 发布 - 虚拟路由器

Cisco Catalyst 8000v Edge Software, IOS XE Release Dublin-17.11.1a ED 请访问原文链接&#xff1a;https://sysin.org/blog/cisco-catalyst-8000v/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org Cisco Catalyst 8000V 边…