|
@@ -14,6 +14,8 @@ import com.fs.live.service.*;
|
|
|
import com.fs.live.utils.LiveCompletionConfigUtils;
|
|
import com.fs.live.utils.LiveCompletionConfigUtils;
|
|
|
import com.fs.live.vo.LiveCompletionAnswerDetailVO;
|
|
import com.fs.live.vo.LiveCompletionAnswerDetailVO;
|
|
|
import com.fs.live.vo.LiveCompletionCouponAnswerResult;
|
|
import com.fs.live.vo.LiveCompletionCouponAnswerResult;
|
|
|
|
|
+import com.fs.live.vo.LiveCompletionCouponClaimItemVO;
|
|
|
|
|
+import com.fs.live.vo.LiveCompletionCouponClaimResult;
|
|
|
import com.fs.live.vo.LiveCompletionCouponConfigVO;
|
|
import com.fs.live.vo.LiveCompletionCouponConfigVO;
|
|
|
import com.fs.live.vo.LiveCompletionCouponInfoVO;
|
|
import com.fs.live.vo.LiveCompletionCouponInfoVO;
|
|
|
import com.fs.live.vo.LiveCompletionCouponNotifyResult;
|
|
import com.fs.live.vo.LiveCompletionCouponNotifyResult;
|
|
@@ -120,6 +122,11 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
|
|
|
List<LiveCompletionCouponInfoVO> couponInfos = loadCouponInfos(couponItems);
|
|
List<LiveCompletionCouponInfoVO> couponInfos = loadCouponInfos(couponItems);
|
|
|
result.setCoupons(couponInfos);
|
|
result.setCoupons(couponInfos);
|
|
|
result.setCoupon(couponInfos.isEmpty() ? null : couponInfos.get(0));
|
|
result.setCoupon(couponInfos.isEmpty() ? null : couponInfos.get(0));
|
|
|
|
|
+ if (couponInfos.isEmpty()) {
|
|
|
|
|
+ log.info("[完课优惠券] 配置券均无效或已删除, 跳过, liveId={}, userId={}, configured={}",
|
|
|
|
|
+ liveId, userId, couponItems != null ? couponItems.size() : 0);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if (!isWatchRateEligible(liveId, userId, watchDuration, config)) {
|
|
if (!isWatchRateEligible(liveId, userId, watchDuration, config)) {
|
|
|
log.info("[完课优惠券] 观看比例未达标(已在上层方法记录详情), liveId={}, userId={}", liveId, userId);
|
|
log.info("[完课优惠券] 观看比例未达标(已在上层方法记录详情), liveId={}, userId={}", liveId, userId);
|
|
@@ -146,8 +153,10 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
|
|
|
|
|
|
|
|
result.setShouldNotify(true);
|
|
result.setShouldNotify(true);
|
|
|
result.setQuestions(questions);
|
|
result.setQuestions(questions);
|
|
|
- log.info("[完课优惠券] 满足推送条件, liveId={}, userId={}, watchDuration={}, completionRate={}%",
|
|
|
|
|
- liveId, userId, watchDuration, config.getCompletionRate());
|
|
|
|
|
|
|
+ log.info("[完课优惠券] 满足推送条件, liveId={}, userId={}, watchDuration={}, completionRate={}%, couponKinds={}, totalSheets={}",
|
|
|
|
|
+ liveId, userId, watchDuration, config.getCompletionRate(),
|
|
|
|
|
+ couponInfos.size(),
|
|
|
|
|
+ couponInfos.stream().mapToInt(c -> c.getCount() != null && c.getCount() > 0 ? c.getCount() : 1).sum());
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("预检查完课优惠券弹窗失败, liveId={}, userId={}", liveId, userId, e);
|
|
log.error("预检查完课优惠券弹窗失败, liveId={}, userId={}", liveId, userId, e);
|
|
|
}
|
|
}
|
|
@@ -251,7 +260,7 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public LiveCouponUser claimCompletionCoupon(LiveCompletionCouponClaimParam param, Long userId) {
|
|
|
|
|
|
|
+ public LiveCompletionCouponClaimResult claimCompletionCoupon(LiveCompletionCouponClaimParam param, Long userId) {
|
|
|
if (param == null || param.getLiveId() == null) {
|
|
if (param == null || param.getLiveId() == null) {
|
|
|
throw new BaseException("参数错误");
|
|
throw new BaseException("参数错误");
|
|
|
}
|
|
}
|
|
@@ -277,23 +286,93 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
|
|
|
if (live == null) {
|
|
if (live == null) {
|
|
|
throw new BaseException("直播不存在");
|
|
throw new BaseException("直播不存在");
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ List<LiveRewardCouponItem> couponItems = config.resolveCouponItems();
|
|
|
|
|
+ // 发券前统一预检:配置可用 + 总库存足够,避免多券场景扣到一半再回滚
|
|
|
|
|
+ assertCouponsReadyToClaim(liveId, couponItems);
|
|
|
|
|
+
|
|
|
List<LiveCouponUser> allCouponUsers = new ArrayList<>();
|
|
List<LiveCouponUser> allCouponUsers = new ArrayList<>();
|
|
|
- for (LiveRewardCouponItem item : config.resolveCouponItems()) {
|
|
|
|
|
|
|
+ List<LiveCompletionCouponClaimItemVO> claimItems = new ArrayList<>();
|
|
|
|
|
+ // 记录已扣库存,任一券失败时回补,避免事务回滚后 Redis 库存丢失
|
|
|
|
|
+ List<AbstractMap.SimpleEntry<LiveCouponIssue, Integer>> deductedStocks = new ArrayList<>();
|
|
|
|
|
+ try {
|
|
|
|
|
+ for (LiveRewardCouponItem item : couponItems) {
|
|
|
|
|
+ if (item == null || item.getCouponId() == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<LiveCouponUser> issued = issueCoupon(live, userId, item.getCouponId(),
|
|
|
|
|
+ item.resolveCount(), deductedStocks);
|
|
|
|
|
+ allCouponUsers.addAll(issued);
|
|
|
|
|
+ claimItems.add(buildClaimItem(issued));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (allCouponUsers.isEmpty()) {
|
|
|
|
|
+ throw new BaseException("优惠券发放失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ List<LiveConsoleOpLogUser> relations = new ArrayList<>();
|
|
|
|
|
+ for (LiveCouponUser couponUser : allCouponUsers) {
|
|
|
|
|
+ relations.add(new LiveConsoleOpLogUser(null, userId, liveId, couponUser.getId()));
|
|
|
|
|
+ }
|
|
|
|
|
+ liveConsoleOpLogService.bindOpLogUsers(opLog.getId(), liveId, relations);
|
|
|
|
|
+
|
|
|
|
|
+ LiveCompletionCouponClaimResult result = new LiveCompletionCouponClaimResult();
|
|
|
|
|
+ result.setOpLogId(opLog.getId());
|
|
|
|
|
+ result.setCouponUser(allCouponUsers.get(0));
|
|
|
|
|
+ result.setCouponUserId(allCouponUsers.get(0).getId());
|
|
|
|
|
+ result.setCouponUserIds(allCouponUsers.stream()
|
|
|
|
|
+ .map(LiveCouponUser::getId)
|
|
|
|
|
+ .collect(Collectors.toList()));
|
|
|
|
|
+ result.setCouponCount(allCouponUsers.size());
|
|
|
|
|
+ result.setCoupons(claimItems);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ } catch (RuntimeException e) {
|
|
|
|
|
+ for (AbstractMap.SimpleEntry<LiveCouponIssue, Integer> entry : deductedStocks) {
|
|
|
|
|
+ liveCouponIssueService.restoreStock(entry.getKey(), entry.getValue());
|
|
|
|
|
+ }
|
|
|
|
|
+ throw e;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 预检每张配置券:关联可用且总库存满足本次领取张数
|
|
|
|
|
+ */
|
|
|
|
|
+ private void assertCouponsReadyToClaim(Long liveId, List<LiveRewardCouponItem> couponItems) {
|
|
|
|
|
+ for (LiveRewardCouponItem item : couponItems) {
|
|
|
if (item == null || item.getCouponId() == null) {
|
|
if (item == null || item.getCouponId() == null) {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
- List<LiveCouponUser> issued = issueCoupon(live, userId, item.getCouponId(), item.resolveCount());
|
|
|
|
|
- allCouponUsers.addAll(issued);
|
|
|
|
|
- }
|
|
|
|
|
- if (allCouponUsers.isEmpty()) {
|
|
|
|
|
- throw new BaseException("优惠券发放失败");
|
|
|
|
|
|
|
+ int grantCount = item.resolveCount();
|
|
|
|
|
+ LiveCoupon coupon = liveCouponService.selectLiveCouponById(item.getCouponId());
|
|
|
|
|
+ String title = coupon != null && StringUtils.isNotEmpty(coupon.getTitle())
|
|
|
|
|
+ ? coupon.getTitle()
|
|
|
|
|
+ : ("优惠券#" + item.getCouponId());
|
|
|
|
|
+
|
|
|
|
|
+ LiveCouponIssue couponIssue = liveCouponIssueService.selectIssueByLiveIdAndCouponId(liveId, item.getCouponId());
|
|
|
|
|
+ if (couponIssue == null || couponIssue.getStatus() == null || couponIssue.getStatus() != 1) {
|
|
|
|
|
+ throw new BaseException("「" + title + "」领取配置不可用或已领完");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!liveCouponIssueService.hasEnoughStock(couponIssue, grantCount)) {
|
|
|
|
|
+ throw new BaseException(grantCount > 1
|
|
|
|
|
+ ? "「" + title + "」库存不足,需领取" + grantCount + "张"
|
|
|
|
|
+ : "「" + title + "」已领完");
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- List<LiveConsoleOpLogUser> relations = new ArrayList<>();
|
|
|
|
|
- for (LiveCouponUser couponUser : allCouponUsers) {
|
|
|
|
|
- relations.add(new LiveConsoleOpLogUser(null, userId, liveId, couponUser.getId()));
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private LiveCompletionCouponClaimItemVO buildClaimItem(List<LiveCouponUser> issued) {
|
|
|
|
|
+ LiveCompletionCouponClaimItemVO vo = new LiveCompletionCouponClaimItemVO();
|
|
|
|
|
+ if (issued == null || issued.isEmpty()) {
|
|
|
|
|
+ vo.setCount(0);
|
|
|
|
|
+ vo.setCouponUserIds(Collections.emptyList());
|
|
|
|
|
+ return vo;
|
|
|
}
|
|
}
|
|
|
- liveConsoleOpLogService.bindOpLogUsers(opLog.getId(), liveId, relations);
|
|
|
|
|
- return allCouponUsers.get(0);
|
|
|
|
|
|
|
+ LiveCouponUser first = issued.get(0);
|
|
|
|
|
+ vo.setCouponId(first.getCouponId());
|
|
|
|
|
+ vo.setTitle(first.getCouponTitle());
|
|
|
|
|
+ vo.setCouponPrice(first.getCouponPrice());
|
|
|
|
|
+ vo.setUseMinPrice(first.getUseMinPrice());
|
|
|
|
|
+ vo.setCount(issued.size());
|
|
|
|
|
+ vo.setCouponUserIds(issued.stream().map(LiveCouponUser::getId).collect(Collectors.toList()));
|
|
|
|
|
+ return vo;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -586,7 +665,8 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
|
|
|
return getCompletionCouponConfig(live);
|
|
return getCompletionCouponConfig(live);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private List<LiveCouponUser> issueCoupon(Live live, Long userId, Long couponId, Integer couponCount) {
|
|
|
|
|
|
|
+ private List<LiveCouponUser> issueCoupon(Live live, Long userId, Long couponId, Integer couponCount,
|
|
|
|
|
+ List<AbstractMap.SimpleEntry<LiveCouponIssue, Integer>> deductedStocks) {
|
|
|
LiveCoupon coupon = liveCouponService.selectLiveCouponById(couponId);
|
|
LiveCoupon coupon = liveCouponService.selectLiveCouponById(couponId);
|
|
|
if (coupon == null) {
|
|
if (coupon == null) {
|
|
|
throw new BaseException("优惠券不存在");
|
|
throw new BaseException("优惠券不存在");
|
|
@@ -598,6 +678,17 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int grantCount = couponCount != null && couponCount > 0 ? couponCount : 1;
|
|
int grantCount = couponCount != null && couponCount > 0 ? couponCount : 1;
|
|
|
|
|
+ Long remain = liveCouponIssueService.tryDeductStock(couponIssue, grantCount);
|
|
|
|
|
+ if (remain == null) {
|
|
|
|
|
+ String title = StringUtils.isNotEmpty(coupon.getTitle()) ? coupon.getTitle() : ("优惠券#" + couponId);
|
|
|
|
|
+ throw new BaseException(grantCount > 1
|
|
|
|
|
+ ? "「" + title + "」库存不足,需领取" + grantCount + "张"
|
|
|
|
|
+ : "「" + title + "」已领完");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (deductedStocks != null && (couponIssue.getIsPermanent() == null || couponIssue.getIsPermanent() != 1)) {
|
|
|
|
|
+ deductedStocks.add(new AbstractMap.SimpleEntry<>(couponIssue, grantCount));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
Date now = new Date();
|
|
Date now = new Date();
|
|
|
List<LiveCouponUser> couponUsers = new ArrayList<>();
|
|
List<LiveCouponUser> couponUsers = new ArrayList<>();
|
|
|
for (int i = 0; i < grantCount; i++) {
|
|
for (int i = 0; i < grantCount; i++) {
|
|
@@ -638,8 +729,8 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
|
|
|
record.setCreateBy(String.valueOf(userId));
|
|
record.setCreateBy(String.valueOf(userId));
|
|
|
liveRewardRecordService.insertLiveRewardRecord(record);
|
|
liveRewardRecordService.insertLiveRewardRecord(record);
|
|
|
|
|
|
|
|
- log.info("完课优惠券发放成功, liveId={}, userId={}, couponId={}, count={}",
|
|
|
|
|
- live.getLiveId(), userId, couponId, grantCount);
|
|
|
|
|
|
|
+ log.info("完课优惠券发放成功, liveId={}, userId={}, couponId={}, count={}, remain={}",
|
|
|
|
|
+ live.getLiveId(), userId, couponId, grantCount, remain);
|
|
|
return couponUsers;
|
|
return couponUsers;
|
|
|
}
|
|
}
|
|
|
|
|
|