redis分布式锁解决表单重复提交的问题
假如用户的网速慢,用户点击提交按钮,却因为网速慢,而没有跳转到新的页面,这时的用户会再次点击提交按钮,举个例子:用户点击订单页面,当点击提交按钮的时候,也许因为网速的原因,没有跳转到新的页面,这时的用户会再次点击提交按钮,如果没有经过处理的话,这时用户就会生成两份订单,类似于这种场景都叫重复提交。
使用redis的setnx和getset命令解决表单重复提交的问题。
1.引入redis依赖和aop依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>1.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2.编写加锁和解锁的方法。
/** * @author wangbin * @description redis分布式锁 * @date 2019年09月20日 */ @Component public class RedisLock { private final Logger logger = LoggerFactory.getLogger(RedisLock.class); @Autowired private StringRedisTemplate redisTemplate; /** * @author wangbin * @description 进行加锁的操作(该方法是单线程运行的) * @date 2019年09月20日 * @param key 某个方法请求url加上cookie中的用户身份使用md5加密生成 * @param value 当前时间+过期时间(10秒) * @return true表示加锁成功 false表示未获取到锁 */ public boolean lock(String key,String value){ //加锁成功返回true if(redisTemplate.opsForValue().setIfAbsent(key,value,10, TimeUnit.SECONDS)){ return true; } String currentValue = redisTemplate.opsForValue().get(key); //加锁失败,再判断是否由于解锁失败造成了死锁的情况 if(StringUtils.isNotEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()){ //获取上一个锁的时间,并且重新设置锁 String oldValue = redisTemplate.opsForValue().getAndSet(key, value); if(StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentValue)){ //设置成功,重新设置锁是保证了单线程的运行 return true; } } return false; } /** * @author wangbin * @description 进行解锁的操作 * @date 2019年09月20日 * @param key 某个方法请求url使用md5加密生成 * @param value 当前时间+过期时间 * @return */ public void unLock(String key,String value){ try { String currentValue = redisTemplate.opsForValue().get(key); if(StringUtils.isNotEmpty(currentValue) && currentValue.equals(value)){ redisTemplate.delete(key); } }catch (Exception e){ logger.error("redis分布式锁,解锁异常",e); } } /** * @author wangbin * @description 进行解锁的操作 * @date 2019年09月20日 * @param key 某个方法请求url使用md5加密生成 * @return */ public void unLock(String key){ try { String currentValue = redisTemplate.opsForValue().get(key); if(StringUtils.isNotEmpty(currentValue)){ redisTemplate.delete(key); } }catch (Exception e){ logger.error("redis分布式锁,解锁异常",e); } } }
3.使用拦截器在请求之前进行加锁的判断。
@Configuration public class LoginInterceptor extends HandlerInterceptorAdapter { private final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class); //超时时间设置为10秒 private static final int timeOut = 10000; @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisLock redisLock; /** * 在请求处理之前进行调用(Controller方法调用之前) * 基于URL实现的拦截器 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String path = request.getServletPath(); if (path.matches(Constants.NO_INTERCEPTOR_PATH)) { //不需要的拦截直接过 return true; } else { // 这写你拦截需要干的事儿,比如取缓存,SESSION,权限判断等 //判断是否是重复提交的请求 if(!redisLock.lock(DigestUtils.md5Hex(request.getRequestURI()+value),String.valueOf(System.currentTimeMillis()+timeOut))){ logger.info("===========获取锁失败,该请求为重复提交请求"); return false; } return true; } } }
4.使用aop在后置通知中进行解锁。
/** * @author wangbin * @description 使用redis分布式锁解决表单重复提交的问题 * @date 2019年09月20日 */ @Aspect @Component public class RepeatedSubmit { @Autowired private RedisLock redisLock; //定义切点 @Pointcut("execution(public * com.kunluntop.logistics.controller..*.*(..))") public void pointcut(){ } //在方法执行完成后释放锁 @After("pointcut()") public void after(){ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); redisLock.unLock(DigestUtils.md5Hex(request.getRequestURI()+ CookieUtils.getCookie(request,"userkey"))); } }
到此这篇关于redis分布式锁解决表单重复提交的问题的文章就介绍到这了,更多相关redis 表单重复提交内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Java Swing组件JFileChooser用法实例分析
这篇文章主要介绍了Java Swing组件JFileChooser用法,结合实例形式分析了java Swing组件JFileChooser文件选择器的功能、使用方法及相关注意事项,需要的朋友可以参考下2017-11-11springboot2.6.4集成swagger3.0遇到的坑及解决方法
这篇文章主要介绍了springboot2.6.4如何集成swagger3.0,在集成的过程中遇到很多问题,本文给大家分享四种问题及相应的解决方案,需要的朋友可以参考下2022-03-03SpringCloud @RefreshScope注解源码层面深入分析
@RefreshScope注解能帮助我们做局部的参数刷新,但侵入性较强,需要开发阶段提前预知可能的刷新点,并且该注解底层是依赖于cglib进行代理的,所以不要掉入cglib的坑,出现刷了也不更新情况2023-04-04详解SpringBoot开发案例之整合定时任务(Scheduled)
本篇文章主要介绍了详解SpringBoot开发案例之整合定时任务(Scheduled),具有一定的参考价值,有兴趣的可以了解一下2017-07-07elasticsearch索引index之Mapping实现关系结构示例
这篇文章主要介绍了elasticsearch索引index之Mapping实现关系结构示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2022-04-04
最新评论