日常开发项目中,经常会要求部分接口做防刷、防重复提交的拦截,比如:获取验证码(前端亦可加入倒计时实现,60秒不能再次获取),下单接口 (网络原因或其他什么原因,有时候点击下单支付,感觉没反应 连点了n次, 导致几秒内相同一个东西支付下单了多笔,被银行风控的情况)等等,所以有些接口我们有必要做防重复提交的拦截,今天就来简单聊聊这个话题.

首当其冲肯定是先引入AOP依赖,maven为例 pom.xml

<!-- aop依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- JSON依赖 -->
<dependency><groupId>net.sf.json-lib</groupId><artifactId>json-lib</artifactId><version>2.4</version><classifier>jdk15</classifier>
</dependency>
<!-- redis依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

有了AOP的支持 接下来我们进行自定义注解 NoRepeatSubmit

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)//作用于方法上
@Retention(RetentionPolicy.RUNTIME)//运行时
public @interface NoRepeatSubmit {/*** 设置请求锁定时间  默认5秒*/int lockTime() default 5000;/*** 当发生重复提交时候默认返回的错误信息*/String errMsg() default "重复提交,请 second 秒后重试";
}
RepeatSubmitAspect切面
package com.karo.unicorn.aspect;import com.karo.unicorn.annotation.NoRepeatSubmit;
import com.karo.unicorn.common.result.ResultAPI;
import com.karo.unicorn.utils.ObjectUtil;
import com.karo.unicorn.utils.http.HttpContextUtils;
import com.karo.unicorn.utils.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;/*** @ClassName RepeatSubmitAspect* @Description TODO :* @Author :Panguaxe* @Date 2020-05-26 16:08* @Version V1.0*/
@Slf4j
@Aspect
@Component
public class RepeatSubmitAspect {@Autowiredprivate RedisTemplate redisTemplate;@Pointcut("@annotation(noRepeatSubmit)")public void pointcut(NoRepeatSubmit noRepeatSubmit) {}@Around("@annotation(noRepeatSubmit)")public Object around(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) throws Throwable {log.warn("AOP防重复提交设置的加锁时间:{}", noRepeatSubmit.lockTime());HttpServletRequest request = HttpContextUtils.getHttpServletRequest();// 请求的方法参数值  POST的JSON请求request.getParameter()是获取不到值的Object[] args = joinPoint.getArgs();JSONObject requestParams = ObjectUtil.isNotBlank(args) ? JSONObject.fromObject(args[0]) : null;log.warn("AOP防重复提交[" + request.getRequestURI() + "]接口的请求参数:{}",requestParams);// 此处可以用token或者JSessionId  TODO 或者使用用户ID作为标识//String token = request.getHeader("Authorization");//根据自己业务替换为你的唯一标识//String token = request.getHeader("token");//根据自己业务替换为你的唯一标识String key = JSONUtil.getValNetsf(requestParams, "userId") + request.getServletPath();log.warn("AOP防重复提交,加锁Key[" + key + "]:{}",requestParams);boolean isSuccess = tryLock(key, noRepeatSubmit.lockTime());log.warn("AOP防重复提交,是否放行:{}",isSuccess ? "放行" : "拦截重复提交");if (!isSuccess) {// 获取锁失败,认为是重复提交的请求return new ResultAPI().error(noRepeatSubmit.errMsg().replace("second",String.valueOf(noRepeatSubmit.lockTime()/1000)),"");}// 获取锁成功, 执行进程Object result;try {result = joinPoint.proceed();} finally {redisTemplate.delete(key);// 解锁}return result;}/*** @MethodName: tryLock* @Param: [key --- key值, lockSeconds  时长]* @Return: boolean       是否获取到* @Author: Panguaxe* @Date: 2020-05-26 16:13* @Description: TODO          最终加强分布式锁*/private boolean tryLock(String key, int lockSeconds) {//lambda表达式return (Boolean) redisTemplate.execute((RedisCallback) connection -> {long expireAt = System.currentTimeMillis() + lockSeconds + 1;log.warn("失效时间:{}", expireAt);Boolean acquire = connection.setNX(key.getBytes(), String.valueOf(expireAt).getBytes());if (acquire) {return true;}byte[] value = connection.get(key.getBytes());if (Objects.nonNull(value) && value.length > 0) {if (Long.parseLong(new String(value)) < System.currentTimeMillis()) {// 如果锁已经过期byte[] oldValue = connection.getSet(key.getBytes(),String.valueOf(System.currentTimeMillis() + lockSeconds + 1).getBytes());// 防止死锁return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();}}return false;});}
}

其中ResultAPI为统一返回结果

使用示例:

