.NETCore WebApi阻止接口重复调用(并发操作)

客户的APP反馈重复提交数据,需要阻止接口重复调用。

分享一段代码给大家:

cs 复制代码
/*===================================================================
 *   程序说明: WebApi开发框架(.NET8+EFCore+AspNetCore)
 *            https://www.cscode.net/archive/webapi-netcore-v3/361414129516549.html
 *   原创作者: 珠海喜鹊信息技术有限公司
 *   创建日期: 2024/12/01 11:43:18
 *   最后修改: 2024/12/01 11:43:18 
 *   版权所有: Copyright 2006~2024, C/S框架网(www.csframework.com)
 *===================================================================*/
using CSFramework.WebApi.Common;
using CSFramework.WebApi.Core.Interfaces;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace CSFramework.WebApi.Core
{
    /// <summary>
    /// 不校验接口请求并发
    /// </summary>
    public class NotRequestConcurrentAttribute : BaseActionFilterAsyncAttribute { }

    /// <summary>
    /// 校验接口是否并发操作,重复请求(使用MemoryCache设置标记)
    /// </summary>
    public class RequestConcurrentAttribute : BaseActionFilterAsyncAttribute
    {
        public RequestConcurrentAttribute() { }

        /// <summary>
        /// 执行操作
        /// </summary>
        /// <param name="filterContext"></param>
        /// <returns></returns>
        public async override Task OnActionExecuting(ActionExecutingContext filterContext)
        {
            //判断是否需要接口请求并发
            if (filterContext.ContainsFilter<NotRequestConcurrentAttribute>())
            {
                await base.OnActionExecuting(filterContext);
                return;
            }

            //当前登录用户
            IUserContext user = Core.Globals.ServiceProvider.GetService<IUserContext>();

            //获取当前登录用户账号
            if (user.UserId.IsEmpty())
            {
                await base.OnActionExecuting(filterContext);
                return;
            }

            Console.WriteLine(">>RequestConcurrentAttribute.OnActionExecuting...");

            //获取缓存key值,如:"userId_9b80d5d0ebda4b11c4ba396cd4312f35"
            var key = GetCacheKey(user.UserId, filterContext.HttpContext.Request.Path);
            var cache = Core.Globals.ServiceProvider.GetService<IMemoryCache>();
            string value = cache.Get<String>(key);//从缓存获取key

            //有标记,并发操作!
            if (!value.IsEmpty())
            {
                var errMessage = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " "
                    + "重复操作,系统已阻止。线程ID:"
                    + System.Threading.Thread.CurrentThread.ManagedThreadId;

                //保存数据日志
                var log = Core.Globals.ServiceProvider.GetService<IApiLogBusiness>();
                log.Log("concurrent", user.IP, "请求并发:" + errMessage);

                //删除缓存标记
                cache.Remove(key);

                throw new BizException(errMessage);
            }
            else
            {
                //设置缓存标记:同一个url,1秒内重复点击
                cache.Set<String>(key, "click", TimeSpan.FromSeconds(1));//设置1秒后自动过期。
            }

            await Task.CompletedTask;
        }

        public override Task OnActionExecuted(ActionExecutedContext context)
        {
            return base.OnActionExecuted(context);
        }

        /// <summary>
        /// 生成缓存key
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="requestPath"></param>
        /// <returns></returns>
        private string GetCacheKey(string userId, string requestPath)
        {
            string localPath = GetApiPath(requestPath);
            var key = userId + "_" + localPath.ToMD5String();
            return key;
        }

        /// <summary>
        /// 获取api接口名称,如:[api/common/get] => [common/get]
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private string GetApiPath(string path)
        {
            // 定义一个Regex对象实例
            Regex r = new Regex(@"(/api)?(/\S+)", RegexOptions.IgnoreCase);

            //在字符串中匹配
            Match m = r.Match(path);
            if (m.Success)
            {
                string local = m.Groups[2].Value;
                return local.ToLower();
            }

            return "";
        }
    }
}

控制器定义特性:

cs 复制代码
[ApiController]
[RequestConcurrentAttribute]
public class _BaseController : ControllerBase
{
    //省略代码......
}

若忽略并发操作:

cs 复制代码
[NotRequestConcurrent]
[HttpGet]
public string Test()
{
    return string.Empty;
}
相关推荐
时光追逐者17 小时前
在 Blazor 中使用 Chart.js 快速创建数据可视化图表
开发语言·javascript·信息可视化·c#·.net·blazor
三天不学习19 小时前
Lucene.Net FSDirectory 和 RAMDirectory 的区别和用法 【搜索引擎系列教程】
搜索引擎·.net·lucene
爱吃奶酪的松鼠丶1 天前
.net GRPC服务搭建,跨进程调用。C#应用和Python应用之间的通信。
python·c#·.net
S3下载站1 天前
Microsoft .NET Framework 4.8 离线安装包 下载
microsoft·.net
AI.NET 极客圈1 天前
AI与.NET技术实操系列(四):使用 Semantic Kernel 和 DeepSeek 构建AI应用
人工智能·.net
源之缘-OFD先行者1 天前
GMap.NET + WPF:构建高性能 ADS-B 航空器追踪平台
.net·wpf·ads-b
小码编匠2 天前
.NET 验证码生成神器基于 SkiaSharp 的高性能方案
后端·c#·.net
专注VB编程开发20年2 天前
Aspose.words,Aspose.cells,vb.net,c#加载许可证,生成操作选择:嵌入的资源
c#·word·.net·vb.net
破罐子不摔2 天前
【C#使用S7.NET库读取和写入西门子PLC变量】
java·c#·.net