Переглянути джерело

1、显示观看时间奖励倒计时:进入直播间自动开始计时。后台可设置时长达到标准,自动得奖励,后台绑定优惠券,可以得奖励(观看奖励劵)多张。并获得参于答题资格。

yys 2 тижнів тому
батько
коміт
2b4cbbae96

+ 70 - 45
fs-live-app/src/main/java/com/fs/live/task/Task.java

@@ -649,8 +649,9 @@ public class Task {
                     return 0;
                 }
                 Long actionCouponId = Long.parseLong(actionCouponIdStr);
+                int actionCouponCount = resolveActionCouponCount(config);
                 LiveCoupon watchRewardCoupon = liveCouponService.selectLiveCouponById(actionCouponId);
-                List<LiveConsoleOpLogUser> couponRelations = bindCouponToUsers(live, userIds, actionCouponId, false);
+                List<LiveConsoleOpLogUser> couponRelations = bindCouponToUsers(live, userIds, actionCouponId, false, actionCouponCount);
                 if (!couponRelations.isEmpty()) {
                     LiveConsoleOpLog watchCouponOpLog = liveConsoleOpLogService.saveLog(
                             liveId,
@@ -660,11 +661,15 @@ public class Task {
                             resolveWatchRewardCouponBizName(watchRewardCoupon, actionCouponId, couponRelations.size())
                     );
                     liveConsoleOpLogService.bindOpLogUsers(watchCouponOpLog.getId(), liveId, couponRelations);
-                    couponRelations.forEach(relation -> sendCouponRewardMessage(
-                            liveId, relation.getUserId(), watchRewardCoupon, watchCouponOpLog));
-                    log.info("{} 观看奖励优惠券发放完成: liveId={}, 用户数={}",
-                            LOG_PREFIX, liveId, couponRelations.size());
-                    return couponRelations.size();
+                    Set<Long> notifiedUserIds = new HashSet<>();
+                    couponRelations.forEach(relation -> {
+                        if (relation.getUserId() != null && notifiedUserIds.add(relation.getUserId())) {
+                            sendCouponRewardMessage(liveId, relation.getUserId(), watchRewardCoupon, watchCouponOpLog);
+                        }
+                    });
+                    log.info("{} 观看奖励优惠券发放完成: liveId={}, 用户数={}, 每人张数={}",
+                            LOG_PREFIX, liveId, notifiedUserIds.size(), actionCouponCount);
+                    return notifiedUserIds.size();
                 }
                 log.warn("{} 观看奖励优惠券发放无成功用户: liveId={}, couponId={}",
                         LOG_PREFIX, liveId, actionCouponId);
@@ -685,10 +690,12 @@ public class Task {
      * @param userIds       待发放用户ID列表
      * @param couponId      优惠券ID
      * @param sendWebSocket 是否立即发送 WebSocket(定时任务由外层统一发送并附带 opLog)
+     * @param couponCount   每人领取张数
      * @return 发放成功的用户关联列表(含 userId、couponUserId,供写入 live_console_op_log_user)
      */
-    private List<LiveConsoleOpLogUser> bindCouponToUsers(Live live, List<Long> userIds, Long couponId, boolean sendWebSocket) {
+    private List<LiveConsoleOpLogUser> bindCouponToUsers(Live live, List<Long> userIds, Long couponId, boolean sendWebSocket, int couponCount) {
         List<LiveConsoleOpLogUser> relations = new ArrayList<>();
+        int grantCount = couponCount > 0 ? couponCount : 1;
         try {
             // 查询优惠券信息
             LiveCoupon coupon = liveCouponService.selectLiveCouponById(couponId);
@@ -714,40 +721,46 @@ public class Task {
 
             for (Long userId : userIds) {
                 try {
-                    // 创建用户优惠券记录
-                    LiveCouponUser couponUser = new LiveCouponUser();
-                    couponUser.setCouponId(couponId);
-                    couponUser.setUserId(userId.intValue());
-                    couponUser.setCouponTitle(coupon.getTitle());
-                    couponUser.setCouponPrice(coupon.getCouponPrice());
-                    couponUser.setUseMinPrice(coupon.getUseMinPrice());
-
-                    // 计算优惠券过期时间
-                    if (couponIssue.getLimitTime() != null) {
-                        couponUser.setLimitTime(couponIssue.getLimitTime());
-                    } else if (coupon.getCouponTime() != null) {
-                        // 如果没有设置领取结束时间,使用优惠券有效期限计算
-                        java.util.Calendar cal = java.util.Calendar.getInstance();
-                        cal.setTime(now);
-                        cal.add(java.util.Calendar.DATE, coupon.getCouponTime().intValue());
-                        couponUser.setLimitTime(cal.getTime());
-                    }
+                    boolean userGranted = false;
+                    for (int i = 0; i < grantCount; i++) {
+                        // 创建用户优惠券记录
+                        LiveCouponUser couponUser = new LiveCouponUser();
+                        couponUser.setCouponId(couponId);
+                        couponUser.setUserId(userId.intValue());
+                        couponUser.setCouponTitle(coupon.getTitle());
+                        couponUser.setCouponPrice(coupon.getCouponPrice());
+                        couponUser.setUseMinPrice(coupon.getUseMinPrice());
+
+                        // 计算优惠券过期时间
+                        if (couponIssue.getLimitTime() != null) {
+                            couponUser.setLimitTime(couponIssue.getLimitTime());
+                        } else if (coupon.getCouponTime() != null) {
+                            // 如果没有设置领取结束时间,使用优惠券有效期限计算
+                            java.util.Calendar cal = java.util.Calendar.getInstance();
+                            cal.setTime(now);
+                            cal.add(java.util.Calendar.DATE, coupon.getCouponTime().intValue());
+                            couponUser.setLimitTime(cal.getTime());
+                        }
 
-                    couponUser.setType("3"); // 获取方式:3-观看奖励
-                    couponUser.setStatus(0); // 状态:0-未核销
-                    couponUser.setIsFail(0); // 是否有效:0-有效
-                    couponUser.setIsDel(0);
-                    couponUser.setCreateTime(now);
+                        couponUser.setType("3"); // 获取方式:3-观看奖励
+                        couponUser.setStatus(0); // 状态:0-未核销
+                        couponUser.setIsFail(0); // 是否有效:0-有效
+                        couponUser.setIsDel(0);
+                        couponUser.setCreateTime(now);
 
-                    liveCouponUserService.insertLiveCouponUser(couponUser);
+                        liveCouponUserService.insertLiveCouponUser(couponUser);
 
-                    // 保存奖励记录 - 优惠券类型的sourceId为couponId
-                    saveUserRewardRecord(live, Collections.singletonList(userId), coupon.getCouponPrice(), 3, couponId);
+                        relations.add(new LiveConsoleOpLogUser(null, userId, live.getLiveId(), couponUser.getId()));
+                        userGranted = true;
+                    }
 
-                    relations.add(new LiveConsoleOpLogUser(null, userId, live.getLiveId(), couponUser.getId()));
+                    if (userGranted) {
+                        // 保存奖励记录 - 优惠券类型的sourceId为couponId(按用户记一条)
+                        saveUserRewardRecord(live, Collections.singletonList(userId), coupon.getCouponPrice(), 3, couponId);
 
-                    if (sendWebSocket) {
-                        sendCouponRewardMessage(live.getLiveId(), userId, coupon);
+                        if (sendWebSocket) {
+                            sendCouponRewardMessage(live.getLiveId(), userId, coupon);
+                        }
                     }
 
                 } catch (Exception e) {
@@ -755,8 +768,8 @@ public class Task {
                 }
             }
 
-            log.info("直播间观看奖励-优惠券发放完成,liveId={}, couponId={}, 成功发放 {} 个用户",
-                    live.getLiveId(), couponId, relations.size());
+            log.info("直播间观看奖励-优惠券发放完成,liveId={}, couponId={}, 每人张数={}, 成功发放 {} 条记录",
+                    live.getLiveId(), couponId, grantCount, relations.size());
 
             return relations;
 
@@ -766,6 +779,13 @@ public class Task {
         return relations;
     }
 
+    private int resolveActionCouponCount(LiveWatchConfig config) {
+        if (config == null || config.getActionCouponCount() == null || config.getActionCouponCount() <= 0) {
+            return 1;
+        }
+        return config.getActionCouponCount().intValue();
+    }
+
     /**
      * 手动触发观看奖励优惠券发放(单用户,供接口调用)
      *
@@ -781,13 +801,17 @@ public class Task {
             }
 
             Long targetCouponId = couponId;
+            int actionCouponCount = 1;
+            String configJson = live.getConfigJson();
+            LiveWatchConfig config = null;
+            if (StringUtils.isNotEmpty(configJson)) {
+                config = JSON.parseObject(configJson, LiveWatchConfig.class);
+            }
             if (targetCouponId == null) {
-                String configJson = live.getConfigJson();
-                if (StringUtils.isEmpty(configJson)) {
+                if (config == null) {
                     return R.error("直播间未配置观看奖励");
                 }
-                LiveWatchConfig config = JSON.parseObject(configJson, LiveWatchConfig.class);
-                if (config == null || !Boolean.TRUE.equals(config.getEnabled())) {
+                if (!Boolean.TRUE.equals(config.getEnabled())) {
                     return R.error("观看奖励未开启");
                 }
                 if (config.getAction() == null || config.getAction().intValue() != 3) {
@@ -799,8 +823,9 @@ public class Task {
                 }
                 targetCouponId = Long.parseLong(actionCouponIdStr);
             }
+            actionCouponCount = resolveActionCouponCount(config);
 
-            List<LiveConsoleOpLogUser> couponRelations = bindCouponToUsers(live, Collections.singletonList(userId), targetCouponId, false);
+            List<LiveConsoleOpLogUser> couponRelations = bindCouponToUsers(live, Collections.singletonList(userId), targetCouponId, false, actionCouponCount);
             if (couponRelations.isEmpty()) {
                 return R.error("优惠券发放失败,请检查优惠券配置及库存");
             }
@@ -813,10 +838,10 @@ public class Task {
                     resolveWatchRewardCouponBizName(watchRewardCoupon, targetCouponId, couponRelations.size())
             );
             liveConsoleOpLogService.bindOpLogUsers(watchCouponOpLog.getId(), liveId, couponRelations);
-            couponRelations.forEach(relation -> sendCouponRewardMessage(
-                    liveId, relation.getUserId(), watchRewardCoupon, watchCouponOpLog));
+            sendCouponRewardMessage(liveId, userId, watchRewardCoupon, watchCouponOpLog);
             return R.ok("观看奖励优惠券发放完成")
                     .put("couponId", targetCouponId)
+                    .put("couponCount", actionCouponCount)
                     .put("opLogId", watchCouponOpLog.getId());
         } catch (Exception e) {
             log.error("手动触发观看奖励优惠券失败, liveId={}, userId={}", liveId, userId, e);

+ 8 - 0
fs-service/src/main/java/com/fs/live/domain/LiveWatchConfig.java

@@ -102,6 +102,10 @@ public class LiveWatchConfig extends BaseEntity{
     @Excel(name = "实施动作优惠券ID")
     private String actionCouponId;
 
+    /** 实施动作优惠券领取数量(action=3时使用,每人领取张数) */
+    @Excel(name = "实施动作优惠券领取数量")
+    private Long actionCouponCount;
+
     /** 优惠券引导语 */
     @Excel(name = "优惠券引导语")
     private String couponGuideText;
@@ -110,6 +114,10 @@ public class LiveWatchConfig extends BaseEntity{
     @Excel(name = "完课优惠券ID")
     private String finishCouponId;
 
+    /** 完课优惠券领取数量(participateCondition=3时使用,每人领取张数) */
+    @Excel(name = "完课优惠券领取数量")
+    private Long finishCouponCount;
+
     /** 配置json */
     @Excel(name = "配置json")
     private String configJson;

+ 3 - 0
fs-service/src/main/java/com/fs/live/param/CouponPO.java

@@ -37,4 +37,7 @@ public class CouponPO extends BaseQueryParam {
     /** 中控台操作留存 ID(App 领取时回传) */
     private Long opLogId;
 
+    /** 领取数量(不传或非法值时默认 1) */
+    private Integer couponCount;
+
 }

+ 55 - 29
fs-service/src/main/java/com/fs/live/service/impl/LiveCompletionCouponServiceImpl.java

@@ -84,6 +84,7 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
         vo.setEnabled(config.isEnabled());
         vo.setCompletionRate(config.getCompletionRate());
         vo.setCouponId(config.getCouponId());
+        vo.setCouponCount(config.getCouponCount());
         vo.setFinishQuestionIds(config.getFinishQuestionIds());
         if (config.isEnabled() && live != null && live.getDuration() != null && live.getDuration() > 0) {
             vo.setRequiredDurationSeconds(calculateRequiredDuration(live.getDuration(), config.getCompletionRate()));
@@ -264,10 +265,16 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
         if (live == null) {
             throw new BaseException("直播不存在");
         }
-        LiveCouponUser couponUser = issueCoupon(live, userId, config.getCouponId());
-        liveConsoleOpLogService.bindOpLogUser(
-                param.getOpLogId(), liveId, userId, couponUser.getId());
-        return couponUser;
+        List<LiveCouponUser> couponUsers = issueCoupon(live, userId, config.getCouponId(), config.getCouponCount());
+        if (couponUsers.isEmpty()) {
+            throw new BaseException("优惠券发放失败");
+        }
+        List<LiveConsoleOpLogUser> relations = new ArrayList<>();
+        for (LiveCouponUser couponUser : couponUsers) {
+            relations.add(new LiveConsoleOpLogUser(null, userId, liveId, couponUser.getId()));
+        }
+        liveConsoleOpLogService.bindOpLogUsers(param.getOpLogId(), liveId, relations);
+        return couponUsers.get(0);
     }
 
     /**
@@ -494,7 +501,7 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
         return getCompletionCouponConfig(live);
     }
 
-    private LiveCouponUser issueCoupon(Live live, Long userId, Long couponId) {
+    private List<LiveCouponUser> issueCoupon(Live live, Long userId, Long couponId, Integer couponCount) {
         LiveCoupon coupon = liveCouponService.selectLiveCouponById(couponId);
         if (coupon == null) {
             throw new BaseException("优惠券不存在");
@@ -505,29 +512,34 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
             throw new BaseException("优惠券领取配置不可用");
         }
 
+        int grantCount = couponCount != null && couponCount > 0 ? couponCount : 1;
         Date now = new Date();
-        LiveCouponUser couponUser = new LiveCouponUser();
-        couponUser.setCouponId(couponId);
-        couponUser.setUserId(userId.intValue());
-        couponUser.setCouponTitle(coupon.getTitle());
-        couponUser.setCouponPrice(coupon.getCouponPrice());
-        couponUser.setUseMinPrice(coupon.getUseMinPrice());
-
-        if (couponIssue.getLimitTime() != null) {
-            couponUser.setLimitTime(couponIssue.getLimitTime());
-        } else if (coupon.getCouponTime() != null) {
-            Calendar cal = Calendar.getInstance();
-            cal.setTime(now);
-            cal.add(Calendar.DATE, coupon.getCouponTime().intValue());
-            couponUser.setLimitTime(cal.getTime());
-        }
-
-        couponUser.setType(COMPLETION_COUPON_TYPE + "-" + live.getLiveId());
-        couponUser.setStatus(0);
-        couponUser.setIsFail(0);
-        couponUser.setIsDel(0);
-        couponUser.setCreateTime(now);
-        liveCouponUserService.insertLiveCouponUser(couponUser);
+        List<LiveCouponUser> couponUsers = new ArrayList<>();
+        for (int i = 0; i < grantCount; i++) {
+            LiveCouponUser couponUser = new LiveCouponUser();
+            couponUser.setCouponId(couponId);
+            couponUser.setUserId(userId.intValue());
+            couponUser.setCouponTitle(coupon.getTitle());
+            couponUser.setCouponPrice(coupon.getCouponPrice());
+            couponUser.setUseMinPrice(coupon.getUseMinPrice());
+
+            if (couponIssue.getLimitTime() != null) {
+                couponUser.setLimitTime(couponIssue.getLimitTime());
+            } else if (coupon.getCouponTime() != null) {
+                Calendar cal = Calendar.getInstance();
+                cal.setTime(now);
+                cal.add(Calendar.DATE, coupon.getCouponTime().intValue());
+                couponUser.setLimitTime(cal.getTime());
+            }
+
+            couponUser.setType(COMPLETION_COUPON_TYPE + "-" + live.getLiveId());
+            couponUser.setStatus(0);
+            couponUser.setIsFail(0);
+            couponUser.setIsDel(0);
+            couponUser.setCreateTime(now);
+            liveCouponUserService.insertLiveCouponUser(couponUser);
+            couponUsers.add(couponUser);
+        }
 
         LiveRewardRecord record = new LiveRewardRecord();
         record.setLiveId(live.getLiveId());
@@ -541,8 +553,9 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
         record.setCreateBy(String.valueOf(userId));
         liveRewardRecordService.insertLiveRewardRecord(record);
 
-        log.info("完课优惠券发放成功, liveId={}, userId={}, couponId={}", live.getLiveId(), userId, couponId);
-        return couponUser;
+        log.info("完课优惠券发放成功, liveId={}, userId={}, couponId={}, count={}",
+                live.getLiveId(), userId, couponId, grantCount);
+        return couponUsers;
     }
 
     private boolean hasIssuedToday(Long liveId, Long userId, Long couponId) {
@@ -653,6 +666,9 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
 
             Integer completionRate = jsonConfig.getInteger("completionRate");
             config.setCompletionRate(completionRate != null && completionRate > 0 && completionRate <= 100 ? completionRate : 90);
+
+            Integer finishCouponCount = jsonConfig.getInteger("finishCouponCount");
+            config.setCouponCount(finishCouponCount != null && finishCouponCount > 0 ? finishCouponCount : 1);
         } catch (Exception e) {
             log.warn("解析完课优惠券配置失败, liveId={}", live.getLiveId(), e);
             return disabledConfig();
@@ -664,6 +680,7 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
         CompletionCouponConfig config = new CompletionCouponConfig();
         config.setEnabled(false);
         config.setCompletionRate(90);
+        config.setCouponCount(1);
         return config;
     }
 
@@ -711,6 +728,7 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
         private boolean enabled;
         private Integer completionRate;
         private Long couponId;
+        private Integer couponCount;
         private String finishQuestionIds;
 
         public boolean isEnabled() {
@@ -737,6 +755,14 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
             this.couponId = couponId;
         }
 
+        public Integer getCouponCount() {
+            return couponCount;
+        }
+
+        public void setCouponCount(Integer couponCount) {
+            this.couponCount = couponCount;
+        }
+
         public String getFinishQuestionIds() {
             return finishQuestionIds;
         }

+ 54 - 37
fs-service/src/main/java/com/fs/live/service/impl/LiveCouponServiceImpl.java

@@ -322,6 +322,9 @@ public class LiveCouponServiceImpl implements ILiveCouponService
             return R.error("领取参数不完整!");
         }
 
+        int grantCount = coupon.getCouponCount() != null && coupon.getCouponCount() > 0
+                ? coupon.getCouponCount() : 1;
+
         LiveCouponIssue issue = liveCouponMapper.selectLiveCouponIssueByLiveIdAndCouponId(
                 coupon.getLiveId(), coupon.getCouponIssueId());
         if (ObjectUtils.isEmpty(issue) || issue.getStatus() == null || issue.getStatus() != 1) {
@@ -335,45 +338,58 @@ public class LiveCouponServiceImpl implements ILiveCouponService
 
         // Redis 预扣减库存,key 必须与展示/初始化时保持一致
         String stockKey = String.format(LiveKeysConstant.LIVE_COUPON_NUM, coupon.getCouponIssueId());
-        Long decrement = redisCache.decrement(stockKey);
+        Long decrement = redisCache.decr(stockKey, (long) grantCount);
 
-        // 库存不足:回补 Redis 多扣的 1,并标记已领完
+        // 库存不足:回补 Redis 多扣的数量;仅当扣减前已为 0 时标记已领完
         if (decrement == null || decrement < 0L) {
             if (decrement != null) {
-                redisCache.incr(stockKey, 1L);
+                redisCache.incr(stockKey, (long) grantCount);
+                if (decrement + grantCount <= 0L) {
+                    issue.setStatus(-1);
+                    issue.setRemainCount(0L);
+                    liveCouponIssueService.updateLiveCouponIssue(issue);
+                }
             }
-            issue.setStatus(-1);
-            issue.setRemainCount(0L);
-            liveCouponIssueService.updateLiveCouponIssue(issue);
-            return R.error("此优惠券已领完");
+            return R.error(grantCount > 1 ? "优惠券库存不足" : "此优惠券已领完");
         }
 
         // 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());
+            List<LiveCouponUser> couponUsers = new ArrayList<>(grantCount);
+            List<LiveConsoleOpLogUser> opLogRelations = new ArrayList<>(grantCount);
+
+            for (int i = 0; i < grantCount; i++) {
+                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());
+
+                liveCouponUserService.insertLiveCouponUser(userRecord);
+                liveCouponIssueUserService.insertLiveCouponIssueUser(record);
+                couponUsers.add(userRecord);
+                opLogRelations.add(new LiveConsoleOpLogUser(
+                        null, coupon.getUserId(), coupon.getLiveId(), userRecord.getId()));
+            }
 
             // 基于 Redis 扣减后的值同步 DB 库存,避免读后写并发丢失更新
             issue.setRemainCount(decrement);
@@ -382,22 +398,23 @@ public class LiveCouponServiceImpl implements ILiveCouponService
             }
             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());
+            liveConsoleOpLogService.bindOpLogUsers(opLogId, coupon.getLiveId(), opLogRelations);
 
+            List<Long> couponUserIds = couponUsers.stream()
+                    .map(LiveCouponUser::getId)
+                    .collect(Collectors.toList());
             return R.ok("恭喜您抢到优惠券")
                     .put("opLogId", opLogId)
-                    .put("couponUserId", userRecord.getId());
+                    .put("couponUserId", couponUserIds.get(0))
+                    .put("couponUserIds", couponUserIds)
+                    .put("couponCount", grantCount);
         } catch (Exception e) {
             // DB 操作失败,回补 Redis 库存,避免库存丢失;重新抛出让事务回滚
-            redisCache.incr(stockKey, 1L);
+            redisCache.incr(stockKey, (long) grantCount);
             throw e;
         }
     }

+ 3 - 0
fs-service/src/main/java/com/fs/live/vo/LiveCompletionCouponConfigVO.java

@@ -14,6 +14,9 @@ public class LiveCompletionCouponConfigVO {
 
     private Long couponId;
 
+    /** 每人领取张数 */
+    private Integer couponCount;
+
     private String finishQuestionIds;
 
     /** 完课所需观看时长(秒) */

+ 10 - 10
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -660,7 +660,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
                                 if ("1".equals(st.getContentType())) {
                                     st.setValue(st.getValue()
-                                            .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText()));
+                                            .replace("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText()));
                                 }
                                 break;
                             //小程序单独
@@ -940,7 +940,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 //                                }
                                 if ("1".equals(st.getContentType())) {
                                     st.setValue(st.getValue()
-                                            .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText()));
+                                            .replace("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText()));
                                 }
                                 break;
                             //小程序单独
@@ -1046,7 +1046,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 sopLogs.setIsHaveApp(1);
                                 sopLogs.setAppSendStatus(0);
                                 String txt2 = StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText();
-                                st.setValue(st.getValue().replaceAll("#客服称呼#", txt2).replaceAll("#销售称呼#", txt2));
+                                st.setValue(st.getValue().replace("#客服称呼#", txt2).replace("#销售称呼#", txt2));
                                 try {
                                     replaceContent(st.getContentType(), st.getValue(), st::setValue, words); // 替换 value
                                 } catch (Exception e) {
@@ -1190,8 +1190,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                         defaultName = contact.getName();
                                     }
                                     st.setValue(st.getValue()
-                                            .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
-                                            .replaceAll("#客户称呼#",contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus())?defaultName:contact.getStageStatus()));
+                                            .replace("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
+                                            .replace("#客户称呼#",contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus())?defaultName:contact.getStageStatus()));
                                 }
 //                            }
 
@@ -1379,9 +1379,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                         case "15":
                             String txt = StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText();
                             st.setValue(st.getValue()
-                                    .replaceAll("#客服称呼#", txt)
-                                    .replaceAll("#销售称呼#", txt)
-                                    .replaceAll("#客户称呼#", StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus()) ? "同学" : contact.getStageStatus()));
+                                    .replace("#客服称呼#", txt)
+                                    .replace("#销售称呼#", txt)
+                                    .replace("#客户称呼#", StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus()) ? "同学" : contact.getStageStatus()));
                             sopLogs.setIsHaveApp(1);
                             sopLogs.setAppSendStatus(0);
                             try {
@@ -1863,8 +1863,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 defaultName = contact.getName();
                             }
                             st.setValue(st.getValue()
-                                    .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
-                                    .replaceAll("#客户称呼#",contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus())?defaultName:contact.getStageStatus()));
+                                    .replace("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
+                                    .replace("#客户称呼#",contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus())?defaultName:contact.getStageStatus()));
                         }
 //                    }