赞
踩
如何防止接口被重复提交?
接口幂等性就是用户对同一操作发起的一次请求或多次请求的结果是一致的,不会因为对此提交而重复执行或出现其他问题。 例:在支付的时候,如果用户以为没支付成功(实际成功了),再次点击按钮导致被扣了两次钱,这是无法接收的问题,所以这个给问题是一定要解决的。本文使用的是AOP切片来解决这个问题。
再后端通过自定义注解,将这个注解作为切点,再在需要防幂等接口上添加注解,在执行方法之前在切片当中进行判断是否是重复提交。这样减少了和业务的耦合。具体的是在切点当中获取用户的token、user_id、url用于作为redis的唯一key,因为redis有自动过期机制,所以只要给这个key设置过期时间,就可以让这个用户在这个key还没过期之前无法重复调用这个接口。
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- @Target(ElementType.METHOD) // 注解只能用于方法
- @Retention(RetentionPolicy.RUNTIME) // 修饰注解的生命周期
- @Documented
- public @interface RepeatSubmit {
- /**
- * 防重复操作过期时间,默认1s
- */
- long expireTime() default 1;
- }
- @Slf4j
- @Component
- @Aspect
- public class RepeatSubmitAspect {
- @Autowired
- private RedisTemplate redisTemplate;
- /**
- * 定义切点
- */
- @Pointcut("@annotation(com.example.demo.apo.RepeatSubmit)")//这里填写自己那个注解的路径
- public void repeatSubmit() {}
-
- @Around("repeatSubmit()")
- public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
-
- ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
- .getRequestAttributes();
- HttpServletRequest request = attributes.getRequest();
- Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
- // 获取防重复提交注解
- RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
- // 获取token当做key,这里是新后端项目获取不到哈,先写死
- // String token = request.getHeader("Authorization");
- String tokenKey = "hhhhhhh,nihao";
- if (StringUtils.isBlank(tokenKey)) {
- throw new RuntimeException("token不存在,请登录!");
- }
- String url = request.getRequestURI();
- /**
- * 通过前缀 + url + token 来生成redis上的 key
- * 可以在加上用户id,这里没办法获取,大家可以在项目中加上
- */
- String redisKey = "repeat_submit_key:"
- .concat(url)
- .concat(tokenKey);
- log.info("==========redisKey ====== {}",redisKey);
-
- if (!redisTemplate.hasKey(redisKey)) {
- redisTemplate.opsForValue().set(redisKey, redisKey, annotation.expireTime(), TimeUnit.SECONDS);
- try {
- //正常执行方法并返回
- return joinPoint.proceed();
- } catch (Throwable throwable) {
- redisTemplate.delete(redisKey);
- throw new Throwable(throwable);
- }
- } else {
- // 抛出异常
- throw new Throwable("请勿重复提交");
- }
- }
- }
-
- @RepeatSubmit(expireTime = 10)//为了方便展示不能重复提交的功能这里将过期时间设置为10s
- @GetMapping("/open/logins")
- public ResultUtil logins(UserParam userParam){
- String login = iUserService.login(userParam);
- return ResultUtil.success(login);
- }
第一次:
第二次
10s后:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。