人征塔战
657.72MB · 2025-11-23
在 Java 中保证接口幂等性(即多次调用同一接口产生与单次调用相同的结果,不会引发副作用),需要结合业务场景选择合适的方案。以下是常见的实现方式及技术细节:
核心思想:为每次请求生成唯一标识(如订单号、请求 ID),服务端通过记录该标识是否已处理,避免重复执行。
数据库唯一约束:将唯一标识作为数据库表的唯一索引,重复请求会触发主键冲突异常,直接返回成功(或错误提示)。
java
运行
// 示例:订单表唯一索引(order_no)
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = "orderNo")})
public class Order {
@Id
private Long id;
private String orderNo; // 唯一订单号(作为幂等标识)
// 其他字段...
}
// 服务层处理
@Transactional
public Result createOrder(OrderDTO dto) {
try {
// 尝试插入订单(依赖数据库唯一约束)
Order order = new Order();
order.setOrderNo(dto.getOrderNo());
orderRepository.save(order);
// 执行后续业务(如扣减库存)
return Result.success();
} catch (DataIntegrityViolationException e) {
// 唯一约束冲突,说明已处理过
log.warn("订单已存在: {}", dto.getOrderNo());
return Result.success(); // 或返回已有结果
}
}
缓存记录(Redis) :利用 Redis 的SETNX(不存在则设置)特性,判断请求是否已处理。
java
运行
@Autowired
private StringRedisTemplate redisTemplate;
public Result processRequest(String requestId) {
// 尝试设置唯一标识,过期时间防止内存溢出
Boolean isFirst = redisTemplate.opsForValue().setIfAbsent(
"idempotent:" + requestId,
"processed",
1, TimeUnit.HOURS
);
if (Boolean.TRUE.equals(isFirst)) {
// 首次请求,执行业务逻辑
doBusiness();
return Result.success();
} else {
// 重复请求,返回已有结果
return Result.success("已处理");
}
}
核心思想:客户端先向服务端申请令牌,请求接口时携带令牌,服务端验证令牌有效性后处理业务,并标记令牌为已使用。
java
运行
// 生成令牌
public String generateToken() {
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("token:" + token, "valid", 30, TimeUnit.MINUTES);
return token;
}
// 校验令牌并处理业务
public Result doBusiness(String token, BusinessDTO dto) {
// 删除令牌(原子操作,确保唯一处理)
Boolean isValid = redisTemplate.delete("token:" + token);
if (Boolean.TRUE.equals(isValid)) {
// 令牌有效,执行业务
process(dto);
return Result.success();
} else {
// 令牌无效(已使用或过期)
return Result.fail("重复请求");
}
}
核心思想:适用于更新操作,通过版本号控制,确保只有版本匹配时才执行更新,避免重复更新。
java
运行
@Entity
public class Product {
@Id
private Long id;
private Integer stock; // 库存
private Integer version; // 版本号
}
@Transactional
public Result reduceStock(Long productId, Integer quantity) {
// 查询商品及当前版本
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("商品不存在"));
// 检查库存
if (product.getStock() < quantity) {
return Result.fail("库存不足");
}
// 乐观锁更新(where条件包含版本号)
int rows = productRepository.reduceStock(
productId,
quantity,
product.getVersion() // 当前版本
);
if (rows > 0) {
return Result.success();
} else {
// 版本不匹配,说明已被其他请求处理
return Result.fail("操作冲突,请重试");
}
}
// Repository层SQL(JPA示例)
@Modifying
@Query("UPDATE Product p SET p.stock = p.stock - :quantity, p.version = p.version + 1 " +
"WHERE p.id = :id AND p.version = :version")
int reduceStock(@Param("id") Long id,
@Param("quantity") Integer quantity,
@Param("version") Integer version);
核心思想:通过状态流转约束,确保接口只能在特定状态下执行,避免重复操作(如订单状态从 “待支付” 到 “已支付” 的单向流转)。
java
运行
public enum OrderStatus {
CREATED(1, "待支付"),
PAID(2, "已支付"),
CANCELLED(3, "已取消");
private int code;
private String desc;
// 构造器、getter...
}
@Transactional
public Result payOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new RuntimeException("订单不存在"));
// 仅允许“待支付”状态执行支付
if (order.getStatus() != OrderStatus.CREATED) {
log.warn("订单状态异常: {}", orderId);
return Result.success("订单已处理"); // 重复支付请求直接返回成功
}
// 执行支付逻辑(如调用支付网关)
boolean paySuccess = paymentGateway.pay(order);
if (paySuccess) {
order.setStatus(OrderStatus.PAID);
orderRepository.save(order);
return Result.success();
} else {
return Result.fail("支付失败");
}
}
核心思想:在分布式系统中,通过分布式锁(如 Redis、ZooKeeper)确保同一时间只有一个请求处理业务。
java
运行
// 基于Redis的分布式锁(使用Redisson)
@Autowired
private RedissonClient redissonClient;
public Result processDistributed(String key) {
RLock lock = redissonClient.getLock("lock:" + key);
try {
// 尝试获取锁,最多等待10秒,持有锁1分钟
boolean locked = lock.tryLock(10, 60, TimeUnit.SECONDS);
if (locked) {
// 检查是否已处理(双重校验)
if (isProcessed(key)) {
return Result.success("已处理");
}
// 执行业务
doBusiness();
markAsProcessed(key); // 标记为已处理
return Result.success();
} else {
// 获取锁失败,可能是重复请求
return Result.fail("操作繁忙,请重试");
}
} finally {
// 释放锁(仅释放自己持有的锁)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
需注意:幂等性设计需结合业务场景,避免过度设计;同时要处理异常情况(如网络超时),确保客户端重试时的正确性。