一.Java防止频繁请求、重复提交的操作代码(后端防抖操作)

在客户端网络慢或者服务器响应慢时,用户有时是会频繁刷新页面或重复提交表单的,这样是会给服务器造成不小的负担的,同时在添加数据时有可能造成不必要的麻烦。所以我们在后端也有必要进行防抖操作。

1.自定义注解

/*** @author Tzeao*/
@Target(ElementType.METHOD) // 作用到方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
public @interface NoRepeatSubmit {//名称,如果不给就是要默认的String name() default "name";
}

2.使用AOP实现该注解

/*** @author Tzeao*/
@Aspect
@Component
@Slf4j
public class NoRepeatSubmitAop {@Autowiredprivate 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, "请勿重复提交或者操作过于频繁!");}}
}

3.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;
}
}

4.测试

  @NoRepeatSubmit(name = "test") // 也可以不给名字,这样就会走默认名字@GetMapping("test")public Result test() {return Result.success("测试阶段!");}

15秒内重复点击就会给提示

这样就完成了一个防止重复提交、频繁申请的程序!!!

二: 订单避免重复下单

1、重复原因
  造成重复下单的原因有很多,比如用户重复提交、网络超时导致的重试(网关超时重试、RPC超时重试,以及前端超时重试等),下单请求的整个链路都可能造成重复,大致可以分成如下三类:

用户重复提交;
        恶意刷单;
        网络原因导致的超时重试;

2、产品方案
  如何防止用户重复下单,并不只是技术的事情,因为技术并不一定能百分百的考虑到所有可能重复的场景,必须依靠产品+技术共同的努力,以及运营、客服等订单重复时的事后处理。

  用户点击”提交订单“按钮后,对按钮置灰,禁止再次提交;
        对于涉及金额比较大的订单,需要弹窗二次确认;
        对用户的下单频率、次数进行限制;

3、技术方案
  用户下单在逻辑上是一个非幂等行为,因此解决方案都是基于物理去重的思路设计的,用某个id表示某次下单行为,通过该id判断是否重复;

如何防止重复下单

防止用户提交,最常规的做法,就是客户端点击下单之后,在收到服务端响应之前,按钮置灰。

当然,防止重复下单,肯定不能只依靠客户端,可能会因为一些网络的抖动,导致仍然有重复的请求到达服务端,所以还是要在服务端做防重/幂等的处理。

PS:这里额外插入一点我对防重和幂等的理解:防重指的是防止重复提交,幂等指的是多次请求如一次,简单说,就是防重可以给对重复请求抛异常,幂等是对重复的请求响应第一次的结果,在我们讨论的这个场景里,幂等就是响应唯一的订单号。

防重第一步,需要识别请求是否重复,这一步,需要客户端配合实现。

为什么呢?大家想一下,下单的时候,服务端怎么去判断这个下单请求是否唯一呢?金额?商品?优惠券?……万一用户就是喜欢,又下了一个一模一样的单呢?

所以,需要客户端在请求下单接口的时候,需要生成一个唯一的请求号:requestId/唯一订单号,服务端拿这个请求号/订单号,判断是否重复请求。

3.1 基于订单号去重
用户进入下单页面时,前端页面先调用订单服务得到一个订单号;
用户提交订单时,携带得到的订单号,向后端发送创建订单请求;
接收到创建订单请求后,订单系统校验订单号是否合法,以及是否已被缓存;
如果存在,说明订单合法,将订单号作为唯一主键在数据库中创建订单;

参考:

订单系统设计 —— 重复下单_库昊天的博客-CSDN博客_如何解决重复下单

如何避免下重复订单为啥会下重了呢?用幂等防止重复订单客户端的流程后端数据表设计下单的实现技术搞定幂等就足够了吗?通知如果还拦不住……这么麻烦,有必要吗?结论 - 腾讯云开发者社区-腾讯云

