为需要保证幂等性的每一次请求创建一个唯一标识token, 先获取token, 并将此token存入redis, 请求接口时, 将此token放到header或者作为请求参数请求接口, 后端接口判断redis中是否存在此token:
当页面加载的时候通过接口获取token
当访问接口时,会经过拦截器,如果发现该接口有自定义的幂等性注解,说明该接口需要验证幂等性(查看请求头里是否有key=token的值,如果有,并且删除成功,那么接口就访问成功,否则为重复提交);如果发现该接口没有自定义的幂等性注解,放行。
添加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);
}
}
/**
* @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
(2)用JMeter测试
配置一百个线程在1秒内访问
配置添加请求接口
添加请求头里的key和value
(3)分析结果
说明只成功调用了一次http://localhost:8080/submit 接口
代码下载
Demooo/springboot-demo-interface-mideng at master · cbeann/Demooo · GitHub
SpringBoot + Redis + 注解 + 拦截器来实现接口幂等性校验:SpringBoot + Redis + 注解 + 拦截器来实现接口幂等性校验
JMeter的使用:Jmeter的简单使用_CBeann的博客-CSDN博客