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

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

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

代码
自定义注解

importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/***@ClassNameNoRepeatSubmit*@Description这里描述*@Authoradmin*@Date2021/3/216:16*/@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceNoRepeatSubmit{/***设置请求锁定时间**@return*/intlockTime()default10;}

AOP

packagecom.hongkun.aop;/***@ClassNameRepeatSubmitAspect*@Description这里描述*@Authoradmin*@Date2021/3/216:15*/importcom.hongkun.until.ApiResult;importcom.hongkun.until.Result;importcom.hongkun.until.RedisLock;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Pointcut;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importorg.springframework.util.Assert;importorg.springframework.web.context.request.RequestAttributes;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importjavax.servlet.http.HttpServletRequest;importjava.util.UUID;importjava.util.concurrent.TimeUnit;/***@authorliucheng*@since2020/01/15*防止接口重复提交*/@Aspect@ComponentpublicclassRepeatSubmitAspect{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(RepeatSubmitAspect.class);@AutowiredprivateRedisLockredisLock;@Pointcut("@annotation(noRepeatSubmit)")publicvoidpointCut(NoRepeatSubmitnoRepeatSubmit){}@Around("pointCut(noRepeatSubmit)")publicObjectaround(ProceedingJoinPointpjp,NoRepeatSubmitnoRepeatSubmit)throwsThrowable{intlockSeconds=noRepeatSubmit.lockTime();RequestAttributesra=RequestContextHolder.getRequestAttributes();ServletRequestAttributessra=(ServletRequestAttributes)ra;HttpServletRequestrequest=sra.getRequest();Assert.notNull(request,"requestcannotnull");//此处可以用token或者JSessionIdStringtoken=request.getHeader("token");Stringpath=request.getServletPath();Stringkey=getKey(token,path);StringclientId=getClientId();booleanisSuccess=redisLock.lock(key,clientId,lockSeconds,TimeUnit.SECONDS);LOGGER.info("tryLockkey=[{}],clientId=[{}]",key,clientId);if(isSuccess){LOGGER.info("tryLocksuccess,key=[{}],clientId=[{}]",key,clientId);//获取锁成功Objectresult;try{//执行进程result=pjp.proceed();}finally{//解锁redisLock.unlock(key,clientId);LOGGER.info("releaseLocksuccess,key=[{}],clientId=[{}]",key,clientId);}returnresult;}else{//获取锁失败,认为是重复提交的请求LOGGER.info("tryLockfail,key=[{}]",key);returnApiResult.success(200,"重复请求,请稍后再试",null);}}privateStringgetKey(Stringtoken,Stringpath){return"00000"+":"+token+path;}privateStringgetClientId(){returnUUID.randomUUID().toString();}}

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

发布于 2022-03-29 22:27:02
收藏
分享
海报
0 条评论
28
上一篇:Springboot怎么整合redis实现简单的数据写入和读取 下一篇:SpringBoot整合Redis的方法
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码