接口防止重复提交,订单避免重复下单相关推荐

  1. SpringBoot解决用户重复提交订单(方式一:通过唯一索引实现)

    文章目录 前言 1.方案实现 1.1.给数据库表增加唯一键约束 1.2.编写获取请求唯一ID的接口 1.3.业务提交的时候,检查唯一ID 2.小结 前言 对于投入运营的软件系统(商城.物流.工厂等), ...

  2. 防止重复提交订单-(转)

    防止重复提交 Button1.Attributes.Add("onclick",   "this.value='正在提交中,请等待--';this.disabled=tr ...

  3. Vue实现订单确认界面禁止浏览器返回操作导致重复提交订单的问题

    哈喽  大家好啊 最近遇到一个问题,就是在提交订单成功后的页面,然后用户去浏览器返回,就导致又提交了一次 然后就想到了如果提交成功页面,就阻止浏览器返回操作 主要实现如下: 1.在mounted的钩子 ...

  4. java 重复提交_java解决重复提交表单

    在前端代码写一个随机数出来,把这个随机数放在两个地方,一个是session域对象中,一个是表单的隐藏域中.传到后台进行比对,根据这两个值是不是一致,一致的话不是表单重复提交,不一致就是表单重复提交.注 ...

  5. php ajax 重复提交,ThinkPHP防止重复提交表单的方法实例分析

    本文实例总结分析了ThinkPHP防止重复提交表单的方法.分享给大家供大家参考,具体如下: 为什么会有表单重复的坑 在开发中,如果一个新增或修改的表单,在后台完成数据库操作后我们设定的不是跳转到其他页 ...

  6. springboot 主键重复导致数据重复_Springboot实现防重复提交和防重复点击(附源码)...

    背景# 同一条数据被用户点击了多次,导致数据冗余,需要防止弱网络等环境下的重复点击 目标# 通过在指定的接口处添加注解,实现根据指定的接口参数来防重复点击 说明# 这里的重复点击是指在指定的时间段内多 ...

  7. ajax按钮禁止重复提交,Ajax禁止重复提交

    var pendingRequests = []; var generatePendingRequestKey = function (obj) { return obj.data || {}; } ...

  8. 架构设计 | 接口幂等性原则,防重复提交Token管理

    本文源码:GitHub·点这里 || GitEE·点这里 一.幂等性概念 1.幂等简介 编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同.就是说,一次和多次请求某一个资源会产 ...

  9. 处理接口超时_架构设计 | 接口幂等性原则,防重复提交Token管理

    一.幂等性概念 1.幂等简介 编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同.就是说,一次和多次请求某一个资源会产生同样的作用影响. 2.HTTP请求 遵循Http协议的请 ...

  10. SpringBoot 自定义注解+AOP+Redis 防止接口重复提交表单数据

    SpringBoot结合Redis处理重复提交 数据重复提交导致多次请求服务.入库,产生脏数据.冗余数据等情况.禁止重复提交使我们保证数据准确性及安全性的必要操作. 实际上,造成这种情况的场景不少: ...

最新文章

  1. CSR稀疏矩阵存储方式
  2. 团队编程项目作业2-团队编程项目开发环境搭建过程
  3. PushYourself
  4. linux 安装 memcached
  5. RHEL5下源码配置LAMP全记录
  6. Lintcode415-Valid Palindrome-Medium
  7. Openstack(二)基本环境准备--网络、时间、yum源等
  8. Java多线程学习八:线程池常用的阻塞队列
  9. script标签中defer和async的区别
  10. 450A - Jzzhu and Children 找规律也可以模拟
  11. Mysql mysqld_safe启动与myslqd启动坑
  12. EmEditor中正则表达式
  13. Tomcat 下面使用软连接指向真实的上传文件夹
  14. 计算机时间校对更改原因,计算机时间校准方法
  15. 董明珠揭示:未来2年这个行业盈利最大,马总点赞说,又要出富翁
  16. 搭建STF远程真机操控环境
  17. 推理悬疑侦探小说大全合集隐私政策
  18. OKHTTP和retrofit 网络框架集成的有https验证的APP破解抓包
  19. 3D Max中布尔运算介绍
  20. 如何获取目标基因的转录因子

热门文章

  1. 厨电智能化趋势下,究竟什么才是真正的“用户思维”?
  2. 没有标准答案的GIS问题
  3. 用计算机弹EXO简谱,Power钢琴简谱-数字双手-EXO
  4. vue 微信公众号获取定位经纬度 腾讯地图逆地址解析为具体地址
  5. vc6.0是c语言吗,vc6.0我二级报的是C语言,听说现在C语 – 手机爱问
  6. “红蓝眼人”问题中旅行者说的话的作用
  7. 【MySQL篇】第三篇——表的操作
  8. Element UI学习记录之布局
  9. 计算机凭证打印格式设置,打印凭证怎么设置纸张
  10. layui时间范围控件限制时间