用Redis来实现接口幂等性校验

简介: 用Redis来实现接口幂等性校验

项目简介


●springboot

●redis

●@ApiIdempotentAnn注解 + 拦截器对请求进行拦截

压测工具: jmeter


实现思路


    为需要保证幂等性的每一次请求创建一个唯一标识token, 先获取token, 并将此token存入redis, 请求接口时, 将此token放到header或者作为请求参数请求接口, 后端接口判断redis中是否存在此token:


如果存在, 正常处理业务逻辑, 并从redis中删除此token, 那么, 如果是重复请求, 由于token已被删除, 则不能通过校验, 返回重复提交

如果不存在, 说明参数不合法或者是重复请求, 返回提示即可


请求流程


当页面加载的时候通过接口获取token


当访问接口时,会经过拦截器,如果发现该接口有自定义的幂等性注解,说明该接口需要验证幂等性(查看请求头里是否有key=token的值,如果有,并且删除成功,那么接口就访问成功,否则为重复提交);如果发现该接口没有自定义的幂等性注解,放行。


代码


pom依赖


添加redis依赖


<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>


自定义注解


即添加了该注解的接口要实现幂等性验证


@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiIdempotentAnn {
  boolean value() default true;
}


幂等性拦截器


package com.example.springbootdemointerfacemideng.intceptor;
import com.example.springbootdemointerfacemideng.annotation.ApiIdempotentAnn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;
/**
 * @author CBeann
 * @create 2020-07-04 18:06
 */
@Component
public class ApiIdempotentInceptor extends HandlerInterceptorAdapter {
  @Autowired private StringRedisTemplate stringRedisTemplate;
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    if (!(handler instanceof HandlerMethod)) {
      return true;
    }
    final HandlerMethod handlerMethod = (HandlerMethod) handler;
    final Method method = handlerMethod.getMethod();
    // 有这个注解
    boolean methodAnn = method.isAnnotationPresent(ApiIdempotentAnn.class);
    if (methodAnn && method.getAnnotation(ApiIdempotentAnn.class).value()) {
      // 需要实现接口幂等性
      boolean result = checkToken(request);
      if (result) {
        return super.preHandle(request, response, handler);
      } else {
        response.setContentType("application/json; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("重复调用");
        writer.close();
        response.flushBuffer();
        return false;
      }
    }
    return super.preHandle(request, response, handler);
  }
  @Override
  public void postHandle(
      HttpServletRequest request,
      HttpServletResponse response,
      Object handler,
      ModelAndView modelAndView)
      throws Exception {
    super.postHandle(request, response, handler, modelAndView);
  }
  private boolean checkToken(HttpServletRequest request) {
    String token = request.getHeader("token");
    if (null == token || "".equals(token)) {
      // 没有token,说明重复调用或者
      return false;
    }
    // 返回是否删除成功
    return stringRedisTemplate.delete(token);
  }
}


MVC配置文件


/**
 * @author chaird
 * @create 2020-09-23 16:13
 */
@Configuration
public class MVCConfig extends WebMvcConfigurerAdapter {
  @Autowired private ApiIdempotentInceptor apiIdempotentInceptor;
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    // 获取http请求拦截器
    registry.addInterceptor(apiIdempotentInceptor).addPathPatterns("/*");
  }
}


接口层


package com.example.springbootdemointerfacemideng.controller;
import com.example.springbootdemointerfacemideng.annotation.ApiIdempotentAnn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * @author chaird
 * @create 2020-09-23 15:47
 */
@RestController
public class ApiController {
  AtomicInteger num = new AtomicInteger(100);
  @Autowired private StringRedisTemplate stringRedisTemplate;
  /**
   * 前端获取token,然后把该token放入请求的header中
   *
   * @return
   */
  @GetMapping("/getToken")
  public String getToken() {
    String token = UUID.randomUUID().toString().substring(1, 9);
    stringRedisTemplate.opsForValue().set(token, "1");
    return token;
  }
  /**
   * 主业务逻辑,num--,并且加了自定义接口
   *
   * @return
   */
  @GetMapping("/submit")
  @ApiIdempotentAnn
  public String rushB() {
    // num--
    num.decrementAndGet();
    return "success";
  }
  /**
   * 查看num的值
   *
   * @return
   */
  @GetMapping("/getNum")
  public String getNum() {
    return String.valueOf(num.get());
  }
}


测试


(1)首先调用http://localhost:8080/getToken  获取token


6.png


(2)用JMeter测试


配置一百个线程在1秒内访问


7.png


8.png


9.png


(3)分析结果


10.png


说明只成功调用了一次http://localhost:8080/submit 接口


代码下载


Demooo/springboot-demo-interface-mideng at master · cbeann/Demooo · GitHub

目录
相关文章
|
NoSQL Java Redis
SpringBoot集成Redis解决表单重复提交接口幂等(亲测可用)
SpringBoot集成Redis解决表单重复提交接口幂等(亲测可用)
1089 0
|
NoSQL 关系型数据库 MySQL
当Redis与MySQL数据一致性校验中Redis数据量小于MySQL时的全量查询处理方法
保持Redis和MySQL之间的数据一致性是一个需要细致规划和持续维护的过程。通过全量数据同步、建立增量更新机制,以及定期执行数据一致性校验,可以有效地管理和维护两者之间的数据一致性。此外,利用现代化的数据同步工具可以进一步提高效率和可靠性。
284 6
|
存储 NoSQL Java
Spring Boot项目中使用Redis实现接口幂等性的方案
通过上述方法,可以有效地在Spring Boot项目中利用Redis实现接口幂等性,既保证了接口操作的安全性,又提高了系统的可靠性。
854 1
|
NoSQL Redis
简单5步实现接口限流 Redis
简单5步实现接口限流 Redis
|
NoSQL Redis
Redis企业项目实战--登录校验拦截器
Redis企业项目实战--登录校验拦截器
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
缓存 NoSQL Java
Redis+Caffeine构建高性能二级缓存
大家好,我是摘星。今天为大家带来的是Redis+Caffeine构建高性能二级缓存,废话不多说直接开始~
1768 0
|
10月前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。
912 25
|
11月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
409 1
Redis专题-实战篇二-商户查询缓存