注: 只是单纯的记录代码: 来源小D课堂
- 使用原生的方式实现,要点在于保证原子性操作
/**
* 1. 校验该优惠卷是否存在
* 2. 检验优惠卷是否可以领取: 时间、库存、超过限制
* 3. 扣减库存
* 4. 保存领卷记录
*
* @param couponId
* @param promotion
* @return
*/
@Override
public ApiResult addCoupon(long couponId, CategoryCouponEnums promotion) {
//获取当前用户信息
LoginUser loginUser = LoginInterceptor.threadLocal.get();
String uuid = CommonUtil.generateUUID();
String key = "lock:coupon:"+couponId;
//加锁设置过期时间
boolean lockResult = redisTemplate.opsForValue().setIfAbsent(key,uuid, Duration.ofSeconds(30));
if (lockResult){
log.info("加锁成功:{}",couponId);
try{
//执行正常逻辑
//判断优惠卷是否存在
CouponDO couponDO = this.getOne(new QueryWrapper<CouponDO>()
.eq("id",couponId)
.eq("category",promotion.name())
.eq("publish",CouponPublishEnums.PUBLISH.name()));
if (couponDO==null){
throw new BizException(BaseCodeEnums.COUPON_NOT_EXITS);
}
//检查优惠卷是否可用
this.checkCoupon(couponDO,loginUser);
//构建领卷记录
CouponRecordDO couponRecordDO = new CouponRecordDO();
BeanUtils.copyProperties(couponDO,couponRecordDO);
couponRecordDO.setCreateTime(new Date());
couponRecordDO.setUseState(CouponUserStateEnums.NEW.name());
couponRecordDO.setUserId(loginUser.getId());
couponRecordDO.setUserName(loginUser.getName());
couponRecordDO.setId(null);
couponRecordDO.setCouponId(couponId);
//扣减库存
int stock = couponRecordService.reduceStock(couponId);
if (stock==1){
couponRecordService.save(couponRecordDO);
}else {
log.warn("发放优惠卷失败:{},用户:{}",couponDO,loginUser);
throw new BizException(BaseCodeEnums.COUPON_NO_STOCK);
}
}finally {
//使用lua脚本来保证删除锁的原子性操作
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Integer execute = redisTemplate.execute(new DefaultRedisScript<>(script, Integer.class), Arrays.asList(key), uuid);
log.info("解除锁:{}",execute);
}
}else{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
log.error("自旋失败");
e.printStackTrace();
}
addCoupon(couponId,promotion);
}
return ApiResult.buildSuccess();
}
- 使用redisson实现
- 引入jar包
<!--redisson分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
- 代码
/**
* 1. 校验该优惠卷是否存在
* 2. 检验优惠卷是否可以领取: 时间、库存、超过限制
* 3. 扣减库存
* 4. 保存领卷记录
*
* @param couponId
* @param promotion
* @return
*/
@Override
public ApiResult addCoupon(long couponId, CategoryCouponEnums promotion) {
//获取当前用户信息
LoginUser loginUser = LoginInterceptor.threadLocal.get();
String key = "lock:coupon:"+couponId;
RLock lock = redissonClient.getLock(key);
lock.lock();
log.info("领卷接口加锁成功:{}",couponId);
try{
//执行正常逻辑
//判断优惠卷是否存在
CouponDO couponDO = this.getOne(new QueryWrapper<CouponDO>()
.eq("id",couponId)
.eq("category",promotion.name())
.eq("publish",CouponPublishEnums.PUBLISH.name()));
if (couponDO==null){
throw new BizException(BaseCodeEnums.COUPON_NOT_EXITS);
}
//检查优惠卷是否可用
this.checkCoupon(couponDO,loginUser);
//构建领卷记录
CouponRecordDO couponRecordDO = new CouponRecordDO();
BeanUtils.copyProperties(couponDO,couponRecordDO);
couponRecordDO.setCreateTime(new Date());
couponRecordDO.setUseState(CouponUserStateEnums.NEW.name());
couponRecordDO.setUserId(loginUser.getId());
couponRecordDO.setUserName(loginUser.getName());
couponRecordDO.setId(null);
couponRecordDO.setCouponId(couponId);
//扣减库存
int stock = couponRecordService.reduceStock(couponId);
if (stock==1){
couponRecordService.save(couponRecordDO);
}else {
log.warn("发放优惠卷失败:{},用户:{}",couponDO,loginUser);
throw new BizException(BaseCodeEnums.COUPON_NO_STOCK);
}
}finally {
lock.unlock();
log.info("解除锁:{}",couponId);
}
return ApiResult.buildSuccess();
}