当前位置: 首页 > news >正文

C#WEB 防重复提交控制

生产环境由于业务较为频繁,若网络或设备出现卡顿可能存在重复提交等问题,该问题通过前端的防止重复提交已经不管用,需要在后端或数据库增加控制。

若项目已经上线一段时间后发生此类问题,整体改造系统的成本较大,可以通过增加拦截器,拦截请求,设定1秒或者一个合理的时间周期为控制条件,如果同一个账户同一个请求路径再次期间内连续请求则拦截本次请求并返回前端错误信息,一定程度上避免业务数据混乱。

本次从.net frameowrk 4.5 级.net Core -.Net 各版本入手,设定公共且兼容的方法来应对并发问题。

本次功能逻辑范围:

1、通过拦截器调用此方法,传入用户、请求路径信息

2、设置白名单,将不需要控制的路径填写在此处,系统判断并发时不会处理白名单内的请求

3、使用内存记录请求时间

4、增加定期内存垃圾处理,将超过10秒未更新的请求记录清空

封装一个方法类:RequestThrottler.cs

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;public static class RequestThrottler
{// 白名单路径(不区分大小写)private static readonly HashSet<string> WhitelistPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase){"/api/health","/login","/logout","/static/",// 可扩展};// 存储结构:key -> { LastAccessTime, LastRequestTime }private static readonly ConcurrentDictionary<string, TimestampEntry> Cache =new ConcurrentDictionary<string, TimestampEntry>();// 清理任务控制private static Timer _cleanupTimer;private static readonly TimeSpan CleanupInterval = TimeSpan.FromSeconds(10);   // 每10秒清理一次private static readonly TimeSpan ExpiryThreshold = TimeSpan.FromSeconds(10);   // 超过10秒未使用则清除// 静态构造函数:启动清理定时器static RequestThrottler(){// 使用 Timer 兼容 .NET Framework 4.5 和 .NET Core+_cleanupTimer = new Timer(CleanupExpiredEntries, null, CleanupInterval, CleanupInterval);}public static bool IsAllowed(string userId, string requestPath){if (string.IsNullOrWhiteSpace(userId) || string.IsNullOrWhiteSpace(requestPath))return true; // 容错:允许var normalizedPath = NormalizePath(requestPath);if (IsWhitelisted(normalizedPath))return true;var key = $"{userId}|{normalizedPath}";var now = DateTime.UtcNow;// 获取或添加条目,并更新最后访问时间var entry = Cache.GetOrAdd(key, new TimestampEntry { LastRequestTime = now });// 原子性检查是否在1秒内重复请求var lastReq = entry.LastRequestTime;if ((now - lastReq).TotalSeconds < 1.0){// 更新最后访问时间(用于清理判断),但不更新请求时间(防止绕过限流)Interlocked.Exchange(ref entry.LastAccessTime, now.Ticks);return false;}// 允许请求:更新请求时间和访问时间var newEntry = new TimestampEntry{LastRequestTime = now,LastAccessTime = now.Ticks};Cache[key] = newEntry; // 覆盖旧值(线程安全)return true;}// 后台清理逻辑private static void CleanupExpiredEntries(object state){try{var cutoffTicks = (DateTime.UtcNow - ExpiryThreshold).Ticks;var keysToRemove = new List<string>();foreach (var kvp in Cache){// 如果最后访问时间早于阈值,则标记删除if (kvp.Value.LastAccessTime < cutoffTicks){keysToRemove.Add(kvp.Key);}}foreach (var key in keysToRemove){Cache.TryRemove(key, out _);}}catch{// 忽略清理异常,避免 Timer 崩溃}}private static string NormalizePath(string path){if (string.IsNullOrEmpty(path)) return path;var idx = path.IndexOf('?');if (idx >= 0) path = path.Substring(0, idx);return path.ToLowerInvariant();}private static bool IsWhitelisted(string path){if (string.IsNullOrEmpty(path)) return false;if (WhitelistPaths.Contains(path)) return true;foreach (var prefix in WhitelistPaths){if (prefix.EndsWith("/") && path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))return true;}return false;}// 内部结构体:存储两个时间戳private class TimestampEntry{public DateTime LastRequestTime; // 上次发起有效请求的时间(用于限流判断)public long LastAccessTime;      // 上次被访问的时间(用于清理判断,用 Ticks 提升性能)}
}