    //@Validated({Update.class,Create.class}) //也可以注释掉 此注解为个人测试自定义参数校验注解用@NoRepeatSubmit(lockTime = 3000)//3000即3秒内不能重复提交@ExceptionHandler(value = Exception.class)//此处可注释掉 统一异常处理@PostMapping("noRepeatSubmit")public APIResult noRepeatSubmit(@RequestBody @Validated({Update.class,Create.class})  UserInfo userInfo){log.warn("请求参数:{}", JSON.toJSONString(userInfo));APIResult result = new APIResult();try {result.success(userInfo);//因为仅测试防重复提交拦截  不做业务处理 直接请求参数返回 仅验证3秒内同一用户 同一接口不能重复提交}catch (Exception e){result.systemError(e.getMessage());}return result;}

Springboot AOP接口防刷、防重复提交相关推荐

  1. redis防表单重复提交

    参考链接: 防表单重复提交的四种方法:https://www.cnblogs.com/huanghuizhou/p/9153837.html 补充几点个人想法: 1. 对于前后端传递token验证的方 ...

  2. JavaWeb -- Struts1 使用示例: 表单校验 防表单重复提交 表单数据封装到实体

    1. struts 工作流程图 超链接 2. 入门案例 struts入门案例:1.写一个注册页面,把请求交给 struts处理<form action="${pageContext.r ...

  3. 基于拦截器实现防表单重复提交

    1.定义自定义注解 2.定义防重复提交拦截器 /*** 防止重复提交拦截器** */ @Component public abstract class RepeatSubmitInterceptor ...

  4. 基于springboot2.x+redis的接口防刷(防DOSS攻击)

    参考文章:https://blog.csdn.net/qq_17635843/article/details/78990881 自定义一个拦截器集成HandlerInterceptorAdapter里 ...

  5. SpringMVC中实现的token,防表单重复提交

    一:首先创建一个token处理类  ,这里的类名叫 TokenHandler private static Logger logger = Logger.getLogger(TokenHandler. ...

  6. Spring Boot中防表单重复提交以及拦截器登录检测

    目录 理论 演示 源码 理论 在用户登录后,如果按F5刷新会出现表单重复提交的问题,解决这个问题后,如果没有拦截器登录检测,就会造成,任意用户可以登录后台界面,所以要有拦截器登录检测. 相关的逻辑步骤 ...

  7. 如何处理接口幂等性问题(重复提交)

    接口幂等:多次请求,结果一致. 同样的请求参数,多次去访问同一个接口,得到的结果是一致的.且服务端(针对于数据入库或数据修改)只处理一次.通俗点讲就是:防止重复提交. 以下演示相关案例 案例1: 数据 ...

  8. java后端接口防止表单重复提交

    导入依赖 <!-- aspectj --><dependency><groupId>org.aspectj</groupId><artifactI ...

  9. 后台利用aop注解的方式防止重复提交

    1.建立标签:防止重复提交 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Avoi ...

  10. Spring Boot学习总结(21)——SpringBoot集成Redis等缓存以注解的方式优雅实现幂等,防千万次重复提交实例代码

    前言 在实际的开发项目中,一个对外暴露的接口往往会面临很多次请求,我们来解释一下幂等的概念:任意多次执行所产生的影响均与一次执行的影响相同.按照这个含义,最终的含义就是 对数据库的影响只能是一次性的, ...

最新文章

  1. 深入理解JVM虚拟机(七):虚拟机字节码执行引擎
  2. M元上升子序列【树状数组+dp】
  3. 16 分频 32 分频是啥意思_Verilog 数字分频器的设计及验证
  4. “android:process“---由日志引发的大灾难
  5. python: 爬取[博海拾贝]图片脚本
  6. Redis中的键值过期操作
  7. php文件读取文件内容,PHP文件系统函数-读取文件内容几种方式
  8. Docker实践(五)docker部署MySQL5.7
  9. 三维激光LiDAR点云数据处理,我帮您!
  10. 关于jpa的mappedBy用法
  11. 快递100 物流公司编码
  12. Java程序员面试分类真题(后附答案解析)
  13. 在phpstudy中安装并使用ThinkPHP 5
  14. 超详细 WebPack 入门教程
  15. 如何查询一个域名的子域名
  16. Jetson Xavier NX小坑——配置cuDNN
  17. Lifecycle使用篇
  18. 华为智慧屏 鸿蒙,精挑细选的高品质大屏,新一代华为智慧屏V系列不要错过
  19. 可以双人玩的三子棋!
  20. 【SpringBoot应用篇】SpringBoot集成j2cache二级缓存框架

热门文章

  1. 东北虎“完达山1号”救护的中国速度!
  2. 《你可能不知道免像控的两个细节问题》
  3. CSDN博客专家申请成功
  4. CodeForces 68 A.Irrational problem(水~)
  5. debian下安装小企鹅输入法
  6. 高级程序员与初级程序员差别在哪里?
  7. Fvuln-自动化web漏洞检测工具
  8. 成功解决ImportError: cannot import name ‘_validate_lengths‘
  9. 今天买了正版的beyondcompare 4.0
  10. 计算机itunes无法安装,Win7电脑无法安装itunes怎么办 win7安装itunes失败的解决方法...