|
|
@@ -36,6 +36,7 @@ import javax.websocket.*;
|
|
|
import javax.websocket.server.ServerEndpoint;
|
|
|
import java.io.EOFException;
|
|
|
import java.io.IOException;
|
|
|
+import java.time.LocalDateTime;
|
|
|
import java.util.*;
|
|
|
import java.util.concurrent.*;
|
|
|
import java.util.concurrent.locks.Lock;
|
|
|
@@ -80,6 +81,7 @@ public class WebSocketServer {
|
|
|
private final LiveCouponMapper liveCouponMapper = SpringUtils.getBean(LiveCouponMapper.class);
|
|
|
private final ILiveWatchLogService liveWatchLogService = SpringUtils.getBean(ILiveWatchLogService.class);
|
|
|
private final ILiveVideoService liveVideoService = SpringUtils.getBean(ILiveVideoService.class);
|
|
|
+ private final ILiveCompletionPointsRecordService completionPointsRecordService = SpringUtils.getBean(ILiveCompletionPointsRecordService.class);
|
|
|
private static Random random = new Random();
|
|
|
|
|
|
// Redis key 前缀:用户进入直播间时间
|
|
|
@@ -349,6 +351,32 @@ public class WebSocketServer {
|
|
|
if (msg.getData() != null && !msg.getData().isEmpty()) {
|
|
|
try {
|
|
|
Long currentDuration = Long.parseLong(msg.getData());
|
|
|
+
|
|
|
+ Live currentLive = liveService.selectLiveByLiveId(liveId);
|
|
|
+ if (currentLive == null) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断直播是否已开始:status=2(直播中) 或 当前时间 >= 开播时间
|
|
|
+ boolean isLiveStarted = false;
|
|
|
+ if (currentLive.getStatus() != null && currentLive.getStatus() == 2) {
|
|
|
+ // status=2 表示直播中
|
|
|
+ isLiveStarted = true;
|
|
|
+ } else if (currentLive.getStartTime() != null) {
|
|
|
+ // 判断当前时间是否已超过开播时间
|
|
|
+ LocalDateTime now = java.time.LocalDateTime.now();
|
|
|
+ isLiveStarted = now.isAfter(currentLive.getStartTime()) || now.isEqual(currentLive.getStartTime());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!isLiveStarted) {
|
|
|
+ log.debug("[心跳-观看时长] 直播未开始(开播倒计时中),不统计观看时长, liveId={}, status={}, startTime={}",
|
|
|
+ liveId, currentLive.getStatus(), currentLive.getStartTime());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ log.debug("[心跳-观看时长] 直播已开始,统计观看时长, liveId={}, userId={}, duration={}秒",
|
|
|
+ liveId, watchUserId, currentDuration);
|
|
|
+
|
|
|
// 使用Hash结构存储:一个直播间一个Hash,包含所有用户的时长
|
|
|
String hashKey = "live:watch:duration:hash:" + liveId;
|
|
|
String userIdField = String.valueOf(watchUserId);
|
|
|
@@ -361,6 +389,8 @@ public class WebSocketServer {
|
|
|
// 设置过期时间(2小时)
|
|
|
redisCache.expire(hashKey, 2, TimeUnit.HOURS);
|
|
|
|
|
|
+ checkAndSendCompletionPointsInRealTime(liveId, watchUserId, currentDuration);
|
|
|
+
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
log.error("[心跳-观看时长] 更新失败, liveId={}, userId={}, data={}",
|
|
|
@@ -1315,5 +1345,48 @@ public class WebSocketServer {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 实时检查并推送完课积分
|
|
|
+ * 在用户观看时长更新时立即检查是否达到完课条件,达到则立即推送
|
|
|
+ * @param liveId 直播间ID
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param duration 当前观看时长(秒)
|
|
|
+ */
|
|
|
+ private void checkAndSendCompletionPointsInRealTime(long liveId, long userId, Long duration) {
|
|
|
+ try {
|
|
|
+ log.debug("[实时完课检查] liveId={}, userId={}, duration={}秒", liveId, userId, duration);
|
|
|
+
|
|
|
+ // 1. 调用完课记录服务检查并创建完课记录
|
|
|
+ completionPointsRecordService.checkAndCreateCompletionRecord(liveId, userId, duration);
|
|
|
+
|
|
|
+ // 2. 查询是否有新的未领取完课记录
|
|
|
+ List<LiveCompletionPointsRecord> unreceivedRecords =
|
|
|
+ completionPointsRecordService.getUserUnreceivedRecords(liveId, userId);
|
|
|
+
|
|
|
+ if (unreceivedRecords == null || unreceivedRecords.isEmpty()) {
|
|
|
+ // 没有待领取的完课记录
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 构建推送消息
|
|
|
+ SendMsgVo sendMsgVo = new SendMsgVo();
|
|
|
+ sendMsgVo.setLiveId(liveId);
|
|
|
+ sendMsgVo.setUserId(userId);
|
|
|
+ sendMsgVo.setCmd("completionPoints");
|
|
|
+ sendMsgVo.setMsg("完成任务!");
|
|
|
+ sendMsgVo.setData(JSONObject.toJSONString(unreceivedRecords.get(0)));
|
|
|
+
|
|
|
+ // 4. 实时推送完课积分弹窗
|
|
|
+ sendCompletionPointsMessage(liveId, userId, sendMsgVo);
|
|
|
+
|
|
|
+ log.info("[实时完课推送] 发送完课积分弹窗通知, liveId={}, userId={}, points={}, duration={}秒",
|
|
|
+ liveId, userId, unreceivedRecords.get(0).getPointsAwarded(), duration);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("[实时完课推送] 实时检查完课积分失败, liveId={}, userId={}, duration={}",
|
|
|
+ liveId, userId, duration, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|