文章总结: 本文阐述了接口幂等性定义及其防止重复扣款等副作用的重要性。对比五种方案,重点推荐基于RedisSETNX的Token机制,并辅以唯一ID与乐观锁策略。提供了Java代码实战,演示通过获取Token与Redis原子操作确保请求仅处理一次,有效保证数据一致性。 综合评分: 89 文章分类: 安全开发,解决方案,应用安全
接口幂等性实现方案
原创
静观云起
码云精炼
2026年1月2日 14:52 广东
一 幂等定义
*幂等性指的是对同一个接口发起一次或多次请求,得到的业务结果是一致的,不会因为重复调用而导致副作用(比如重复扣款、重复创建订单等)。*
二 幂等实现方案
| 方案 | 适用场景 | 实现要点 |
| — | — | — |
| Token机制(推荐) | 前端先请求获取一个唯一Token,再提交业务请求,并带上该Token | 利用Redis的SETNX或分布式锁,保证同一个Token只处理一次 |
| 唯一业务ID(如订单号、交易流水号) | 业务本身有唯一标识,如订单ID、支付流水 | 通过数据库唯一索引/去重表防止重复插入操作 |
| 乐观锁 | 更新类操作,如库存扣减、金额变更 | 通过版本号或条件更新(如 UPDATE ... WHERE version=?) |
| 状态机控制 | 有限状态流转的业务(如订单状态只能从”待支付”到”已支付”) | 校验当前状态是否允许操作,避免非法重复提交 |
| 请求去重表 | 所有请求记录落库,记录请求参数、状态 | 冗余高,一般配合唯一索引做防重 |
三 代码实战
以token机制实现方式为案例,采用java代码实现
- 定义请求参数对象BusinessRequest.java
/** * 请求参数实体类(用于接收JSON请求体) */@Data public class BusinessRequest { // 幂等 Token,客户端必须传 private String idempotentToken; // 示例字段:用户ID private String userId; // 示例字段:业务数据 private String data; }
2. 服务接口
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.web.bind.annotation.*;import java.util.UUID;import java.util.concurrent.TimeUnit;
@RestController@RequestMapping("/api")public class IdempotentController { // Redis 操作模板 @Autowired private StringRedisTemplate redisTemplate;
/** * 获取幂等Token通常由服务端生成,也可以让客户端生成如UUID * 客户端拿到这个Token后,在后续请求时必须原样传回来 */ @GetMapping("/token") public String getToken() { // 生成唯一Token,可以使用UUID,也可以结合用户信息、 // 业务ID等生成更精准的 key String token = UUID.randomUUID().toString(); // 返回给客户端,客户端后续请求要带上它 return token; }
/** * 业务接口示例:比如创建订单、支付等需要幂等性的操作 * 客户端必须在请求体中传入之前获取到的幂等Token */ @PostMapping("/business") public String handleBusiness( @RequestBody BusinessRequest request) { // 从请求体中获取幂等 Token String idempotentToken = request.getIdempotentToken(); if (idempotentToken == null || idempotentToken.trim().isEmpty()) { return "request parameter idempotentToken empty"; } // 构造Redis Key,一般格式:idempotent:{token} String redisKey = "idempotent:" + idempotentToken; /** * 使用Redis的setIfAbsent现幂等控制(SETNX效果) * 如果key不存在,则设置它,并返回true;如果已存在, * 返回false */ Boolean isFirstRequest = redisTemplate.opsForValue() .setIfAbsent( // key redisKey, // value(可以是任意值,表示已处理) "1", // 过期时间 1, // 单位:1小时(根据业务可调整,比如24小时) TimeUnit.HOURS ); // 如果不是第一次请求(token已存在),直接拒绝,防止重复操作 if (isFirstRequest == null || !isFirstRequest) { return "not repeate commit"; } //只有第一次请求才会走到这里,执行实际的业务逻辑 String result = doBusinessLogic(request); return result; } /** * 模拟实际的业务处理方法,比如保存订单、调用支付接口等 * 注意:这里应该包含事务、异常处理等逻辑,确保业务本身的正确性 */ private String doBusinessLogic(BusinessRequest request) { /** * 此处编写你的核心业务代码,比如: * 1. 创建订单 * 2. 扣减库存 * 3. 调用第三方支付 * 示例返回 */ return request.getUserId() + " handle success" ; } }
3. 测试
<1>获取幂等Token
GET http://192.168.93.20/api/token
响应结果:550e8400-e29b-41d4-a716-446655440000
客户端拿到这个Token,保存好,在下一步业务请求时必须原样传回去
<2>提交业务请求带幂等Token
POST http://192.168.93.20/api/businessContent-Type: application/json{ "idempotentToken": "550e8400-e29b-41d4-a716-446655440000", "userId": "user_001", "data": "购买VIP会员"}
首次响应:
user_001 handle success
<3>重复请求相同Token后的响应
not repeate commit
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:码云精炼 静观云起《接口幂等性实现方案》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论