jzo2o我是如何防止用户重复下单的

这里用到了aop技术 分布式锁 简单来说就是 第一个请求成功获取锁后,后续的请求会因为无法获取锁而被阻塞或直接拒绝 这很好的解决了重复提交的问题
举个很简单的例子来说 假设用户 A 快速点击了两次提交按钮。
但是管理员发现这三次请求都对应同一个钥匙(比如 "order:123"),因此只会允许第一个请求进入,后续的请求会被拒绝。
管理员就相当于是切面逻辑 这时基于注解的切点表达式 被这个注解标记的方法 切面会在方法执行前后自动处理加锁和解锁的逻辑 下面的注解不仅仅可以标记方法也可以传参 下面都是可以传进去的参数
比如说一个方法可以这样 比如说这时有两个请求A和B

@Lock(formatter = "order:{orderId}", time = 5, unit = TimeUnit.SECONDS, block = false) public void submitOrder(String orderId) { // 提交订单的逻辑 }

`@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Lock {

/**
 * 加锁key的表达式,支持表达式
 */
String formatter();

/**
 * 加锁时长
 */
long time() default 5;

/**
 * 阻塞超时时间,默认2分钟,当block为true的时候生效
 */
long waitTime() default 120;

/**
 * 加锁时间单位
 */
TimeUnit unit() default TimeUnit.SECONDS;

/**
 * 方法访问完后要不要解锁,默认自动解锁
 */
boolean unlock() default true;

/**
 * 如果设定了true将在获取锁时阻塞等待waitTime时间
 */
boolean block() default false;

/**
 * 是否启用自动续期,如果使用自动续期则unlock必须设置为true
 */
boolean startDog() default false;

}
@Aspect
public class LockAspect {

private final RedissonClient redissonClient;

public LockAspect(RedissonClient redissonClient) {
    this.redissonClient = redissonClient;
}

@Around("@annotation(lock)")
public Object handleLock(ProceedingJoinPoint pjp, Lock lock) throws Throwable {
    //锁key的格式化字符,此字符串中有spEL表达式
    String formatter = lock.formatter();
    Method method = AspectUtils.getMethod(pjp);
    Object[] args = pjp.getArgs();
    //得到锁的key
    String redisKey = AspectUtils.parse(formatter, method, args);
    //获取锁阻塞等待的时间,如果是0表示去尝试获取锁,如果获取不到则结束
    long waitTime = 0;
    //阻塞等待获取锁
    if (lock.block()) {
        //根据时间单位转换成ms
        waitTime = lock.waitTime();
    }
    //加锁时长
    long time = lock.time();
    //启动看门狗自动续期
    if(lock.startDog()){
        time = -1;
        //如果设置自动续期必须在方法执行后释放锁
        if(!lock.unlock()){
            throw new BadRequestException(ErrorInfo.Msg.REQUEST_PARAM_ILLEGAL);
        }
    }
    //得到锁对象
    RLock rLock = redissonClient.getLock(redisKey);
    //尝试加锁
    boolean success = rLock.tryLock(waitTime,time, lock.unit());
    if (!success && !lock.block()) {
        //未阻塞要求的情况下未得到锁
        throw new BadRequestException(ErrorInfo.Msg.REQUEST_OPERATE_FREQUENTLY);
    }
    if (!success) {
        //阻塞情况下未得到锁,请求超时
        throw new RequestTimeoutException(ErrorInfo.Msg.REQUEST_TIME_OUT);
    }

    try {
        return pjp.proceed();
    } finally {
        if (lock.unlock()) {
            rLock.unlock();
        }
    }

}`

第一步:解析锁 key
String redisKey = AspectUtils.parse(formatter, method, args);
根据注解中的 formatter = "order:{orderId}" 和参数 orderId = 123,生成锁的 key:redisKey = "order:123"
第二步:尝试获取锁
boolean success = rLock.tryLock(waitTime, time, lock.unit());
Redis 中的分布式锁会检查是否已经有线程持有 "order:123" 这个锁。
当前没有其他线程持有这个锁,因此请求 A 成功获取了锁
第三步:执行目标方法
return pjp.proceed();
第四步释放锁
if (lock.unlock()) {
rLock.unlock();
}
这时请求B 比如说是连续点击第二下的那个订单 他也会进入进来并且解析 尝试获取锁 但这时A已经进去了 无法获取 我们设置的规则是 因为 block = false,所以请求 B 不会等待,而是直接抛出异常:
,返回错误信息:“请求过于频繁”。

posted @ 2025-04-01 23:24  JavaYzz  阅读(10)  评论(0)    收藏  举报