You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot POST接口如何基于Idempotency-key实现幂等校验

SpringBoot POST接口Idempotency-key幂等实现方案

单纯靠@Cacheable默认逻辑无法完全匹配你的需求:@Cacheable默认命中缓存后会直接返回缓存的接口响应值,而你需要命中重复幂等键时返回409 Conflict状态码,且要求仅业务记录创建成功后才写入缓存,因此需要补充少量自定义逻辑,以下是可直接落地的实现方式:


核心逻辑对齐

先明确和需求匹配的执行流:

  1. 请求进入时先从请求头提取Idempotency-key
  2. 用该key查缓存,存在对应记录直接返回409
  3. 缓存不存在则执行业务创建逻辑
  4. 业务执行成功(无异常)才将key和响应结果写入缓存,设置合理过期时间
  5. 业务执行失败则不写缓存,允许同key重试

方案1:自定义AOP切面实现(推荐,无并发穿透问题)

该方案灵活度最高,可精准控制全流程逻辑,兼容本地缓存、分布式缓存所有场景。

步骤1:基础配置

  • 项目引入Spring Cache依赖,启动类添加@EnableCaching注解
  • 生产环境建议替换默认内存缓存为Caffeine(本地缓存)或Redis(分布式缓存),支持自定义过期时间
  • 缓存key统一规则:idempotency:{接口标识}:{幂等键值},避免跨接口key冲突

步骤2:编写幂等键提取工具类

public class IdempotencyUtil {
    public static String getCurrentRequestKey() {
        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attrs == null) return null;
        HttpServletRequest request = attrs.getRequest();
        // 请求头名称大小写不敏感,直接读取即可
        return request.getHeader("Idempotency-key");
    }
}

步骤3:自定义幂等标记注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // 缓存过期时间,单位秒,默认5分钟
    long expireSeconds() default 300;
}

步骤4:编写切面处理核心逻辑

@Aspect
@Component
public class IdempotentAspect {
    private static final String CACHE_NAME = "idempotency_cache";
    @Autowired
    private CacheManager cacheManager;

    @Around("@annotation(idempotentConfig)")
    public Object handleIdempotency(ProceedingJoinPoint joinPoint, Idempotent idempotentConfig) throws Throwable {
        String idempotencyKey = IdempotencyUtil.getCurrentRequestKey();
        // 按需添加校验:幂等键为空直接返回400错误
        Assert.hasText(idempotencyKey, "Idempotency-key不能为空");

        Cache cache = cacheManager.getCache(CACHE_NAME);
        Assert.notNull(cache, "幂等缓存初始化失败");

        // 第一步:校验重复请求
        Cache.ValueWrapper existRecord = cache.get(idempotencyKey);
        if (existRecord != null) {
            throw new ResponseStatusException(HttpStatus.CONFLICT, "重复请求,请勿重复提交");
        }

        // 第二步:执行业务逻辑
        Object response;
        try {
            response = joinPoint.proceed();
        } catch (Exception e) {
            // 业务执行抛异常,不写入缓存,允许同key重试
            throw e;
        }

        // 第三步:业务执行成功才写入缓存
        cache.put(idempotencyKey, response);
        // 若使用Redis缓存,可在此处额外调用expire命令设置自定义过期时间
        return response;
    }
}

高并发场景下如果用Redis做缓存,可将查询和写入逻辑替换为RedisTemplate#opsForValue().setIfAbsent()原子操作,完全避免并发请求穿透到业务层。

步骤5:接口上添加注解即可生效

@PostMapping("/project/createStudent")
@Idempotent(expireSeconds = 600) // 该接口幂等缓存10分钟过期
public StudentVO createStudent(@RequestBody CreateStudentDTO dto) {
    // 原有业务逻辑:参数校验、创建学生记录、组装返回值
    return studentService.create(dto);
}

方案2:基于@Cacheable的轻量实现(适合低并发场景)

如果不想写自定义切面,也可以通过@Cacheable的SpEL表达式直接读取请求头的幂等键作为缓存key,结合前置校验实现需求:

  1. 先在Controller中注入CacheManager,编写前置校验方法
@Autowired
private CacheManager cacheManager;

private void checkDuplicateRequest(String key) {
    Cache cache = cacheManager.getCache("idempotency_cache");
    if (cache != null && cache.get(key) != null) {
        throw new ResponseStatusException(HttpStatus.CONFLICT, "重复请求");
    }
}
  1. 接口上配置@Cacheable,指定key为请求头中的幂等键,通过unless参数控制仅成功响应才写入缓存
@PostMapping("/project/createStudent")
@Cacheable(
    cacheNames = "idempotency_cache",
    key = "#request.getHeader('Idempotency-key')",
    unless = "#result == null" // 可根据自身业务返回结构调整,比如返回码非200时不存缓存
)
public StudentVO createStudent(@RequestBody CreateStudentDTO dto, HttpServletRequest request) {
    // 前置校验重复请求
    String key = request.getHeader("Idempotency-key");
    checkDuplicateRequest(key);
    // 原有业务逻辑
    return studentService.create(dto);
}

注意:该方案存在并发穿透风险,高并发下同个幂等键的多个请求可能同时通过前置校验进入业务层,仅适合并发量低于100QPS的内部接口使用。


注意事项

  • 幂等缓存必须设置过期时间,避免无效key长期占用缓存空间,过期时间根据业务场景设置为5-15分钟即可,覆盖前端重试、网络超时的典型时间窗口
  • 客户端生成的幂等键需保证全局唯一,推荐使用UUID v4格式,不要用业务字段作为幂等键
  • 分布式部署场景必须用Redis等集中式缓存做幂等存储,本地缓存会导致多节点间校验不生效

内容的提问来源于stack exchange,提问作者user3462941

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 18:27:33