mcpField
I
idempotency

idempotency

github.com/yanko-belov开发1336 次安装40 周前更新
代码仓库

关于 idempotency

面向编码 Agent 的后端幂等性实践技能,介绍如何使用幂等键保护关键变更,使请求重试不会重复产生副作用。

主要功能

  1. 01幂等键模式
  2. 02键的生成与存储指导
  3. 03重复风险识别
  4. 04重试时返回缓存响应

适用场景

  1. 01防止支付端点重复扣款
  2. 02避免重复创建订单或账户
  3. 03检查依赖前端防重的 API

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/yanko-belov/code-craft --skill idempotency

安装后在 Claude Code 中输入/idempotency确认已加载。

详细介绍

幂等性

概述

关键操作必须支持安全重试。请使用幂等键。

网络会故障。客户端会重试。用户会重复点击。如果没有幂等性,重试就会导致重复扣款、重复创建订单或数据损坏。

适用场景

  • 支付处理端点
  • 创建订单
  • 任何不应重复执行的操作
  • 被要求“相信前端”能防止重复操作

铁律

NEVER rely on frontend to prevent duplicate requests.

没有例外:

  • 不能因为“前端会禁用按钮”
  • 不能因为“我们会显示加载状态”
  • 不能因为“这种情况很少发生”
  • 不能因为“用户不会重复点击”

检测:重复风险信号

如果变更操作没有防重复保护,请立即停止:

// ❌ VIOLATION: No idempotency protection
app.post('/payments', async (req, res) => {
  const { userId, amount, cardToken } = req.body;
  
  // If this request retries, user gets charged twice!
  const payment = await stripeCharge(amount, cardToken);
  await db.payments.create({ userId, amount, stripeId: payment.id });
  
  res.json({ success: true });
});

可能出现的问题:

  • 网络超时 → 客户端重试 → 重复扣款
  • 用户重复点击 → 两个请求 → 重复扣款
  • 移动应用的重试逻辑 → 多个请求

正确模式:幂等键

// ✅ CORRECT: Idempotency key protection

app.post('/payments', async (req, res) => {
  // Require idempotency key
  const idempotencyKey = req.headers['idempotency-key'];
  if (!idempotencyKey) {
    return res.status(400).json({ 
      error: 'Idempotency-Key header is required' 
    });
  }
  
  const { userId, amount, cardToken } = validated(req.body);
  
  // Check for existing request with this key
  const existing = await db.idempotencyKeys.findOne({
    where: { key: idempotencyKey, userId }
  });
  
  if (existing) {
    // Return cached response
    return res.status(existing.statusCode).json(existing.response);
  }
  
  try {
    // Process the payment
    const payment = await stripeCharge(amount, cardToken);
    await db.payments.create({ userId, amount, stripeId: payment.id });
    
    const response = { success: true, paymentId: payment.id };
    
    // Cache the response
    await db.idempotencyKeys.create({
      key: idempotencyKey,
      userId,
      statusCode: 200,
      response,
      expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours
    });
    
    res.json(response);
  } catch (error) {
    // Cache error responses too (optional, depends on error type)
    throw error;
  }
});

// Client usage:
// POST /payments
// Headers: { "Idempotency-Key": "user-123-order-456-attempt-1" }

幂等键设计

键生成(客户端)
// Option 1: UUID per request
const key = crypto.randomUUID();

// Option 2: Deterministic (better for retries)
const key = `${userId}-${orderId}-${timestamp}`;

// Option 3: Hash of request content
const key = hash(JSON.stringify({ userId, items, amount }));
键存储(服务端)
interface IdempotencyRecord {
  key: string;
  userId: string;
  statusCode: number;
  response: any;
  createdAt: Date;
  expiresAt: Date;  // Clean up old keys
}

哪些操作需要幂等性

操作风险解决方案
支付重复扣款幂等键
创建订单重复订单幂等键
减少库存扣减过量幂等键
发送邮件重复邮件幂等键
创建账户重复账户唯一约束 + 幂等键

抵御压力的准则

1. “前端会防止重复操作”

压力:“我们会禁用按钮、显示加载状态”

**回应:**网络会自动重试。JavaScript 可能崩溃。用户手速很快。

**行动:**在后端实现幂等性。前端用户体验不等于保护措施。

2. “这种情况很少发生”

压力:“重复操作只是罕见的边缘情况”

**回应:**罕见情况 × 大量用户 = 许多愤怒的用户。一次重复扣款就足以引发客服噩梦。

**行动:**保护所有关键变更操作。

3. “用户不会重复点击”

压力:“我们的用户很谨慎”

**回应:**用户的网络连接可能很慢。按钮可能很小。受挫时就会不断点击。

**行动:**绝不要依赖用户行为。

4. “数据库有唯一约束”

压力:“重复插入会失败”

**回应:**唯一约束会抛出错误。用户会看到错误。用户体验很糟糕。

**行动:**幂等性会返回相同的成功响应。

危险信号——立即停止并重新考虑

  • 支付端点没有幂等性
  • “前端会处理重复操作的防护”
  • 网络重试会造成副作用
  • 用户报告重复扣款
  • 不支持 Idempotency-Key 标头

以上任何一项都意味着:添加幂等性保护。

快速参考

不安全安全
信任前端要求提供幂等键
重复请求时报错返回缓存的响应
假设请求只会发送一次按支持重试的方式设计
POST 始终创建新资源POST + 键 = 最多执行一次

常见的自我开脱(都不成立)

借口事实
“前端会防止”网络会重试。用户会重复点击。
“很少发生”罕见情况 × 规模 = 许多事故。
“用户很谨慎”用户也是人。
“唯一约束”约束会抛出错误,而不是返回成功。
“太复杂”比处理客服工单简单。

总结

所有关键变更操作都必须要求幂等键。

永远不要相信前端保护。按幂等键缓存响应。对重复请求返回相同的响应。定期清理旧键。

评论

支持 Markdown