Quellcode durchsuchen

1、调整自动化不发送问题,以及直播展示数据错误,web心跳问题处理

yys vor 2 Tagen
Ursprung
Commit
c80591d92f

+ 3 - 4
fs-common/src/main/java/com/fs/common/utils/DictUtils.java

@@ -41,8 +41,7 @@ public class DictUtils
         Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
         if (StringUtils.isNotNull(cacheObj))
         {
-            List<SysDictData> dictDatas = StringUtils.cast(cacheObj);
-            return dictDatas;
+            return RedisCache.convertCacheList(cacheObj, SysDictData.class);
         }
         return null;
     }
@@ -98,7 +97,7 @@ public class DictUtils
                 }
             }
         }
-        else
+        else if (StringUtils.isNotEmpty(datas))
         {
             for (SysDictData dict : datas)
             {
@@ -138,7 +137,7 @@ public class DictUtils
                 }
             }
         }
-        else
+        else if (StringUtils.isNotEmpty(datas))
         {
             for (SysDictData dict : datas)
             {

+ 22 - 19
fs-live-app/src/main/java/com/fs/live/task/LiveCompletionPointsTask.java

@@ -2,7 +2,6 @@ package com.fs.live.task;
 
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.domain.R;
-import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.StringUtils;
 import com.fs.live.domain.Live;
 import com.fs.live.domain.LiveCompletionPointsRecord;
@@ -26,8 +25,6 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import static com.fs.live.websocket.service.WebSocketServer.USER_ENTRY_TIME_KEY;
-
 /**
  * 直播完课奖励定时任务(积分 / 优惠券)
  */
@@ -35,9 +32,6 @@ import static com.fs.live.websocket.service.WebSocketServer.USER_ENTRY_TIME_KEY;
 @Component
 public class LiveCompletionPointsTask {
 
-    @Autowired
-    private RedisCache redisCache;
-
     @Autowired
     private ILiveWatchUserService liveWatchUserService;
 
@@ -141,13 +135,31 @@ public class LiveCompletionPointsTask {
     /**
      * 完课优惠券弹窗:与定时任务一致,先写留存再推送 WebSocket
      */
+    /**
+     * 尝试推送完课优惠券弹窗(定时任务 / WebSocket 心跳共用)
+     * @return 是否推送成功
+     */
+    public boolean tryDispatchCompletionCouponNotify(Long liveId, Long userId, Long watchDuration) {
+        return dispatchCompletionCouponNotify(liveId, userId, watchDuration, false) != null;
+    }
+
     private LiveConsoleOpLog dispatchCompletionCouponNotify(Long liveId, Long userId, Long watchDuration, boolean forcePush) {
         LiveCompletionCouponNotifyResult notifyResult =
                 completionCouponService.prepareCompletionCouponNotify(liveId, userId, watchDuration, forcePush);
         if (notifyResult == null || !notifyResult.isShouldNotify()) {
+            if (notifyResult != null && notifyResult.isEligible()) {
+                log.info("[完课优惠券] 观看已达标但未推送弹窗, liveId={}, userId={}, " +
+                                "可能原因: 未配置今日问题 / 今日已推送 / 今日已领券",
+                        liveId, userId);
+            }
             return null;
         }
-        return saveAndPushCompletionCouponNotify(liveId, userId, notifyResult);
+        LiveConsoleOpLog opLog = saveAndPushCompletionCouponNotify(liveId, userId, notifyResult);
+        if (opLog == null) {
+            log.warn("[完课优惠券] 满足推送条件但 WebSocket 发送失败(用户未在线或连接已断开), liveId={}, userId={}",
+                    liveId, userId);
+        }
+        return opLog;
     }
 
     private LiveConsoleOpLog saveAndPushCompletionCouponNotify(Long liveId, Long userId,
@@ -226,20 +238,11 @@ public class LiveCompletionPointsTask {
     }
 
     /**
-     * 与 scanLiveWatchUserStatus 一致:DB 累计时长 + 当前在线会话未落库部分
+     * DB 累计时长 + Redis 当前会话 + hash 缓存,与 ILiveWatchUserService.getUserWatchDuration 一致
      */
     private long resolveEffectiveWatchDuration(Long liveId, Long userId) {
-        Long total = liveWatchUserService.getTotalWatchDuration(liveId, userId);
-        long duration = total != null ? total : 0L;
-        String entryTimeKey = String.format(USER_ENTRY_TIME_KEY, liveId, userId);
-        Long entryTime = redisCache.getCacheObject(entryTimeKey);
-        if (entryTime != null) {
-            long sessionSeconds = (System.currentTimeMillis() - entryTime) / 1000;
-            duration += sessionSeconds;
-            log.debug("[完课定时] 累加Redis在线时长, liveId={}, userId={}, dbDuration={}s, redisSession={}s, total={}s",
-                    liveId, userId, total, sessionSeconds, duration);
-        }
-        return duration;
+        Long duration = liveWatchUserService.getUserWatchDuration(liveId, userId);
+        return duration != null ? duration : 0L;
     }
 
     @FunctionalInterface

+ 25 - 0
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -24,6 +24,7 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.live.domain.*;
 import com.fs.live.service.*;
+import com.fs.live.task.LiveCompletionPointsTask;
 import com.fs.live.vo.LiveConsoleOpLogVo;
 import com.fs.live.vo.LiveGoodsVo;
 import lombok.extern.slf4j.Slf4j;
@@ -457,6 +458,9 @@ public class WebSocketServer {
                     // 更新心跳时间
                     heartbeatCache.put(session.getId(), System.currentTimeMillis());
                     sendMessage(session, JSONObject.toJSONString(R.ok().put("data", msg)));
+                    if (userType == 0) {
+                        checkCompletionRewardsOnHeartbeat(liveId, userId);
+                    }
                     break;
                 case "sendMsg":
                     // 参数校验
@@ -1824,6 +1828,27 @@ public class WebSocketServer {
         }
     }
 
+    /**
+     * 心跳节流检查完课积分 / 完课优惠券(每 60 秒最多一次)
+     */
+    private void checkCompletionRewardsOnHeartbeat(long liveId, long userId) {
+        String throttleKey = "live:completion:heartbeat:check:" + liveId + ":" + userId;
+        if (!Boolean.TRUE.equals(redisCache.setIfAbsent(throttleKey, "1", 60, TimeUnit.SECONDS))) {
+            return;
+        }
+        try {
+            Long duration = liveWatchUserService.getUserWatchDuration(liveId, userId);
+            if (duration == null || duration <= 0) {
+                return;
+            }
+            checkAndSendCompletionPointsInRealTime(liveId, userId, duration);
+            SpringUtils.getBean(LiveCompletionPointsTask.class)
+                    .tryDispatchCompletionCouponNotify(liveId, userId, duration);
+        } catch (Exception e) {
+            log.error("[完课心跳检查] 失败, liveId={}, userId={}", liveId, userId, e);
+        }
+    }
+
     /**
      * 实时检查并推送完课积分
      * 在用户观看时长更新时立即检查是否达到完课条件,达到则立即推送

+ 17 - 16
fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java

@@ -1122,24 +1122,25 @@ public class CompanyUserServiceImpl implements ICompanyUserService
             throw new CustomException("销售已禁用");
         }
 
-        batchUpdateBindCompanyUserId(Collections.singletonList(userId), companyUserId);
-
         CompanyCompanyFsuser existBind = companyCompanyFsuserMapper.getInfoByUserId(String.valueOf(userId));
-        if (existBind == null) {
-            CompanyCompanyFsuser bind = new CompanyCompanyFsuser();
-            bind.setUserId(userId);
-            bind.setCompanyId(companyUser.getCompanyId());
-            bind.setCompanyUserId(companyUserId);
-            bind.setStatus(1);
-            bind.setBindType(0);
-            bind.setCreateTime(DateUtils.getNowDate());
-            companyCompanyFsuserMapper.insertCompanyCompanyUser(bind);
-        } else if (!companyUserId.equals(existBind.getCompanyUserId())) {
-            existBind.setCompanyUserId(companyUserId);
-            existBind.setCompanyId(companyUser.getCompanyId());
-            existBind.setUpdateTime(DateUtils.getNowDate());
-            companyCompanyFsuserMapper.updateCompanyCompanyUser(existBind);
+        if (existBind != null) {
+            if (companyUserId.equals(existBind.getCompanyUserId())) {
+                batchUpdateBindCompanyUserId(Collections.singletonList(userId), companyUserId);
+                return;
+            }
+            throw new CustomException("该会员已绑定销售,无法切换绑定");
         }
+
+        batchUpdateBindCompanyUserId(Collections.singletonList(userId), companyUserId);
+
+        CompanyCompanyFsuser bind = new CompanyCompanyFsuser();
+        bind.setUserId(userId);
+        bind.setCompanyId(companyUser.getCompanyId());
+        bind.setCompanyUserId(companyUserId);
+        bind.setStatus(1);
+        bind.setBindType(0);
+        bind.setCreateTime(DateUtils.getNowDate());
+        companyCompanyFsuserMapper.insertCompanyCompanyUser(bind);
     }
 
     @Override

+ 1 - 1
fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java

@@ -137,7 +137,7 @@ public interface LiveMapper
     @Select("select * from live where status != 3 and live_type in (2,3) and is_audit = 1 " +
             "and config_json is not null " +
             "and JSON_EXTRACT(config_json, '$.enabled') = true " +
-            "and JSON_EXTRACT(config_json, '$.participateCondition') = '3' " +
+            "and CAST(JSON_UNQUOTE(JSON_EXTRACT(config_json, '$.participateCondition')) AS UNSIGNED) = 3 " +
             "and JSON_EXTRACT(config_json, '$.finishCouponId') is not null " +
             "and JSON_EXTRACT(config_json, '$.finishCouponId') != ''")
     List<Live> selectLiveListWithCompletionCouponEnabled();

+ 29 - 5
fs-service/src/main/java/com/fs/live/service/impl/LiveAutoTaskServiceImpl.java

@@ -13,6 +13,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.DictUtils;
 import com.fs.live.domain.*;
 import com.fs.live.mapper.*;
 import com.fs.live.param.LiveLotteryProduct;
@@ -151,8 +152,12 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
             if(liveCouponIssue == null)return R.error("优惠券未发布或未关联到直播间");
             LiveCouponIssueRelation liveCouponIssueRelation = liveCouponMapper.selectCouponRelation(liveAutoTask.getLiveId(),liveCouponIssue.getId());
             if(liveCouponIssueRelation == null) return R.error("优惠券尚未添加在直播间");
-            if(ObjectUtil.isEmpty(liveCouponIssueRelation.getGoodsId())) return R.error("未绑定商品,无法制定自动化任务!");
-            liveCoupon.setGoodsId(liveCouponIssueRelation.getGoodsId());
+            if (!isVerifyCouponType(liveCoupon) && ObjectUtil.isEmpty(liveCouponIssueRelation.getGoodsId())) {
+                return R.error("未绑定商品,无法制定自动化任务!");
+            }
+            if (ObjectUtil.isNotEmpty(liveCouponIssueRelation.getGoodsId())) {
+                liveCoupon.setGoodsId(liveCouponIssueRelation.getGoodsId());
+            }
             liveAutoTask.setContent(JSON.toJSONString(liveCoupon));
             baseMapper.insertLiveAutoTask(liveAutoTask);
         } else if (liveAutoTask.getTaskType() == 6L) {
@@ -324,9 +329,13 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
             LiveCouponIssue liveCouponIssue = liveCouponIssueMapper.selectIssueByLiveIdAndCouponId(liveAutoTask.getLiveId(), liveCoupon.getCouponId());
             if(liveCouponIssue == null)return R.error("未发布优惠券或未关联到直播间!");
             LiveCouponIssueRelation liveCouponIssueRelation = liveCouponMapper.selectCouponRelation(liveAutoTask.getLiveId(),liveCouponIssue.getId());
-            if(liveCouponIssueRelation == null) return R.error("未绑定商品,无法发布!");
-            if(ObjectUtil.isEmpty(liveCouponIssueRelation.getGoodsId())) return R.error("未绑定商品,无法发布!");
-            liveCoupon.setGoodsId(liveCouponIssueRelation.getGoodsId());
+            if(liveCouponIssueRelation == null) return R.error("优惠券尚未添加在直播间");
+            if (!isVerifyCouponType(liveCoupon) && ObjectUtil.isEmpty(liveCouponIssueRelation.getGoodsId())) {
+                return R.error("未绑定商品,无法发布!");
+            }
+            if (ObjectUtil.isNotEmpty(liveCouponIssueRelation.getGoodsId())) {
+                liveCoupon.setGoodsId(liveCouponIssueRelation.getGoodsId());
+            }
             liveAutoTask.setContent(JSON.toJSONString(liveCoupon));
             baseMapper.updateLiveAutoTask(liveAutoTask);
         } else if (liveAutoTask.getTaskType() == 6L) {
@@ -730,4 +739,19 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
             log.error("删除红包缓存失败,redId: {}, liveId: {}", redId, liveId, e);
         }
     }
+
+    /**
+     * 核销券/无门槛券无需绑定直播商品
+     */
+    private boolean isVerifyCouponType(LiveCoupon coupon) {
+        if (coupon == null || coupon.getType() == null) {
+            return false;
+        }
+        if (coupon.getType() == 3L) {
+            return true;
+        }
+        String typeLabel = DictUtils.getDictLabel("store_coupon_type", String.valueOf(coupon.getType()));
+        return StringUtils.isNotEmpty(typeLabel)
+                && (typeLabel.contains("核销") || typeLabel.contains("代金券"));
+    }
 }

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

@@ -427,7 +427,7 @@ public class LiveCompletionCouponServiceImpl implements ILiveCompletionCouponSer
     private boolean isWatchRateEligible(Long liveId, Long userId, Long watchDuration, CompletionCouponConfig config) {
         Long actualWatchDuration = watchDuration;
         if (actualWatchDuration == null) {
-            actualWatchDuration = liveWatchUserService.getTotalWatchDuration(liveId, userId);
+            actualWatchDuration = liveWatchUserService.getUserWatchDuration(liveId, userId);
         }
         if (actualWatchDuration == null || actualWatchDuration <= 0) {
             log.info("[完课优惠券] 用户观看时长为0或null, liveId={}, userId={}, watchDuration={}",

+ 18 - 33
fs-service/src/main/java/com/fs/live/service/impl/LiveCompletionPointsRecordServiceImpl.java

@@ -2,11 +2,11 @@ package com.fs.live.service.impl;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.fs.common.core.domain.R;
 import com.fs.common.exception.base.BaseException;
-import com.fs.his.domain.FsUser;
-import com.fs.his.domain.FsUserIntegralLogs;
-import com.fs.his.mapper.FsUserIntegralLogsMapper;
-import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.enums.FsUserIntegralLogTypeEnum;
+import com.fs.his.param.FsUserAddIntegralTemplateParam;
+import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.live.domain.Live;
 import com.fs.live.domain.LiveCompletionPointsRecord;
 import com.fs.live.mapper.LiveCompletionPointsRecordMapper;
@@ -40,10 +40,7 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
     private ILiveService liveService;
 
     @Autowired
-    private FsUserMapper fsUserMapper;
-
-    @Autowired
-    private FsUserIntegralLogsMapper fsUserIntegralLogsMapper;
+    private IFsUserIntegralLogsService fsUserIntegralLogsService;
 
     @Autowired
     private ILiveWatchUserService liveWatchUserService;
@@ -203,33 +200,21 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
             throw new BaseException("该完课积分已领取");
         }
 
-        // 4. 更新用户积分
-        FsUser user = fsUserMapper.selectFsUserByUserId(userId);
-        if (user == null) {
-            throw new BaseException("用户不存在");
+        // 4. 发放积分并写入 fs_user_integral_logs(logType=27 直播完课积分)
+        FsUserAddIntegralTemplateParam integralParam = new FsUserAddIntegralTemplateParam();
+        integralParam.setUserId(userId);
+        integralParam.setLogType(FsUserIntegralLogTypeEnum.TYPE_27);
+        integralParam.setBusinessId("live_completion_" + recordId);
+        integralParam.setPoints(Long.valueOf(record.getPointsAwarded()));
+        R integralResult = fsUserIntegralLogsService.addIntegralTemplate(integralParam);
+        if (integralResult == null || !"200".equals(String.valueOf(integralResult.get("code")))) {
+            String msg = integralResult != null && integralResult.get("msg") != null
+                    ? integralResult.get("msg").toString()
+                    : "完课积分发放失败";
+            throw new BaseException(msg);
         }
 
-        Long currentIntegral = user.getIntegral() != null ? user.getIntegral() : 0L;
-        Long newIntegral = currentIntegral + record.getPointsAwarded();
-
-        FsUser updateUser = new FsUser();
-        updateUser.setUserId(userId);
-        updateUser.setIntegral(newIntegral);
-        fsUserMapper.updateFsUser(updateUser);
-
-        // 5. 记录积分变动日志
-        FsUserIntegralLogs integralLog = new FsUserIntegralLogs();
-        integralLog.setUserId(userId);
-        integralLog.setIntegral(Long.valueOf(record.getPointsAwarded()));
-        integralLog.setBalance(newIntegral);
-        integralLog.setLogType(25); // 5-直播完课积分
-        integralLog.setBusinessId("live_completion_" + recordId); // 业务ID:直播完课记录ID
-        integralLog.setBusinessType(25); // 5-直播完课
-        integralLog.setStatus(1);
-        integralLog.setCreateTime(new Date());
-        fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLog);
-
-        // 6. 更新完课记录状态
+        // 5. 更新完课记录状态
         LiveCompletionPointsRecord updateRecord = new LiveCompletionPointsRecord();
         updateRecord.setId(recordId);
         updateRecord.setReceiveStatus(1);