温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

SpringBoot怎么结合Aop+Redis防止接口重复提交

发布时间:2022-03-29 13:45:25 来源:亿速云 阅读:410 作者:iii 栏目:大数据

这篇“SpringBoot怎么结合Aop+Redis防止接口重复提交”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“SpringBoot怎么结合Aop+Redis防止接口重复提交”文章吧。

在实际的开发项目中,一个对外暴露的接口往往会面临很多次请求,我们来解释一下幂等的概念:任意多次执行所产生的影响均与一次执行的影响相同。按照这个含义,最终的含义就是 对数据库的影响只能是一次性的,不能重复处理。如何保证其幂等性,通常有以下手段:

1、数据库建立唯一性索引,可以保证最终插入数据库的只有一条数据。

2、token机制,每次接口请求前先获取一个token,然后再下次请求的时候在请求的header体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token。

3、悲观锁或者乐观锁,悲观锁可以保证每次for update的时候其他sql无法update数据(在数据库引擎是innodb的时候,select的条件必须是唯一索引,防止锁全表)

4、先查询后判断,首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行。

为什么要防止接口重复提交?
对于有些敏感操作接口,比如新增数据接口、付款接口,要是用户操作不当多次点击提交按钮,这些接口就会被多次请求,最后可能导致系统异常。

前端可以如何控制?
前端可以通过js进行控制,当用户点击提交按钮,
1.按钮设置多少秒内不可点击状态
2.按钮点击后弹出loading提示框,避免再次点击,直到接口请求返回后
3.按钮点击后跳转到新的页面

但是,请记住,永远不要相信用户的行为,因为你不知道用户会做哪些奇葩的操作,所以,最重要的还是要在后端处理。

使用aop+redis进行拦截处理
一.创建切面类RepeatSubmitAspect
实现过程:接口请求后,token+请求路径作为key值去redis中读取数据,若能找到这个key,则证明是重复提交的,反之不是。若不是重复提交,则直接放行,并将这个key写入redis中,并设置一定时间过期(我这里是设置的5s过期)


在传统的web项目中,为了防止重复提交,通常做法是:后端生成唯一的提交令牌(uuid),存储在服务端,页面在发起请求时,携带次令牌,后端验证请求后删除令牌,保证请求的唯一性。
但是,上诉的做法是需要前后端都需要进行改动,如果在项目初期,是可以实现的,但是,在项目的后期,很多功能都实现好了,不可能大范围的去改动。

思路
1.自定义注解@NoRepeatSubmit 标记所有Controller中提交的请求
2.通过AOP对所有标记了@NoRepeatSubmit 的方法进行拦截
3.在业务方法执行前,获取当前用户的token或者JSessionId+当前请求地址,作为一个唯一的key,去获取redis分布式锁,如果此时并发获取,只有一个线程能获取到。
4.业务执行后,释放锁

关于Redis分布式锁
使用Redis是为了在负载均衡部署,如果是单机的项目可以使用一个本地线程安全的Cache替代Redis

代码
自定义注解
 

import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /**  * @ClassName NoRepeatSubmit  * @Description 这里描述  * @Author admin  * @Date 2021/3/2 16:16  */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoRepeatSubmit {     /**      * 设置请求锁定时间      *      * @return      */     int lockTime() default 10; }

AOP

package com.hongkun.aop; /**  * @ClassName RepeatSubmitAspect  * @Description 这里描述  * @Author admin  * @Date 2021/3/2 16:15  */ import com.hongkun.until.ApiResult; import com.hongkun.until.Result; import com.hongkun.until.RedisLock; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.UUID; import java.util.concurrent.TimeUnit; /**  * @author liucheng  * @since 2020/01/15  * 防止接口重复提交  */ @Aspect @Component public class RepeatSubmitAspect {     private static final Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class);     @Autowired     private RedisLock redisLock;     @Pointcut("@annotation(noRepeatSubmit)")     public void pointCut(NoRepeatSubmit noRepeatSubmit) {     }     @Around("pointCut(noRepeatSubmit)")     public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {         int lockSeconds = noRepeatSubmit.lockTime();         RequestAttributes ra = RequestContextHolder.getRequestAttributes();         ServletRequestAttributes sra = (ServletRequestAttributes) ra;         HttpServletRequest request = sra.getRequest();         Assert.notNull(request, "request can not null");         // 此处可以用token或者JSessionId         String token = request.getHeader("token");         String path = request.getServletPath();         String key = getKey(token, path);         String clientId = getClientId();         boolean isSuccess = redisLock.lock(key, clientId, lockSeconds,TimeUnit.SECONDS);         LOGGER.info("tryLock key = [{}], clientId = [{}]", key, clientId);         if (isSuccess) {             LOGGER.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);             // 获取锁成功             Object result;             try {                 // 执行进程                 result = pjp.proceed();             } finally {                 // 解锁                 redisLock.unlock(key, clientId);                 LOGGER.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);             }             return result;         } else {             // 获取锁失败,认为是重复提交的请求             LOGGER.info("tryLock fail, key = [{}]", key);             return ApiResult.success(200, "重复请求,请稍后再试", null);         }     }     private String getKey(String token, String path) {         return "00000"+":"+token + path;     }     private String getClientId() {         return UUID.randomUUID().toString();     } }

以上就是关于“SpringBoot怎么结合Aop+Redis防止接口重复提交”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI