.NET 接口限流、防重、幂等性设计

一、设计思路

  表格

 

特性 作用 实现方式
限流 防止接口被刷、防止流量洪峰 令牌桶 / 滑动窗口 + Redis / 内存缓存
防重 防止前端重复点击 / 网络重试重复提交 请求唯一 ID + 短时间锁
幂等性 保证接口重复调用结果一致 唯一业务键 + 状态机 + 分布式锁

 


 

二、.NET 完整实现代码

 

1. 依赖安装

 

Install-Package AspNetCoreRateLimit # 限流
Install-Package StackExchange.Redis # Redis
Install-Package MediatR # 可选
Install-Package Microsoft.Extensions.Caching.StackExchangeRedis

 


 

2. 接口限流

  使用 AspNetCoreRateLimit,支持 IP 限流、客户端限流。  

appsettings.json

 

"IpRateLimiting": {
  "EnableEndpointRateLimiting": true,
  "StackBlockedRequests": false,
  "HttpStatusCode": 429,
  "GeneralRules": [
    {
      "Endpoint": "*",
      "Period": "1s",
      "Limit": 10  // 每秒最多 10 次
    },
    {
      "Endpoint": "POST:/api/order",
      "Period": "1s",
      "Limit": 2   // 订单接口每秒最多 2 次
    }
  ]
}

 

Program.cs 注册

 

builder.Services.AddMemoryCache();
builder.Services.AddInMemoryRateLimiting();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();

app.UseIpRateLimiting(); // 必须放在路由之前

  限流返回:429 Too Many Requests  


 

3. 防重复提交

  核心:请求唯一 ID(RequestId)+ 短时间锁   前端提交时携带
Request-Id 头,后端校验是否重复。  

防重过滤器 / 中间件

 

/// <summary>
/// 防重复提交特性
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class IdempotentAttribute : Attribute
{
    /// <summary>
    /// 锁过期时间(秒)
    /// </summary>
    public int ExpireSeconds { get; set; } = 5;
}

 

防重过滤器

 

public class IdempotentFilter : IAsyncActionFilter
{
    private readonly IDistributedCache _cache;

    public IdempotentFilter(IDistributedCache cache)
    {
        _cache = cache;
    }

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // 只拦截 POST/PUT/DELETE
        var method = context.HttpContext.Request.Method;
        if (method is "GET" or "HEAD")
        {
            await next();
            return;
        }

        // 获取请求唯一 ID(前端传入 Header)
        if (!context.HttpContext.Request.Headers.TryGetValue("Request-Id", out var requestId))
        {
            context.Result = new JsonResult(new { code = 400, msg = "缺少 Request-Id" });
            return;
        }

        var attr = context.ActionDescriptor.EndpointMetadata
            .OfType<IdempotentAttribute>()
            .FirstOrDefault();

        var key = $"req:lock:{requestId}";

        // 已存在 = 重复提交
        var exists = await _cache.GetStringAsync(key);
        if (!string.IsNullOrEmpty(exists))
        {
            context.Result = new JsonResult(new { code = 429, msg = "请勿重复提交" });
            return;
        }

        // 加锁
        await _cache.SetStringAsync(key, "1", new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(attr?.ExpireSeconds ?? 5)
        });

        await next();
    }
}

 

注册

 

builder.Services.AddScoped<IdempotentFilter>();
builder.Services.AddDistributedRedisCache(options =>
{
    options.Configuration = "localhost:6379";
});

builder.Services.AddControllers(o =>
{
    o.Filters.Add<IdempotentFilter>();
});

 

使用

 

[HttpPost("order")]
[Idempotent(ExpireSeconds = 10)] // 10 秒内防重
public async Task<IActionResult> CreateOrder(OrderDto dto)

 


 

4. 幂等性设计

 

幂等性定义

  同一个请求执行 1 次和执行 N 次,结果完全一样,不会产生脏数据。   适用场景:  

  • 支付
  • 订单创建
  • 退款
  • 表单提交
  • 回调接口

 

实现方案

 

步骤

 

  1. 前端获取 token(幂等号)
  2. 提交时携带 token
  3. 后端使用 分布式锁 + 状态机 保证只执行一次

 

代码实现

 

1)获取幂等 Token

 

[HttpGet("token")]
public async Task<IActionResult> GetIdempotentToken()
{
    var token = Guid.NewGuid().ToString("N");
    await _cache.SetStringAsync($"idt:{token}", "ok", TimeSpan.FromMinutes(10));
    return Ok(new { token });
}

 

2)幂等接口

 

[HttpPost("pay")]
public async Task<IActionResult> Pay(PayRequest dto)
{
    if (string.IsNullOrEmpty(dto.IdempotentToken))
        return BadRequest("缺少幂等Token");

    var lockKey = $"lock:pay:{dto.IdempotentToken}";

    // 分布式锁(只有一个请求能进入)
    var lockTaken = await _cache.GetStringAsync(lockKey);
    if (!string.IsNullOrEmpty(lockTaken))
        return Ok(new { code = 200, msg = "已支付,请勿重复提交" });

    await _cache.SetStringAsync(lockKey, "1", TimeSpan.FromMinutes(5));

    try
    {
        // 1. 查询订单是否已处理
        var order = await _db.Orders.FirstOrDefaultAsync(o => o.OrderNo == dto.OrderNo);
        if (order?.Status == PayStatus.Success)
            return Ok(new { code = 200, msg = "成功" });

        // 2. 执行业务
        await DoPay(dto);

        // 3. 更新状态(关键!)
        order.Status = PayStatus.Success;
        await _db.SaveChangesAsync();

        return Ok(new { code = 200, msg = "支付成功" });
    }
    catch
    {
        // 失败释放锁,允许重试
        await _cache.RemoveAsync(lockKey);
        throw;
    }
}

 


 

三、三者区别

  表格  

名称 目的 范围
限流 保护服务器不被打垮 频率控制
防重 防止短时间重复点击 请求唯一 ID
幂等性 保证多次调用结果一致 业务唯一键

 


 

四、实践

 

  1. 防重 + 幂等一起用
    • 防重:前端快速重复点击
    • 幂等:网络重试、回调重试、消息重试

     

  2. 限流放在网关层最好(Kong / YARP)
  3. 幂等必须依赖数据库状态   不能只靠缓存,缓存丢了会出问题
  4. POST/PUT/DELETE 必须幂等
  5. 防重时间 5~10 秒,幂等时间 5~30 分钟

 


 

总结

 

  • 限流:保护服务,控制请求频率
  • 防重:防止重复提交,依赖 RequestId
  • 幂等性:保证结果一致,依赖 Token + 状态机
  • 三者组合使用,是企业级 API 安全标准方案

文章摘自:https://www.cnblogs.com/chuansheng/p/19916164