0
点赞
收藏
分享

微信扫一扫

用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

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

(2)用JMeter测试

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

用Redis来实现接口幂等性校验_数据库_02

配置添加请求接口

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

添加请求头里的key和value

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

(3)分析结果

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

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

代码下载

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

参考

SpringBoot + Redis + 注解 + 拦截器来实现接口幂等性校验:​​SpringBoot + Redis + 注解 + 拦截器来实现接口幂等性校验​​

JMeter的使用:​​Jmeter的简单使用_CBeann的博客​​

举报

相关推荐

0 条评论