-
2020-06-30 13:56:27
最近在开发的过程中遇到前端没有对提交按钮做点击后变灰处理,必须在后端添加防止重复提交的校验。网上有很多中方案,我这边采用的是aop+自定义注解方式实现。
刚开始采用利用自定义注解+aop+redis防止重复提交这篇博客的逻辑去实现,但是后来在测试多线程访问的时候会出现问题,然后参考网上Redis分布式锁的逻辑,多线程情况下测试只有一个可以通过。参考了LockManager中关于加锁的逻辑。具体的代码逻辑就不占了,只是在上面介绍的资料基础上做了稍微的改造。参考资料
https://blog.csdn.net/weixin_37505014/article/details/103461741
https://gitee.com/billion/redisLock/更多相关内容 -
JAVA后端防止重复提交demo (LRUMap+Caffeine)
2021-05-18 18:02:29JAVA 防止重复提交工具类主要依赖代码 主要依赖 <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.9.0</...1.Caffeine 是基于Java 8的高性能,接近最佳的缓存库。
2.LRUMap则是实现的LRP算法的Map集合类,它继承于AbstractLinkedMap抽象类。
主要依赖
<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.4</version> </dependency>
通用工具类代码
import com.github.benmanes.caffeine.cache.*; import org.apache.commons.collections4.map.LRUMap; import org.apache.commons.lang3.StringUtils; import java.util.concurrent.*; /** * @author beike */ public class IdempotentUtils { /** * 根据 LRU(Least Recently Used,最近最少使用)算法淘汰数据的 Map 集合,最大容量 100 个 */ private static final LRUMap<String, Integer> REQ_CACHE = new LRUMap<>(100); /** * 同步加载 * 弱引用不管当前内存空间足够与否,都会回收它的内存 */ private static final LoadingCache<String, String> SYNC_LOADING_CACHE = Caffeine.newBuilder() //数量上限 .maximumSize(1_000) //初始容量 .initialCapacity(128) // 弱引用value .weakValues() // 过期机制 .expireAfterWrite(5, TimeUnit.SECONDS) //刷新机制 .refreshAfterWrite(5, TimeUnit.SECONDS) .removalListener((RemovalListener<String, String>) (key, value, cause) -> System.out.println("key:" + key + ", value:" + value + ", 删除原因:" + cause.toString())) .build(key -> null); /** * Caffeine 同步加载 * * @param key 一般是请求的url+业务id * @return 重复提交 true 第一次提交 */ public static boolean judgeCaffeineSync(String key) { String value = SYNC_LOADING_CACHE.get(key); // 重复请求判断 if (StringUtils.isNotBlank(value)) { return false; } // 非重复请求,存储请求 SYNC_LOADING_CACHE.put(key, key); return true; } /** * 双重检测锁 DCL+LRUMap * * @param key 一般是请求的url+业务id * @param lockClass 服务层Class * @return 重复提交 true 第一次提交 */ public static boolean judgeLruMap(String key, Object lockClass) { if (REQ_CACHE.containsKey(key)) { // 重复请求 return false; } synchronized (lockClass) { // 重复请求判断 if (REQ_CACHE.containsKey(key)) { return false; } // 非重复请求,存储请求key REQ_CACHE.put(key, 1); } return true; } }
案例
@PutMapping("/update") @ApiOperation(value = "更新商品", httpMethod = "PUT") public ResponseData<String> update(@RequestBody @Validated Vo vo) { log.info("{}用户更新,{}", TokenUtil.getCurrentUserName(), JSON.toJSONString(vo)); if (!IdempotentUtils.judgeCaffeineSync("/v1/goods/update/" + vo.getId())) { return ResponseUtil.fail(MessageConstant.SYS_REPEAT_SUMMIT_FAIL); } //todo 执行业务代码 service.update(vo); return ResponseUtil.success(); }
-
Java怎样防止重复提交
2020-12-22 17:08:54防止重复提交java解决 B/S结构的软件开发中,特别是在越大型的分布式应用中体现的越明显,后端的处理往往会因为出现较多的时间消耗而引起延迟,这种延迟有可能过长而终使用户认为是自己的操作错误,导致他们重新... -
Java后端防止频繁请求、重复提交
2022-04-10 15:29:21Java后端防止频繁请求、重复提交 在客户端网络慢或者服务器响应慢时,用户有时是会频繁刷新页面或重复提交表单的,这样是会给服务器造成不小的负担的,同时在添加数据时有可能造成不必要的麻烦。所以我们在后端也有...Java后端防止频繁请求、重复提交
在客户端网络慢或者服务器响应慢时,用户有时是会频繁刷新页面或重复提交表单的,这样是会给服务器造成不小的负担的,同时在添加数据时有可能造成不必要的麻烦。所以我们在后端也有必要进行防抖操作。
- 自定义注解
/** * @author Tzeao */ @Target(ElementType.METHOD) // 作用到方法上 @Retention(RetentionPolicy.RUNTIME) // 运行时有效 public @interface NoRepeatSubmit { //名称,如果不给就是要默认的 String name() default "name"; }
- 使用AOP实现该注解
/** * @author Tzeao */ @Aspect @Component @Slf4j public class NoRepeatSubmitAop { @Autowired private RedisService redisService; /** * 切入点 */ @Pointcut("@annotation(com.qwt.part_time_admin_api.common.validation.NoRepeatSubmit)") public void pt() { } @Around("pt()") public Object arround(ProceedingJoinPoint joinPoint) throws Throwable { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); assert attributes != null; HttpServletRequest request = attributes.getRequest(); //这里是唯一标识 根据情况而定 String key = "1" + "-" + request.getServletPath(); // 如果缓存中有这个url视为重复提交 if (!redisService.haskey(key)) { //通过,执行下一步 Object o = joinPoint.proceed(); //然后存入redis 并且设置15s倒计时 redisService.setCacheObject(key, 0, 15, TimeUnit.SECONDS); //返回结果 return o; } else { return Result.fail(400, "请勿重复提交或者操作过于频繁!"); } } }
- serice,也可以放在工具包里面,这里我们使用到了Redis来对key和标识码进行存储和倒计时,所以在使用时还需要连接一下Redis
package com.qwt.part_time_admin_api.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.*; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.TimeUnit; /** * @author Tzeao */ @Component public class RedisService { @Autowired public RedisTemplate redisTemplate; /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @return 缓存的对象 */ public <T> ValueOperations<String, T> setCacheObject(String key, T value) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); operation.set(key, value); return operation; } /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 * @return 缓存的对象 */ public <T> ValueOperations<String, T> setCacheObject(String key, T value, Integer timeout, TimeUnit timeUnit) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); operation.set(key, value, timeout, timeUnit); return operation; } /** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public <T> T getCacheObject(String key) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 删除单个对象 * * @param key */ public void deleteObject(String key) { redisTemplate.delete(key); } /** * 删除集合对象 * * @param collection */ public void deleteObject(Collection collection) { redisTemplate.delete(collection); } /** * 缓存List数据 * * @param key 缓存的键值 * @param dataList 待缓存的List数据 * @return 缓存的对象 */ public <T> ListOperations<String, T> setCacheList(String key, List<T> dataList) { ListOperations listOperation = redisTemplate.opsForList(); if (null != dataList) { int size = dataList.size(); for (int i = 0; i < size; i++) { listOperation.leftPush(key, dataList.get(i)); } } return listOperation; } /** * 获得缓存的list对象 * * @param key 缓存的键值 * @return 缓存键值对应的数据 */ public <T> List<T> getCacheList(String key) { List<T> dataList = new ArrayList<>(); ListOperations<String, T> listOperation = redisTemplate.opsForList(); Long size = listOperation.size(key); for (int i = 0; i < size; i++) { dataList.add(listOperation.index(key, i)); } return dataList; } /** * 缓存Set * * @param key 缓存键值 * @param dataSet 缓存的数据 * @return 缓存数据的对象 */ public <T> BoundSetOperations<String, T> setCacheSet(String key, Set<T> dataSet) { BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); Iterator<T> it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 获得缓存的set * * @param key * @return */ public <T> Set<T> getCacheSet(String key) { Set<T> dataSet = new HashSet<>(); BoundSetOperations<String, T> operation = redisTemplate.boundSetOps(key); dataSet = operation.members(); return dataSet; } /** * 缓存Map * * @param key * @param dataMap * @return */ public <T> HashOperations<String, String, T> setCacheMap(String key, Map<String, T> dataMap) { HashOperations hashOperations = redisTemplate.opsForHash(); if (null != dataMap) { for (Map.Entry<String, T> entry : dataMap.entrySet()) { hashOperations.put(key, entry.getKey(), entry.getValue()); } } return hashOperations; } /** * 获得缓存的Map * * @param key * @return */ public <T> Map<String, T> getCacheMap(String key) { Map<String, T> map = redisTemplate.opsForHash().entries(key); return map; } /** * 获得缓存的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection<String> keys(String pattern) { return redisTemplate.keys(pattern); } /** * @param key * @return */ public boolean haskey(String key) { return redisTemplate.hasKey(key); } public Long getExpire(String key) { return redisTemplate.getExpire(key); } public <T> ValueOperations<String, T> setBillObject(String key, List<Map<String, Object>> value) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); operation.set(key, (T) value); return operation; } /** * 缓存list<Map<String, Object>> * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 * @return 缓存的对象 */ public <T> ValueOperations<String, T> setBillObject(String key, List<Map<String, Object>> value, Integer timeout, TimeUnit timeUnit) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); operation.set(key, (T) value, timeout, timeUnit); return operation; } /** * 缓存Map * * @param key * @param dataMap * @return */ public <T> HashOperations<String, String, T> setCKdBillMap(String key, Map<String, T> dataMap) { HashOperations hashOperations = redisTemplate.opsForHash(); if (null != dataMap) { for (Map.Entry<String, T> entry : dataMap.entrySet()) { hashOperations.put(key, entry.getKey(), entry.getValue()); } } return hashOperations; } }
- 测试
@NoRepeatSubmit(name = "test") // 也可以不给名字,这样就会走默认名字 @GetMapping("test") public Result test() { return Result.success("测试阶段!"); }
-
java后端解决重复提交问题
2017-10-02 18:10:15一、为什么会出现重复提交?...对于前端的办法这里就不做演示了,因为前端的控制虽然能够防止数据的重复提交但是治标不治本。这里主要介绍第二种方法。 3.2 后端解决: 思路:主要是利用唯一Token一、为什么会出现重复提交?
主要是由于网络的延迟问题以及页面刷新的操作。
二、表单的重复提交会导致的问题?
主要能够造成很多脏数据。
三、解决的办法:
3.1 前端解决办法:通过前端的方法将提交按钮变灰。对于前端的办法这里就不做演示了,因为前端的控制虽然能够防止数据的重复提交但是治标不治本。这里主要介绍第二种方法。
3.2 后端解决: 思路:主要是利用唯一Token值与提交参数相匹配验证。
后端解决的代码示例:
1.前端页面
2.发送Token值去前端页面代码:<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'index.jsp' starting page</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> <!-- <link rel="stylesheet" type="text/css" href="styles.css"> --> </head> <body> <form action="${pageContext.request.contextPath}/DoFormServlet" method="post"> <input type="hidden" name="token" value="${sessionToken}"> 用户名:<input type="text" name="userName"> <input type="submit" value="提交" id="submit"> </form> </body> </html>
3.具体解决重复提交核心代码:package session; import java.io.IOException; import java.util.UUID; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/SendTokenToForm") public class SendTokenToForm extends HttpServlet { /** * */ private static final long serialVersionUID = 5841829906440324978L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getSession().setAttribute("sessionToken", UUID.randomUUID().toString()); req.getRequestDispatcher("/index.jsp").forward(req, resp); } }
感想:解决数据重复提交虽然技术没有多么高大上,但是由于平时的不怎么注意,很多人都忽略这个细节,解决起来也花不了多少的时间。所以平时对于代码的细节方面应该多多斟酌,追求代码完美。避免不必要的事件发生,即使发生的概率很小。package session; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/DoFormServlet") public class DoFormServlet extends HttpServlet { /** * */ private static final long serialVersionUID = 82128771669092572L; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (!isSubmit(req)) { resp.getWriter().write("数据已提交"); System.out.println("数据已提交"); } //让线程休眠0.9秒,方便测试 try { Thread.sleep(900); } catch (InterruptedException e) { e.printStackTrace(); } String userName = req.getParameter("userName"); System.out.println("正在往数据库插入数据"+userName); resp.getWriter().write("success"); } /** * @Title: isSubmit * @Description: 判断token值是否相同以及是否有伪token值得传入 * @author 西安工业大学-查文彬 * @time 2017年10月2日 下午5:53:41 * @param request * @return * boolean */ public boolean isSubmit(HttpServletRequest request){ String sessionToken = (String) request.getSession().getAttribute("sessionToken"); String parameter = request.getParameter("token"); if (!(sessionToken.equals(parameter))) { return false; } request.getSession().removeAttribute("sessionToken"); return true; } }
-
Java 后端防重提交解决办法及讨论
2021-07-23 15:41:52需要注意点,本方法的解决偏向于防重复提交,不针对于对接口幂等性的处理等需要具体原子性的操作. 虽然也能用,但针对大流量情况下,交互、响应及性能上还是不能得到保证. 存在交互差 错误, 重复的情况 结论 先给代码吧,... -
javaWeb应用后端防止表单重复提交
2019-05-29 15:24:54正常我们防止一个页面的表单重复提交有2种途径 1:客户端控制(比如js判断,按钮置灰不可用等等这个大家自行网上查询) 2:服务器端针对api自己多业务逻辑判断 实际使用的场景中,我们大多是2者结合起来做,不能把... -
Java后端解决重复提交问题(没搞懂,先不管)
2021-02-12 09:40:18前言发生条件:接受到一个请求,该请求没有执行完成又接受到相同请求,导致数据错误(如果是前一个... 添加拦截器,拦截需要防重复提交的请求2. 通过注解@Token来添加token/移除token3. 前端页面表单添加(如果是Aj... -
Java后台防止客户端重复请求、提交表单实现原理
2020-08-25 05:44:43主要介绍了Java后台防止客户端重复请求、提交表单实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
java 后端防止前端重复提交数据
2021-03-16 20:48:44//自定义一个防止重复提交的注解package com.mingwen.common.SubmitMore;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.... -
后台防止表单重复提交
2021-03-14 03:48:132、将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的...3、服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就... -
java后台防重复提交
2021-11-18 14:03:29import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD, Element... -
java面试 - 后台如何防止表单重复提交?
2022-03-04 07:11:34方案一:利用Session防止表单重复提交 方案二:判断请求url的数据是否和上一次相同 方案三:利用Spring AOP 和redis的锁来实现防止表单重复提交 .... -
干货实战~Java如何防止接口重复提交
2021-02-12 22:01:19正如本文标题所言,今天我们来聊一聊在Java应用系统中如何防止接口重复提交;简单地讲,这其实就是“重复提交”的话题,本文将从以下几个部分展开介绍:“重复提交”简介与造成的后果“防止接口重复提交”的实现思路... -
java防止重复提交解决方案
2020-07-03 09:31:05java开发防止重复提交问题问题描述解决思路代码解释 问题描述 1.在我们项目开发过程中会出现用户保存操作时候快速点击两次会出现一条数据在数据库保存多条数据。 2.遇见上述问题我们首先跟前端开发沟通,在前端开发... -
JAVA后端生成Token(令牌),用于校验客户端,防止重复提交
2018-02-27 17:48:09JAVA后端生成Token(令牌),用于校验客户端,防止重复提交1.概述:在web项目中,服务端和前端经常需要交互数据,有的时候由于网络相应慢,客户端在提交某些敏感数据(比如按照正常的业务逻辑,此份数据只能保存一份... -
经典好文!java后端解决重复提交问题
2021-07-15 02:20:39java基础 1.1java的8种基本数据类型装箱拆箱 1.2重写重载封装继承多态 1.3 Stack Queue 1.7 Concurrent包 1.8面向对象 1.9 String StringBuffer StringBuilder hashcode equ 1.10 java文件读取 1.11 Java反射 1.12 ... -
Java SpringBoot+Redis实现后端接口防重复提交校验
2021-06-20 21:11:34/** * 防止重复提交拦截器 */ @Component public class RepeatSubmitInterceptor implements HandlerInterceptor { public final String REPEAT_PARAMS = "repeatParams"; public final String REPEAT_TIME = ... -
Java中如何避免重复提交请求
2021-03-06 02:23:33查看后台日志后发现一个同样的请求提交了多次,后果就是轻则导致产生多条...二、产生原因导致重复请求的原因很多,大体为以下几种:多次点击提交按钮反复刷新页面点击浏览器后退按钮,导致重复提交表单浏览器重复的H... -
java并发访问重复请求过滤问题
2020-08-27 10:47:08本篇文章给大家分享了关于java并发访问重复请求过滤的相关问题以及解决方法,对此有需要的朋友参考学习下。 -
后端如何防止重复提交
2022-04-10 11:24:10后端如何防止重复提交 -
后端防止重复点击设计
2020-11-23 16:38:19在同一时刻,调用同一个方法,且入参一致则认定为是重复点击,此时不在执行后续方法。 三、思路 1、为了方法的通用性以及和业务系统进行解耦,在此使用aop的环绕增强。 2、在增强中判断当前的类名+方法名+入... -
防止重复提交_后端实现
2022-05-08 19:09:27前端页面,有一提交按钮,如何防止用户多次点击呢(比如支付页面的支付按钮,要做此限制,不然会扣多笔钱)?前端方面,可以给按钮加上单次点击后就出现loading效果(按钮不可点击 ,并会有一个转圈的动画);那后端... -
java项目springboot后端怎么防止前端重复提交表单-通过注解实现-redis版
2020-06-06 11:13:02前端提交表单按钮如果不小心按了两次或者多次,那么表单数据就被重复插入数据库,尤其是网络延时的情况下,多次点击提交按钮是常有的事,那么后端怎么来避免这个问题呢,最好的办法就是通过注解+拦截器验证重复的... -
java 后端做重复提交拦截基于aop
2020-05-18 10:02:52} 5、防止重复操作的方法加入注解 超时时间可以自己配置。这个拦截的思路是。某个用户在指定时间内不能请求相同地址。完美破解同一个用户在不同浏览器同时操作一条数据以及同一个浏览器重复单击按钮。不足之处请... -
Java 保证接口的幂等性 接口唯一性 避免重复提交
2021-03-19 20:28:46举栗子:当网络延迟的情况下,用户多次点击,多条数据插入到数据库,或者造成数据的不一致性,如提交订单,扣钱等。 简述一下前端解决思考 :按钮只让用户点击一次,重定向至友好页面,避免多次点击,此文主要介绍... -
java 防止重复提交
2021-03-01 08:09:46前两种是利用javascript,后面一种是在使用Struts的情况下的参考实现:1、javascript ,设置一个变量,只允许提交一次。var checkSubmitFlg = false;function checkSubmit(){if (checkSubmitFlg == true){return false... -
防止表单重复提交的解决方案整理
2021-03-15 03:51:08用户在操作表单Post数据时往往会出现表单数据重复提交的问题,尤其在Web开发中此类问题比较常见。刷新页面,后退操作以前的页面,单机多次按钮都会导致数据重复提交。此类问题是因为浏览器重复提交HTTP请求导致。...
收藏数
12,400
精华内容
4,960