接口防止重复提交,订单避免重复下单

article/2025/8/20 11:20:47

一.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博客_如何解决重复下单

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


http://chatgpt.dhexx.cn/article/1mPi6kVE.shtml

相关文章

java项目中,如何解决用户重复提交订单进行支付

支付模块为防止用户重复下单购买套餐做唯一标识进行防重处理。具体实现逻辑&#xff1a; 当用户点击购买发起选购套餐时&#xff0c;后台支付服务通过UUID中的randomUUID方法生成唯一标识和套餐信息一起带到前端同时使用map集合存入后台&#xff08;使用用户id作为键&#xff…

Django项目实战----订单页面的显示和生成订单、提交订单的逻辑

创建订单模型类 models.py from django.db import models# Create your models here. from django.db import modelsfrom apps.goods.models import SKU from apps.users.models import User, Address from utils.models import BaseModelclass OrderInfo(BaseModel):"&…

购物车 -- 结算、提交订单接口开发

在购物车列表中选择对应的商品之后&#xff0c;点击提交生成订单的过程 流程图&#xff1a; 接口实现&#xff1a; 收货地址列表接口&#xff1a; 此操作的数据库实现可以通过tkMapper通过方法完成 service接口&#xff1a; package com.qfedu.fmmall.service;import com.…

订单操作-订单详情查询代码实现

订单详情 在order-list.jsp页面上对"详情"添加链接 <button type"button" class"btn bg-olive btn-xs" onclick"location.href${pageContext.request.contextPath}/orders/findById.do?id${orders.id}"> 详情</button>…

电商项目 - 用户订单确认及下订单操作

一、订单确认页功能流程图 1、进入登录确认页之前会&#xff0c;先进入登录拦截器 同样是使用ThreadLocal来存储用户的登录信息&#xff0c;从请求的request中获取登录信息 Component public class LoginUserInterceptor implements HandlerInterceptor {public static Threa…

解决支付订单,重复提交问题!

点击上方“朱小厮的博客”&#xff0c;选择“设为星标” 后台回复"书"&#xff0c;获取 后台回复“k8s”&#xff0c;可领取k8s资料 概述 如图是一个简化的下单流程&#xff0c;首先是提交订单&#xff0c;然后是支付。支付的话&#xff0c;一般是走支付网关&#xf…

SpringBoot解决用户重复提交订单(方式二:通过Redis实现)

文章目录 前言1、方案实践1.1、引入Redis依赖1.2、添加Redis环境配置1.3、编写获取请求唯一ID的接口&#xff0c;同时将唯一ID存入redis 1.4、编写服务验证逻辑&#xff0c;通过 aop 代理方式实现1.5、在相关的业务接口上&#xff0c;增加SubmitToken注解即可 2、小结 前言 在…

后端怎样防止重复提交订单?

点击上方关注 “终端研发部” 设为“星标”&#xff0c;和你一起掌握更多数据库知识 一般我们都是这样做的&#xff1a; 创建订单的时候&#xff0c;用订单信息计算一个哈希值&#xff0c;判断redis中是否有key&#xff0c;有则不允许重复提交&#xff0c;没有则生成一个新key&…

后端怎么防止重复提交订单

前言 接口幂等性问题&#xff0c;对于开发人员来说&#xff0c;是一个跟语言无关的公共问题。本文分享了一些解决这类问题非常实用的办法&#xff0c;绝大部分内容我在项目中实践过的&#xff0c;给有需要的小伙伴一个参考。 不知道你有没有遇到过这些场景&#xff1a; 有时…

bootstrap订单提交页面

下载地址 基于bootstrap实现的订单提交页面&#xff0c;常见的电商购物网站订单确认提交页面。 dd:

移动端-确认订单页面

项目准备 lib文件中存放外来的文件&#xff0c;就比如这个项目使用到字体图标&#xff0c;那存放的就是字体图标的文件&#xff0c;css 样式&#xff0c;images 重要的图片&#xff0c;uploads 页面随时更新的图片&#xff0c;其次就是html文件。 base.css *{margin: 0;pad…

confirm-order提交订单

目录 顶部导航条&#xff1a;复用head组件新增收货地址订单收货地址页面顶部导航条&#xff1a;复用head组件无地址地址列表新增地址 增加收货地址add_address顶部导航条&#xff1a;复用head组件地址信息表单其他组件 送达时间商店商品底部弹出消息&#xff1a;复用alertTip组…

订单。。。

一、库存扣减和订单表不一致 1、网络抖动—网速是好是坏&#xff0c;不稳定。最大延迟与最小延迟的时间差&#xff0c;如最大延迟是20毫秒&#xff0c;最小延迟为5毫秒&#xff0c;那么网络抖动就是15毫秒 2、库存数据不一致的原因&#xff1a; 1&#xff09;事务性的问题 – …

实现提交订单的功能

根据购物车中的商品名称和数量生成了结算信息&#xff0c;并可以填写收货人姓名、联系电话和收货地址&#xff0c;本任务将实现提交订单的功能。 一、创建订单页面order.jsp <% page language"java" import"java.util.*" pageEncoding"UTF-8"…

美多商城项目:结算订单与提交订单

一、结算订单 1. 结算订单逻辑分析 结算订单是从Redis购物车中查询出被勾选的商品信息进行结算并展示。 2. 结算订单接口设计和定义 1.请求方式 选项方案请求方法GET请求地址/orders/settlement/ 3. 结算订单后端逻辑实现 class OrderSettlementView(LoginRequiredMixin, Vie…

电商系统-提交订单并发处理

在多个用户同时发起对一个商品的下单请求时&#xff0c;先查询商品库存&#xff0c;再修改商品库存&#xff0c;会出现资源竞争问题&#xff0c;导致库存的最终结果出现异常。 1、并发下单问题演示 每个不同的用户在程序上&#xff0c;我们可以理解成不同的线程&#xff0c;每…

Android Studio_Toast消息提醒

Android Studio_Toast消息提醒 1、Toast是Android系统提供的一种非常简洁的消息提醒方式&#xff0c;程序中可以使用它实现将短小的消息通知给用户&#xff0c;一点时间后自动消失&#xff0c;且不占用屏幕的任何空间。 2、Toast用法其实非常简单&#xff0c;通过静态方法make…

Vue 消息提示通知的几种方式汇总

Vue 消息提示通知组件&#xff08;Message /Notification&#xff09;是我们日常开发中经常使用的组件&#xff0c;它可用作与用户交互的反馈提示&#xff0c;信息提交成功、错误、操作警告等场景使用。原生JavaScript 提供了 alert、prompt、confirm 等方法 提示框1>Messag…

java信息提醒怎么实现_jsp怎么实现消息提醒

如果你是平台级别的系统,可以考虑消息队列的中间件,例如:阿里巴巴的rocketmq,用这个来做消息订阅与分发。 如果你只是简简单单的需要提示到web(jsp)页面,可以用js定时ajax访问后台,后台来确定是否有数据更新,无论这个数据是哪来的。 推荐课程:Java教程。 这里就使用在JSP页面…

RabbitMQ真延时队列实现消息提醒功能

RabbitMQ真延时队列实现消息提醒功能 一、需求场景 用户可以制定多个计划&#xff0c;同时可给该计划设置是否需要到点提醒&#xff0c;且中途可以取消提醒或修改提醒时间。 二、需要解决的问题 学习过rabbitmq的同学们都知道&#xff0c;通过TTL死信队列可以实现延时队列的…