面试中网络阻塞导致用户重复下单问题及解决方案

目录

面试中网络阻塞导致用户重复下单问题及解决方案

一、普通下单流程

二、幂等性问题分析

三、解决方案

(一)前端限制

(二)后端限制


在面试中,经常会遇到一些实际场景的问题,比如当网络出现阻塞时,用户不断点击下单按钮导致重复下单的情况。本文将详细介绍这种场景下的问题分析及解决方案。

一、普通下单流程

  1. 用户发起下单操作,订单服务保存一条订单信息,此时订单状态为未支付。
  2. 用户确认订单,订单状态从未支付改为待支付。
  3. 用户选择支付方式进行钱包支付,此步骤由第三方支付平台负责。
  4. 支付完成后,支付平台回调到支付服务,支付服务再调用订单服务将订单状态修改为已支付。

二、幂等性问题分析

真正会出现重复下单和幂等性问题的是用户下单这一步。如果在网络阻塞时用户多次点击下单按钮,订单服务会生成很多条订单信息,这不仅造成资源浪费,还会影响用户体验,因为用户会莫名其妙地发现出现了很多未支付的订单信息。

三、解决方案

(一)前端限制

当用户点击了第一次下单按钮后,将按钮置为不可用,防止用户无意点击多次。但这种方式只能防君子,不能防小人。

(二)后端限制

采用 Redis 提供的 C 的 NX(SETNX)来保证唯一幂等性。

  1. 当用户进行立即购买下单时,可以通过当前用户唯一标识(如登录后的 token)结合当前商品的 URL 以及一个表示重复下单的 key(如 “recommit”)作为 key,调用 SETNX 进行存储。如果 value 无值则返回 true 并保存成功,若有值则返回 false。
  2. 同时需要设置过期时间,比如三秒或五秒。因为在这个时间内,用户基本上不可能有这么快的速度重复购买商品,如果重复购买,很可能是恶意的,可以排除掉。

以下是一个通用的防止重复提交的示例代码:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.UUID;

@Aspect
@Component
public class RepeatSubmitAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Pointcut("@annotation(com.example.RepeatSubmit)")
    public void repeatSubmitPointcut() {}

    @Around("repeatSubmitPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取注解
        RepeatSubmit repeatSubmit = joinPoint.getTarget().getClass().getAnnotation(RepeatSubmit.class);
        if (repeatSubmit == null) {
            return joinPoint.proceed();
        }
        // 获取用户唯一标识,这里假设用户已登录且有 token
        String token = "user_token";
        // 获取当前 URL 和参数等
        String url = joinPoint.getSignature().toShortString();
        // 生成唯一 key
        String key = token + url + repeatSubmit.key();
        // 调用 SETNX
        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, UUID.randomUUID().toString(), repeatSubmit.expireSeconds(), repeatSubmit.timeUnit());
        if (result!= null && result) {
            return joinPoint.proceed();
        } else {
            // 重复提交的处理,可以抛出异常或记录日志
            return null;
        }
    }
}

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
    String key();
    int expireSeconds();
    TimeUnit timeUnit();
}

需要注意的是,这里只是保证了防重复提交,在下单的过程当中还会有一系列的线程不安全问题,比如重复扣减库存、网络不稳定导致支付失败等。如果对这些问题感兴趣,可以通过作者主页的联系方式进行学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值