分布式锁实现用户锁

用户锁的作用

秒杀、支付等场景,用户频繁点击按钮,会造成同一时刻调用多次接口【第一次请求接口还没响应数据,用户又进行了第二次请求】,造成数据异常和网络拥堵。添加用户锁,在用户第二次点击按钮时,拦击用户请求。限制用户在操作未完成前不能再进行下一次相同操作

1.主要组成

Dul:用户锁注解,自定义锁注解,然后给需要加锁的方法加上此注解
DistributedUserLock:锁接口
RedisDistributedUserLock:分布式锁实现类
DistributedUserLockAspect:切面类【核心】

自定义锁注解,利用切面给所有加注解的方法加分布式锁防止用户重复点击按钮。

2.关键代码分析:

DistributedUserLockAspect:用redis的setIfAbsent 进行加锁,防止死锁,10秒后自动释放锁。

@Around("@annotation(com.nascent.ecrp.mall.common.distribute.Dul)")
    public Object distributedLock(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        if (request == null) {
            return proceedingJoinPoint.proceed();
        }
        String token = request.getHeader("token");
        if (StringUtils.isBlank(token)) {
            return proceedingJoinPoint.proceed();
        }
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String lockKey = "USER_LOCK_KEY_OF_" + method.getDeclaringClass().getName() + "." + method.getName();

        Dul distributedKey = method.getAnnotation(Dul.class);

        int lockTime = distributedKey.lockTimeSeconds();
        TimeUnit timeUnit = distributedKey.timeUnit();

        // USER_LOCK_KEY_OF_+类名+方法名+token作为redis的key
        lockKey += "_" + token;

        try {
            // 加锁成功,说明请求已经处理完,可以继续访问
            if (distributedUserLock.lock(lockKey, lockTime, timeUnit)) {
                return proceedingJoinPoint.proceed();
            } else {
                // 加锁失败,说明第一次的请求还没处理完
                throw new WmDefinitelyRuntimeException("操作过于频繁,请稍后再试");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new WmDefinitelyRuntimeException(e);

        } finally {
            distributedUserLock.unlock(lockKey);
        }
    }

3.全部代码

Dcl

/**
 * 分布式用户锁
 * 限制用户在操作未完成前不能再进行下一次相同操作
 *
 */
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dul {

    /**
     * 锁定的时间防止死锁,单位秒,默认10
     *
     * @return 同步锁定的时间
     */
    int lockTimeSeconds() default 10;

    /**
     * 时间单位
     *
     * @return 时间单位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}


DistributedUserLock

默认10s后自动释放锁

/**
 * 分布式用户锁
 */
public interface DistributedUserLock extends Lock {

    /**
     * 锁住用户操作
     *
     * @param lockKey       锁
     * @param expireSeconds 锁有效时间
     * @param timeUnit      时间单位
     * @return 是否获取成功
     * @throws InterruptedException 中断异常
     */
    boolean lock(String lockKey, int expireSeconds, TimeUnit timeUnit) throws InterruptedException;

    /**
     * 释放分布式锁
     *
     * @param lockKey   锁
     */
    void unlock(String lockKey);
}

RedisDistributedUserLock

/**
 * redis 分布式锁
 */
@SuppressWarnings({"UnusedReturnValue", "NullableProblems", "unused", "RedundantThrows"})
@Component
public class RedisDistributedUserLock implements DistributedUserLock {

    /**
     * 锁存在
     */
    private final static Long TYPE_LOCK = 1L;

    /**
     * 缓存
     */
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void lock() {
        throw new UnsupportedOperationException();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        throw new InterruptedException();
    }

    @Override
    public boolean tryLock() {
        throw new UnsupportedOperationException();
    }

    /**
     * 锁
     * @param lockKey       锁
     * @param expireSeconds 锁有效时间
     * @param timeUnit      时间单位
     * @return true 可以进行操作,false 锁已存在
     * @throws InterruptedException
     */
    @Override
    public boolean lock(String lockKey, int expireSeconds, TimeUnit timeUnit) throws InterruptedException {
       /* Object obj = redisTemplate.opsForValue().get(lockKey);
        if (obj != null) {
            return false;
        }
        redisTemplate.opsForValue().set(lockKey, TYPE_LOCK, expireSeconds, timeUnit);
        return true;*/
        // 加锁,如果key不存在,进行加锁,如果key已经存在返回加锁失败
       return  redisTemplate.opsForValue().setIfAbsent(lockKey, TYPE_LOCK, expireSeconds, timeUnit);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        throw new UnsupportedOperationException();
    }

    /**
     * 解锁
     * @param lockKey   锁
     */
    @Override
    public void unlock(String lockKey) {
        redisTemplate.delete(lockKey);
    }

    @Override
    public void unlock() {
        throw new UnsupportedOperationException();
    }

    @SuppressWarnings("NullableProblems")
    @Override
    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }

}

DistributedUserLockAspect

/**
 * 用户操作锁Aspect
 */
@Aspect
@Component
@Order(-1)
public class DistributedUserLockAspect {

    /**
     * 分布式锁
     */
    @Autowired
    private DistributedUserLock distributedUserLock;

    @Autowired
    private HttpServletRequest request;

    /**
     * @param proceedingJoinPoint proceedingJoinPoint
     */
    @Around("@annotation(com.nascent.ecrp.mall.common.distribute.Dul)")
    public Object distributedLock(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        if (request == null) {
            return proceedingJoinPoint.proceed();
        }
        String token = request.getHeader("token");
        if (StringUtils.isBlank(token)) {
            return proceedingJoinPoint.proceed();
        }
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String lockKey = "USER_LOCK_KEY_OF_" + method.getDeclaringClass().getName() + "." + method.getName();

        Dul distributedKey = method.getAnnotation(Dul.class);

        int lockTime = distributedKey.lockTimeSeconds();
        TimeUnit timeUnit = distributedKey.timeUnit();

        // USER_LOCK_KEY_OF_+类名+方法名+token作为redis的key
        lockKey += "_" + token;

        try {
            // 加锁成功,说明请求已经处理完,可以继续访问
            if (distributedUserLock.lock(lockKey, lockTime, timeUnit)) {
                return proceedingJoinPoint.proceed();
            } else {
                // 加锁失败,说明第一次的请求还没处理完
                throw new WmDefinitelyRuntimeException("操作过于频繁,请稍后再试");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new WmDefinitelyRuntimeException(e);

        } finally {
            distributedUserLock.unlock(lockKey);
        }
    }
}

秒杀接口添加锁注解

秒杀活动开始,用户点击按钮,进行上锁,lockTimeSeconds【默认10秒】内不能再点击,lockTimeSeconds秒后自动释放锁。

/**
     * 参与秒杀
     *
     * @return CommonResult
     */
    @Dul
    @Dcl(keyIndex = 0)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public CommonResult doSeckill(WmMarketingSeckillPostVo seckillPostVo) {
        String checkResult = checkGoodsSeckillInfo(seckillPostVo.getSeckillOrderInfoList(),seckillPostVo.getShopId());
		if(checkResult.equals(ErrorCode.SECKILL_ACTIVITY_UPDATE.getCode())){
			return new CommonResult().setFailed().setCode(ErrorCode.SECKILL_ACTIVITY_UPDATE.getCode()).setMsg("活动状态已变更,请重新下单");
		}
        List<JSONObject> seckillOrderList = new ArrayList<>();
        //下单校验
        List<SecKillBuyInfo> buyInfos = new ArrayList<>();
        SeckillOrderCheckRequest seckillOrderCheckRequest = new SeckillOrderCheckRequest();
        seckillOrderCheckRequest.setCustomerId(getCustomerId());
        seckillOrderCheckRequest.setShopId(seckillPostVo.getShopId());
        for(WmMarketingSeckillPostVo.SeckillOrderInfo seckillOrderInfo : seckillPostVo.getSeckillOrderInfoList()){
            SecKillBuyInfo secKillBuyInfo = new SecKillBuyInfo();
            secKillBuyInfo.setActivityId(seckillOrderInfo.getMarketingGuid());
            List<SeckillBuyGoodsInfo> seckillBuyGoodsInfos = new ArrayList<>();
            SeckillBuyGoodsInfo seckillBuyGoodsInfo = new SeckillBuyGoodsInfo();
            seckillBuyGoodsInfo.setGoodsId(seckillOrderInfo.getGoodsId());
            seckillBuyGoodsInfo.setGoodsLibId(seckillOrderInfo.getGoodsLibId());
            List<SeckillBuySkuInfo> seckillBuySkuInfos = new ArrayList<>();
            SeckillBuySkuInfo seckillBuySkuInfo = new SeckillBuySkuInfo();
            seckillBuySkuInfo.setGoodsSkuId(StringUtil.isBlank(seckillOrderInfo.getGoodsSkuId()) ? "0" : seckillOrderInfo.getGoodsSkuId());
            seckillBuySkuInfo.setBuyCount(seckillOrderInfo.getGoodsNum());
            seckillBuySkuInfos.add(seckillBuySkuInfo);
            seckillBuyGoodsInfo.setSkuInfos(seckillBuySkuInfos);
            seckillBuyGoodsInfos.add(seckillBuyGoodsInfo);
            secKillBuyInfo.setGoodsList(seckillBuyGoodsInfos);
            buyInfos.add(secKillBuyInfo);
        }
        seckillOrderCheckRequest.setBuyInfos(buyInfos);
        seckillOrderCheckRequest.setGroupId(getGroupId());
        SeckillOrderCheckResponse seckillOrderCheckResponse = OpenPlatformClient.exec(getGroupId(), seckillOrderCheckRequest);
        log.info("【调用中台下单校验接口响应结果】seckillOrderCheckResponse="+JSON.toJSONString(seckillOrderCheckResponse));
        if(!seckillOrderCheckResponse.success&&seckillOrderCheckResponse.getCode().equals("50402")){
            log.info("【秒杀下单已达限购上限】");
            return new CommonResult().setFailed().setCode("50402").setMsg("已达限购上限");
        }
        if(!seckillOrderCheckResponse.success&&seckillOrderCheckResponse.getCode().equals("50404")){
            log.info("【秒杀剩余库存不足】");
            return new CommonResult().setFailed().setCode("50404").setMsg("剩余库存不足");
        }
        if(!seckillOrderCheckResponse.success&&seckillOrderCheckResponse.getCode().equals("50005")){
            log.info("【秒杀活动已结束】");
            return new CommonResult().setFailed().setCode("50005").setMsg("活动已结束");
        }
        AssertUtil.assertTrue(seckillOrderCheckResponse.getSuccess()&&seckillOrderCheckResponse.getResult().isSeckillSuccess(),"秒杀活动校验失败");
        Map<String,ActivitySeckillCheckInfo> activityCheckInfoMap = new HashMap<>();
        for(ActivitySeckillCheckInfo activitySeckillCheckInfo : seckillOrderCheckResponse.getResult().getCheckInfos()){
            Long goodsId = activitySeckillCheckInfo.getItemInfos().get(0).getGoodsId();
            Long goodsLibId = activitySeckillCheckInfo.getItemInfos().get(0).getGoodsLibId();
            String skuId = activitySeckillCheckInfo.getItemInfos().get(0).getSkuInfos().get(0).getSkuId();//无sku也会返回到sku级,skuid为0
            activityCheckInfoMap.put(activitySeckillCheckInfo.getActivityId()+"_"+goodsLibId+"_"+goodsId+"_"+skuId,activitySeckillCheckInfo);
        }
        Map<String, ItemDetailsInfo> itemInfoMap = getGoodsInfos(seckillPostVo.getShopId(),seckillPostVo.getSeckillOrderInfoList());
        Integer expireMinute = seckillOrderCheckResponse.getResult().getExpireMinute();
        for(WmMarketingSeckillPostVo.SeckillOrderInfo seckillOrderInfo :seckillPostVo.getSeckillOrderInfoList()){
            ItemDetailsInfo itemInfo = itemInfoMap.get(seckillOrderInfo.getGoodsId()+"_"+seckillOrderInfo.getGoodsLibId());
            Long marketGuid = seckillOrderInfo.getMarketingGuid();
            Long goodsId = seckillOrderInfo.getGoodsId();
            Long goodsLibId = seckillOrderInfo.getGoodsLibId();
            String goodsSkuId = seckillOrderInfo.getGoodsSkuId()==null?"0":seckillOrderInfo.getGoodsSkuId();//无sku也会返回到sku级,skuid为0
            ActivitySeckillCheckInfo activitySeckillCheckInfo = activityCheckInfoMap.get(marketGuid+"_"+goodsLibId+"_"+goodsId+"_"+goodsSkuId);
            String activityName = activitySeckillCheckInfo.getActivityName();
            //秒杀价
            BigDecimal price = itemInfo.getPrice();
            for(ActivitySeckillCheckItemInfo activitySeckillCheckItemInfo : activitySeckillCheckInfo.getItemInfos()){
                if(activitySeckillCheckItemInfo.getGoodsId().longValue() == seckillOrderInfo.getGoodsId().longValue() && activitySeckillCheckItemInfo.getGoodsLibId().longValue() == seckillOrderInfo.getGoodsLibId().longValue()){
                    for(ActivitySeckillCheckSkuInfo seckillCheckSkuInfo : activitySeckillCheckItemInfo.getSkuInfos()){
                        if(seckillCheckSkuInfo.getSkuId().equals("0")||seckillCheckSkuInfo.getSkuId().equals(seckillOrderInfo.getGoodsSkuId())){
                            price = seckillCheckSkuInfo.getPrice();
                        }
                    }
                }
            }
            JSONObject orderJson = createOrderDetail(marketGuid,activityName,expireMinute,seckillOrderInfo.getGoodsNum(), goodsId, goodsSkuId, price, itemInfo);
            seckillOrderList.add(orderJson);
        }
        return new CommonResult(seckillOrderList);
    }
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值