欢迎光临
我们一直在努力

Spring Boot高并发必学:接口幂等性设计与防重复提交实战(Redis+注解)

背景

在订单创建、支付等接口中,用户可能因网络延迟重复点击按钮,导致后端收到多次相同请求,产生多条重复订单。幂等性 指同一个请求无论执行多少次,对资源状态的影响都相同。本文将教你基于 Redis + 自定义注解 优雅实现防重复提交。

实现原理

客户端请求前先向服务端申请一个 唯一 token(或前端生成 UUID)

请求时将 token 放入 Header

服务端接收到请求,检查 Redis 中是否存在该 token

如果存在,则删除 token 并执行业务逻辑;如果不存在,则说明是重复请求,直接拒绝

代码实现

  • 自定义注解 @Idempotent
  • import java.lang.annotation.*;

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Idempotent {
    String value() default "idempotent_token";
    long expire() default 60; // token有效期(秒)
    }

  • 生成 Token 的接口
  • @RestController
    public class TokenController {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/token")
    public String getToken() {
    String token = UUID.randomUUID().toString();
    // 存储到Redis,设置过期时间60秒
    redisTemplate.opsForValue().set(token, "1", 60, TimeUnit.SECONDS);
    return token;
    }
    }

  • 拦截器(或AOP)处理幂等性校验
  • @Component
    public class IdempotentInterceptor implements HandlerInterceptor {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    if (!(handler instanceof HandlerMethod)) {
    return true;
    }
    HandlerMethod handlerMethod = (HandlerMethod) handler;
    Idempotent idempotent = handlerMethod.getMethodAnnotation(Idempotent.class);
    if (idempotent == null) {
    return true;
    }
    String token = request.getHeader("idempotent-token");
    if (StringUtils.isEmpty(token)) {
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().write("{\\"code\\":400,\\"msg\\":\\"缺少幂等token\\"}");
    return false;
    }
    // 使用Redis的原子操作:删除成功则说明token有效且未被使用
    Boolean deleted = redisTemplate.delete(token);
    if (Boolean.TRUE.equals(deleted)) {
    return true; // 首次请求,继续执行
    } else {
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().write("{\\"code\\":409,\\"msg\\":\\"重复请求,请勿重试\\"}");
    return false;
    }
    }
    }

  • 注册拦截器
  • @Configuration
    public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private IdempotentInterceptor idempotentInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(idempotentInterceptor).addPathPatterns("/**");
    }
    }

  • 在需要防重复的接口上使用注解
  • @PostMapping("/order")
    @Idempotent(expire = 120)
    public Result createOrder(@RequestBody OrderVo orderVo) {
    // 业务逻辑(只有第一次请求能到达这里)
    orderService.create(orderVo);
    return Result.success();
    }

    测试流程

    前端先调用 GET /token 获取 token

    调用 POST /order 时,Header 带上 idempotent-token: xxx

    第一次请求成功,Redis 中的 token 被删除

    第二次相同 token 请求,拦截器返回“重复请求”

    进阶优化

    使用 Lua 脚本确保“判断存在并删除”的原子性(但 redisTemplate.delete 本身是原子的)

    支持 token 有效期与业务时间匹配:注解中设置 expire 时间应大于业务最大处理时间

    分布式环境:完全适用,因为 Redis 是共享的

    总结

    通过注解 + Redis 的方式,我们几乎零侵入地实现了接口幂等性。这种方式比前端禁用按钮更可靠,因为后端从根本上杜绝了重复提交。生产环境中还可以结合 @Around AOP 来实现更灵活的控制。

    赞(0)
    未经允许不得转载:171主机测评 » Spring Boot高并发必学:接口幂等性设计与防重复提交实战(Redis+注解)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址