1、NET Framework 4.5(ASP.NET MVC / Web API)全局拦截(推荐):通过 ActionFilterAttribute

public class ThrottleAttribute : ActionFilterAttribute
{public override void OnActionExecuting(ActionExecutingContext filterContext){var user = filterContext.HttpContext.User?.Identity?.Name ?? "anonymous";var path = filterContext.HttpContext.Request.Path;if (!RequestThrottler.IsAllowed(user, path)){filterContext.Result = new HttpStatusCodeResult(429, "请求过于频繁");}base.OnActionExecuting(filterContext);}
}

2. .NET Core 3.1 / .NET 6 / .NET 7 / .NET 8(ASP.NET Core)

Action Filter(局部控制)
public class ThrottleActionFilter : IActionFilter
{public void OnActionExecuting(ActionExecutingContext context){var userId = context.HttpContext.User?.Identity?.Name ?? "anonymous";var path = context.HttpContext.Request.Path;if (!RequestThrottler.IsAllowed(userId, path)){context.Result = new ObjectResult("请求过于频繁") { StatusCode = 429 };}}public void OnActionExecuted(ActionExecutedContext context) { }
}// 注册
services.AddScoped<ThrottleActionFilter>();// 使用
[ServiceFilter(typeof(ThrottleActionFilter))]
public IActionResult Submit() { ... }

http://www.dtcms.com/a/596749.html

相关文章:

  • Linux:systemd服务之.service文件(二)
  • 24_FastMCP 2.x 中文文档之FastMCP服务端认证:构建完整的 OAuth 服务器详解
  • Linux:认识Systemd服务(一)
  • Python编程实战 - Python实用工具与库 - 爬取并存储网页数据
  • 网站建设中字样图片wordpress首页调用文章数量
  • “基于‘多模态SCA+全周期协同’的中间件开源风险治理实践”荣获OSCAR开源+安全及风险治理案例
  • BetterDisplay Pro for Mac显示器增强工具
  • 解决huggingface下载仓库时有部分大文件不能下载的问题
  • Qt键盘组合
  • Qt中的QShortcut:高效键盘快捷方式开发指南
  • c mvc制作网站开发google谷歌
  • STM32F103RCT6+STM32CubeMX+keil5(MDK-ARM)+Flymcu完成轮询方式检测按键
  • paimon实战 -- Flink 写入 Paimon 流程深度解析
  • HOT100题打卡第35天——二分查找
  • R语言 | 带重要性相关热图和贡献图如何解释?如何绘制随机森林计算结果重要性及相关性图?[学习笔记]
  • 做 专而精 的网站网站建设个人主要事迹
  • 怎么查看一个网站是谁做的注册城乡规划师备考
  • CMake开源库的编译与使用
  • GitLab CI/CD和Arbess,开源免费CI/CD工具选型指南
  • Observability:适用于 PHP 的 OpenTelemetry:EDOT PHP 加入 OpenTelemetry 项目
  • 第二十七篇:C++20协程:异步编程的终极革命
  • 【后端】【面试】 ③ PostgreSQL高级面试题(含答案与实战案例)
  • 静态加载法
  • 规范使用指针
  • 工业实时数据库选型指南:深度解析紫金桥 vs TDengine
  • DNS主从服务器的配置
  • DDR4 4.7 Write Leveling
  • 云手机通常使用什么架构
  • 幽冥大陆(十七)手机摄像头注册到电脑——东方仙盟炼气期
  • DNS记录更新后为什么还是访问不到新服务器?