下单接口防重提交问题

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 下单接口防重提交问题

公众号merlinsea


  • 背景


  • 用户下单的时候,可能由于网络延迟导致用户多次提交的是同一笔订单,但作为业务方需要保证用户只会支付一次。即用户多次点击提交支付的时候下单接口应该具有幂等性。
  • 幂等性介绍
  • 幂等即如果用户同一个请求多次到达,那么我们只能处理一次,即默认第一次会处理,后续的请求会忽略。
  • 对于输出,同一个请求不论来多少次,返回的结果都必须是相同的。
  • 因此幂等通常会选取请求中可以唯一标识这个请求的参数作为幂等key,幂等请求的结果和幂等key应该存储在redis中,后序相同的请求到来可以直接冲redis中获取。


  • 用户下单流程
  • 1、选中购物车中的商品,点击结算 ------->后端返回一个提交令牌token同时跳转到提交订单支付页面【提交订单】
  • 2、用户点击提交订单,进行支付【确认支付】


  • 说明:下单流程中第1步提交订单实际上是直接和我们的后台进行响应,速度是比较快的,因此在这一步是不会出现重复下单,但对于第2步由于支付需要和第三方交互,用户可能由于延迟多次支付,因此在第1步的时候我们会生成一个唯一标识这个订单的令牌token交给用户【同时token会在后端保存一份】,用户在第2步的时候会携带token进行支付【支付完后端就删除了这个token】,这样我们就可以保证不论用户支付多少次,实际上的付款只会执行一次。


  • 解决重复下单问题的思路
  • 将用户下单过程分为两步执行,第一步中会生成一个提交令牌token,并将这个提交的令牌token存到redis中,在第二步中用户点击提交订单进行支付的时候会携带上这个提交令牌token,如果发现redis中有这个token说明是第一次提交,调用第三方支付接口同时删除redis中的这个令牌。这样在第二个步骤中不管用户点击了多少次都只会支付一次(因为后续的提交都找不到这个token了)。

640.jpg


  • 第一步:点击购物车结算按钮,获取提交订单的唯一令牌【这步是和本项目的微服务交互,故速度会比较快】


@ApiOperation("获取提交订单令牌")
@GetMapping("get_token")
public JsonData getOrderToken(){
    LoginUser loginUser = LoginInterceptor.threadLocal.get();
    String key = String.format(CacheKey.SUBMIT_ORDER_TOKEN_KEY,loginUser.getId());
    String token = CommonUtil.getStringNumRandom(32);
    redisTemplate.opsForValue().set(key,token,30,TimeUnit.MINUTES);
    return JsonData.buildSuccess(token);
}

640.jpg

  • 第二步:点击提交订单接口并携带token【这一步要和第三方支付交互,速度可能会比较慢,关键是在这步容易出现重复提交】


/**
 * * 防重提交
 * * 用户微服务-确认收货地址
 * * 商品微服务-获取最新购物项和价格
 * * 订单验价
 *   * 优惠券微服务-获取优惠券
 *   * 验证价格
 * * 锁定优惠券
 * * 锁定商品库存
 * * 创建订单对象
 * * 创建子订单对象
 * * 发送延迟消息-用于自动关单
 * * 创建支付信息-对接三方支付
 *
 * @param orderRequest
 * @return
 */
@Override
@Transactional
public JsonData confirmOrder(ConfirmOrderRequest orderRequest) {
    LoginUser loginUser = LoginInterceptor.threadLocal.get();
    String orderToken = orderRequest.getToken();
    if(StringUtils.isBlank(orderToken)){
        throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_NOT_EXIST);
    }
    //原子操作 校验令牌,删除令牌
    String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
    Long result = redisTemplate.execute(new DefaultRedisScript<>(script,Long.class), Arrays.asList(String.format(CacheKey.SUBMIT_ORDER_TOKEN_KEY,loginUser.getId())),orderToken);
    //redis中找不到提交token 说明是重复提交,直接抛出异常
    if(result == 0L){
        throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
    }
    //说明是第一次提交,执行下单支付相关业务todo【调用第三方支付接口】
}
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
存储 缓存 NoSQL
防止订单重复提交或支付分布式锁方案设计
防止订单重复提交或支付分布式锁方案设计
752 0
|
NoSQL Java Redis
服务端如何防止订单重复支付!
如图是一个简化的下单流程,首先是提交订单,然后是支付。 支付的话,一般是走支付网关(支付中心),然后支付中心与第三方支付渠道(微信、支付宝、银联)交互。 支付成功以后,异步通知支付中心,支付中心更新自身支付订单状态,再通知业务应用,各业务再更新各自订单状态。
服务端如何防止订单重复支付!
|
3月前
|
SQL NoSQL 前端开发
大厂如何解决订单幂等问题
本文探讨了分布式系统中接口幂等性的重要性和实现方法,特别是在防止重复下单的场景中。首先介绍了通过数据库事务处理创建订单时的原子性需求。接着分析了服务间调用时可能遇到的重复请求问题,提出每个请求需具备唯一标识,并记录处理状态以识别并阻止重复操作。具体实践包括生成全局唯一的订单ID,利用数据库主键唯一性约束来防止重复插入,以及使用Redis存储订单支付状态。此外,文章还讨论了解决ABA问题(即数据在两次检查之间被修改的问题)的方法,引入版本号机制来确保数据更新的原子性和一致性。这些技术方案不仅限于订单服务,也可广泛应用于需要实现幂等性的其他业务场景中。
支付系统42----支付宝支付-定时查单-订单已支付,如果我们在定时查单的状态中,我们明明已经支付的订单,却在本地状态中显示没有支付,这是因我们的异步通知因为种种原因没有接受到,支付宝端成功,本地失败
支付系统42----支付宝支付-定时查单-订单已支付,如果我们在定时查单的状态中,我们明明已经支付的订单,却在本地状态中显示没有支付,这是因我们的异步通知因为种种原因没有接受到,支付宝端成功,本地失败
支付系统34----支付成功异步通知,处理重复通知,我们在我们程序当中找到处理订单的processOrder方法,我们要在更新订单状态和记录日志之前,先处理重复通知
支付系统34----支付成功异步通知,处理重复通知,我们在我们程序当中找到处理订单的processOrder方法,我们要在更新订单状态和记录日志之前,先处理重复通知
|
6月前
|
消息中间件 Java Unix
MQ产品使用合集之消费订单状态,订单消费待支付消息失败,是否会导致其他订单也没法消费
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
Java Spring 容器
通过注解开发来实现下单接口的防重提交
通过注解开发来实现下单接口的防重提交
|
SQL 负载均衡 NoSQL
【防止重复下单】分布式系统接口幂等性实现方案
【防止重复下单】分布式系统接口幂等性实现方案
1741 0
【防止重复下单】分布式系统接口幂等性实现方案
|
消息中间件 存储 资源调度
订单超时怎么处理?我们用这种方案
在电商业务下,许多订单超时场景都在24小时以上,对于超时精度没有那么敏感,并且有海量订单需要批处理,推荐使用基于定时任务的跑批解决方案。
1572 5
订单超时怎么处理?我们用这种方案