.NETCore WebApi阻止接口重复调用(并发操作)
客户的APP反馈重复提交数据,需要阻止接口重复调用。
分享一段代码给大家:
/*===================================================================
* 程序说明: 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 "";
}
}
}
控制器定义特性:
[ApiController]
[RequestConcurrentAttribute]
public class _BaseController : ControllerBase
{
//省略代码......
}
若忽略并发操作:
[NotRequestConcurrent]
[HttpGet]
public string Test()
{
return string.Empty;
}