- 创建自定义注解
/**
* 防止接口重复提交
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoRepeatSubmit {
/**
* 超时时间 默认为10s
* @return
*/
long timeout() default 10;
}
- 引入aop依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 实现切面
@Slf4j
@Aspect
@Component
public class NoRepeatSubmitAop {
@Autowired
RedisTemplate redisTemplate;
/**
* api重复提交判断,没有考虑高并发情况
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("@annotation(com.example.activiti.annotation.NoRepeatSubmit)")
public Object around(ProceedingJoinPoint joinPoint) {
log.info("========api重复提交判断===========");
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("token");
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//获取自定义注解
NoRepeatSubmit annotation = methodSignature.getMethod().getAnnotation(NoRepeatSubmit.class);
long timeout = annotation.timeout();
//token+请求url为key
BoundValueOperations valueOps = redisTemplate.boundValueOps(token + request.getRequestURI());
if (valueOps.get() != null) {
throw new SubmitAgainException("请勿重复提交,请稍后再试!");
}
valueOps.set(1, timeout, TimeUnit.SECONDS);
Object proceed = null;
try {
proceed = joinPoint.proceed();
} catch (Throwable throwable) {
log.error("接口:{}调用异常,错误信息:{},参数信息:{}",request.getRequestURL(),throwable.getMessage(), JSONUtil.toJsonStr(request.getParameterMap()));
throw new RuntimeException("接口调用异常");
}finally {
redisTemplate.delete(token + request.getRequestURI());
log.info("========api重复提交判断结束,删除key===========");
}
return proceed;
}
}
实现原理:每次请求的时候会将用户token+请求的url作为key存入Redis,每次请求的时候去查询key是否存在,执行完成请求后会删除key
- 进行测试 (请求头部得携带用户token)
@NoRepeatSubmit
@ApiOperation(value = "测试接口")
@GetMapping("/success")
public JsonResult success() throws InterruptedException {
Thread.sleep(5000);
return JsonResult.success();
}
效果