一、设计思路
表格
| 特性 | 作用 | 实现方式 |
|---|---|---|
| 限流 | 防止接口被刷、防止流量洪峰 | 令牌桶 / 滑动窗口 + 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 次,结果完全一样,不会产生脏数据。 适用场景:
- 支付
- 订单创建
- 退款
- 表单提交
- 回调接口
实现方案
步骤
- 前端获取 token(幂等号)
- 提交时携带 token
- 后端使用 分布式锁 + 状态机 保证只执行一次
代码实现
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 |
| 幂等性 | 保证多次调用结果一致 | 业务唯一键 |
四、实践
- 防重 + 幂等一起用
- 防重:前端快速重复点击
- 幂等:网络重试、回调重试、消息重试
- 限流放在网关层最好(Kong / YARP)
- 幂等必须依赖数据库状态 不能只靠缓存,缓存丢了会出问题
- POST/PUT/DELETE 必须幂等
- 防重时间 5~10 秒,幂等时间 5~30 分钟
总结
- 限流:保护服务,控制请求频率
- 防重:防止重复提交,依赖 RequestId
- 幂等性:保证结果一致,依赖 Token + 状态机
- 三者组合使用,是企业级 API 安全标准方案
文章摘自:https://www.cnblogs.com/chuansheng/p/19916164
