一、幂等校验思路:
前端:请求时先获取唯一标识,然后带着唯一标识去请求业务接口
后端:唯一标识生成接口:生成唯一标识,存储redis,返回前端。
业务处理接口:在业务接口上添加自定义幂等校验注解,前端请求时,拦截器进行拦截判断该请求的方法上是否添加了幂等校验注解,如有进行校验,无放行。校验逻辑:判断redis是否存在,存在则删除该幂等标识(删除是为了防止并发问题)并放行,不存在说明是重复请求。
二、代码样例:
1、幂等校验注解
/*
* 在需要保证 接口幂等性 的Controller的方法上使用此注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
}
2、拦截器
@Component
//幂等性拦截,方法上是否添加幂等性校验注解
public class IdempotenceInterceptor implements HandlerInterceptor {
@Autowired
private IdempotentImpl idempotentImpl;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断是否是方法请求
if (!(handler instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod= (HandlerMethod)handler;
Method method = handlerMethod.getMethod();
Idempotent idempotent= method.getAnnotation(Idempotent.class);
if (null !=idempotent){
String idem= request.getHeader("idempotent");
if (StringUtils.isEmpty(idem)){
throw new GlobalException(ResponseCode.FAILUER_1.getCode(),"未携带幂等标识");
}
//幂等校验
idempotentImpl.checkToken(idem);
}
return true;
}
}
/*
* @Description
*/
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
@Autowired
private IdempotenceInterceptor idempotenceInterceptor;
/*
* 幂等性拦截器
* */
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(idempotenceInterceptor);
}
}
3、幂等标识生成校验
@Component
public class IdempotentImpl {
private static final String IDEMPOTENT = "idempotent";
@Resource
private RedisSessionTemplate redisSessionTemplate;
//创建一个唯一idempotent标识
public String createToken() {
String uuid = UUID.randomUUID().toString();
//存储redis,保留固定时间,超过失效
redisSessionTemplate.set(uuid, uuid, 60 * 30);
return uuid;
}
public void checkToken(String idempotent) {
//如果redis中不存在,说明该标识已请求处理
if (!redisSessionTemplate.exists(idempotent)) {
throw new GlobalException(ResponseCode.FAILUER_1.getCode(), "重复请求");
}
//防止并发问题,要判断删除是否成功,不成功说明已请求处理
Long del = redisSessionTemplate.del(idempotent);
if (del <= 0) {
throw new GlobalException(ResponseCode.FAILUER_1.getCode(), "重复请求");
}
}
}
4、业务请求接口加幂等注解
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private TestService testService;
@GetMapping("/selectTest")
@Idempotent()
public String selectTest(){
try {
return testService.SelectTest();
}catch (Exception ex){
throw new GlobalException(1001,"错误异常");
}
}
}