|
|
@@ -243,6 +243,16 @@ public class LiveCouponServiceImpl implements ILiveCouponService
|
|
|
}
|
|
|
|
|
|
if (isShow) {
|
|
|
+ // 如果之前是"已领完"状态(-1),重新上架时恢复为正常状态
|
|
|
+ if (couponIssue.getStatus() != null && couponIssue.getStatus() == -1) {
|
|
|
+ couponIssue.setStatus(1);
|
|
|
+ liveCouponIssueService.updateLiveCouponIssue(couponIssue);
|
|
|
+ // 重新初始化Redis库存缓存
|
|
|
+ redisCache.setCacheObject(
|
|
|
+ String.format(LiveKeysConstant.LIVE_COUPON_NUM, couponIssueId),
|
|
|
+ couponIssue.getRemainCount().intValue(),
|
|
|
+ 30, java.util.concurrent.TimeUnit.MINUTES);
|
|
|
+ }
|
|
|
// updateChangeShow 会自动收起其它券,仅保留当前展示的一张
|
|
|
liveCouponMapper.updateChangeShow(liveId, couponIssueId);
|
|
|
LiveConsoleOpLog opLog = liveConsoleOpLogService.saveCouponShowLog(
|
|
|
@@ -306,8 +316,15 @@ public class LiveCouponServiceImpl implements ILiveCouponService
|
|
|
@Override
|
|
|
@Transactional
|
|
|
public R claimCoupon(CouponPO coupon) {
|
|
|
- LiveCouponIssue issue = liveCouponMapper.selectLiveCouponIssueByLiveIdAndCouponId(coupon.getLiveId(), coupon.getCouponIssueId());
|
|
|
- if (coupon == null || ObjectUtils.isEmpty(issue)||issue.getStatus() != 1) {
|
|
|
+ // 参数校验:先判空再使用,避免 NPE
|
|
|
+ if (coupon == null || coupon.getLiveId() == null
|
|
|
+ || coupon.getCouponIssueId() == null || coupon.getUserId() == null) {
|
|
|
+ return R.error("领取参数不完整!");
|
|
|
+ }
|
|
|
+
|
|
|
+ LiveCouponIssue issue = liveCouponMapper.selectLiveCouponIssueByLiveIdAndCouponId(
|
|
|
+ coupon.getLiveId(), coupon.getCouponIssueId());
|
|
|
+ if (ObjectUtils.isEmpty(issue) || issue.getStatus() == null || issue.getStatus() != 1) {
|
|
|
return R.error("优惠券不存在或者已下架!");
|
|
|
}
|
|
|
|
|
|
@@ -316,67 +333,73 @@ public class LiveCouponServiceImpl implements ILiveCouponService
|
|
|
return R.error("优惠券配置不存在!");
|
|
|
}
|
|
|
|
|
|
- Long decrement = redisCache.decrement(String.format(LiveKeysConstant.LIVE_COUPON_NUM , coupon.getCouponIssueId()));
|
|
|
+ // Redis 预扣减库存,key 必须与展示/初始化时保持一致
|
|
|
+ String stockKey = String.format(LiveKeysConstant.LIVE_COUPON_NUM, coupon.getCouponIssueId());
|
|
|
+ Long decrement = redisCache.decrement(stockKey);
|
|
|
|
|
|
- if (decrement < 0L) {
|
|
|
+ // 库存不足:回补 Redis 多扣的 1,并标记已领完
|
|
|
+ if (decrement == null || decrement < 0L) {
|
|
|
+ if (decrement != null) {
|
|
|
+ redisCache.incr(stockKey, 1L);
|
|
|
+ }
|
|
|
issue.setStatus(-1);
|
|
|
issue.setRemainCount(0L);
|
|
|
- redisCache.deleteObject(String.valueOf(issue.getId()));
|
|
|
liveCouponIssueService.updateLiveCouponIssue(issue);
|
|
|
return R.error("此优惠券已领完");
|
|
|
}
|
|
|
- Date now = DateUtils.getNowDate();
|
|
|
- LiveCouponIssueUser record = new LiveCouponIssueUser();
|
|
|
- record.setUserId(coupon.getUserId());
|
|
|
- record.setIssueId(issue.getId());
|
|
|
- record.setCreateTime(now);
|
|
|
- record.setUpdateTime(now);
|
|
|
- record.setIsDel(0);
|
|
|
- record.setGoodsId(coupon.getGoodsId());
|
|
|
-
|
|
|
-
|
|
|
- LiveCouponUser userRecord = new LiveCouponUser();
|
|
|
- userRecord.setCouponId(liveCoupon.getCouponId());
|
|
|
- userRecord.setUserId(Math.toIntExact(coupon.getUserId()));
|
|
|
- userRecord.setCouponTitle(liveCoupon.getTitle());
|
|
|
- userRecord.setCouponPrice(liveCoupon.getCouponPrice());
|
|
|
- userRecord.setUseMinPrice(liveCoupon.getUseMinPrice());
|
|
|
- userRecord.setCreateTime(now);
|
|
|
- userRecord.setUpdateTime(now);
|
|
|
- userRecord.setStatus(0);
|
|
|
- userRecord.setLimitTime(DateUtils.addDays(now, Math.toIntExact(liveCoupon.getCouponTime())));
|
|
|
- userRecord.setIsFail(1);
|
|
|
- userRecord.setIsDel(0);
|
|
|
- userRecord.setIsDel(0);
|
|
|
- userRecord.setGoodsId(coupon.getGoodsId());
|
|
|
- userRecord.setType("live-"+coupon.getLiveId());
|
|
|
- userRecord.setVerifyCode(generateVerifyCode());
|
|
|
- //库存 remain_count
|
|
|
- issue.setRemainCount(issue.getRemainCount()-1);
|
|
|
- liveCouponIssueService.updateLiveCouponIssue(issue);
|
|
|
-
|
|
|
- liveCouponUserService.insertLiveCouponUser(userRecord);
|
|
|
- liveCouponIssueUserService.insertLiveCouponIssueUser(record);
|
|
|
-
|
|
|
- Long opLogId = coupon.getOpLogId();
|
|
|
- if (opLogId == null) {
|
|
|
- opLogId = liveConsoleOpLogService.resolveLatestCouponShowOpLogId(coupon.getLiveId(), issue.getId());
|
|
|
- }
|
|
|
- liveConsoleOpLogService.bindOpLogUser(
|
|
|
- opLogId, coupon.getLiveId(), coupon.getUserId(), userRecord.getId());
|
|
|
-
|
|
|
- // 更新优惠卷数量
|
|
|
- if (issue.getRemainCount() > 0) {
|
|
|
- LiveCouponIssue liveCouponIssue = new LiveCouponIssue();
|
|
|
- liveCouponIssue.setId(issue.getId());
|
|
|
- liveCouponIssue.setRemainCount(issue.getRemainCount() - 1);
|
|
|
- liveCouponIssueMapper.updateLiveCouponIssue(liveCouponIssue);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- return R.ok("恭喜您抢到优惠券")
|
|
|
- .put("opLogId", opLogId)
|
|
|
- .put("couponUserId", userRecord.getId());
|
|
|
+
|
|
|
+ // Redis 扣减成功,执行 DB 操作;若 DB 异常则回补 Redis 库存
|
|
|
+ try {
|
|
|
+ Date now = DateUtils.getNowDate();
|
|
|
+ LiveCouponIssueUser record = new LiveCouponIssueUser();
|
|
|
+ record.setUserId(coupon.getUserId());
|
|
|
+ record.setIssueId(issue.getId());
|
|
|
+ record.setCreateTime(now);
|
|
|
+ record.setUpdateTime(now);
|
|
|
+ record.setIsDel(0);
|
|
|
+ record.setGoodsId(coupon.getGoodsId());
|
|
|
+
|
|
|
+ LiveCouponUser userRecord = new LiveCouponUser();
|
|
|
+ userRecord.setCouponId(liveCoupon.getCouponId());
|
|
|
+ userRecord.setUserId(Math.toIntExact(coupon.getUserId()));
|
|
|
+ userRecord.setCouponTitle(liveCoupon.getTitle());
|
|
|
+ userRecord.setCouponPrice(liveCoupon.getCouponPrice());
|
|
|
+ userRecord.setUseMinPrice(liveCoupon.getUseMinPrice());
|
|
|
+ userRecord.setCreateTime(now);
|
|
|
+ userRecord.setUpdateTime(now);
|
|
|
+ userRecord.setStatus(0);
|
|
|
+ userRecord.setLimitTime(DateUtils.addDays(now, Math.toIntExact(liveCoupon.getCouponTime())));
|
|
|
+ userRecord.setIsFail(1);
|
|
|
+ userRecord.setIsDel(0);
|
|
|
+ userRecord.setGoodsId(coupon.getGoodsId());
|
|
|
+ userRecord.setType("live-" + coupon.getLiveId());
|
|
|
+ userRecord.setVerifyCode(generateVerifyCode());
|
|
|
+
|
|
|
+ // 基于 Redis 扣减后的值同步 DB 库存,避免读后写并发丢失更新
|
|
|
+ issue.setRemainCount(decrement);
|
|
|
+ if (decrement == 0L) {
|
|
|
+ issue.setStatus(-1);
|
|
|
+ }
|
|
|
+ liveCouponIssueService.updateLiveCouponIssue(issue);
|
|
|
+
|
|
|
+ liveCouponUserService.insertLiveCouponUser(userRecord);
|
|
|
+ liveCouponIssueUserService.insertLiveCouponIssueUser(record);
|
|
|
+
|
|
|
+ Long opLogId = coupon.getOpLogId();
|
|
|
+ if (opLogId == null) {
|
|
|
+ opLogId = liveConsoleOpLogService.resolveLatestCouponShowOpLogId(coupon.getLiveId(), issue.getId());
|
|
|
+ }
|
|
|
+ liveConsoleOpLogService.bindOpLogUser(
|
|
|
+ opLogId, coupon.getLiveId(), coupon.getUserId(), userRecord.getId());
|
|
|
+
|
|
|
+ return R.ok("恭喜您抢到优惠券")
|
|
|
+ .put("opLogId", opLogId)
|
|
|
+ .put("couponUserId", userRecord.getId());
|
|
|
+ } catch (Exception e) {
|
|
|
+ // DB 操作失败,回补 Redis 库存,避免库存丢失;重新抛出让事务回滚
|
|
|
+ redisCache.incr(stockKey, 1L);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
@Override
|