|
|
@@ -2,8 +2,10 @@ package com.fs.live.task;
|
|
|
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import com.fs.common.core.redis.RedisCache;
|
|
|
+import com.fs.live.domain.Live;
|
|
|
import com.fs.live.domain.LiveCompletionPointsRecord;
|
|
|
import com.fs.live.service.ILiveCompletionPointsRecordService;
|
|
|
+import com.fs.live.service.ILiveService;
|
|
|
import com.fs.live.websocket.bean.SendMsgVo;
|
|
|
import com.fs.live.websocket.service.WebSocketServer;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
@@ -11,8 +13,11 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.scheduling.annotation.Scheduled;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
-import java.util.Collection;
|
|
|
+import java.util.HashSet;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
/**
|
|
|
* 直播完课积分定时任务
|
|
|
@@ -30,47 +35,53 @@ public class LiveCompletionPointsTask {
|
|
|
@Autowired
|
|
|
private WebSocketServer webSocketServer;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private ILiveService liveService;
|
|
|
+
|
|
|
/**
|
|
|
* 定时检查观看时长并创建完课记录
|
|
|
* 每分钟执行一次
|
|
|
+ * 优化:使用Hash结构 + 防重复推送
|
|
|
*/
|
|
|
@Scheduled(cron = "0 */1 * * * ?")
|
|
|
public void checkCompletionStatus() {
|
|
|
try {
|
|
|
- // 1. 获取所有观看时长的Redis key
|
|
|
- Collection<String> keys = redisCache.keys("live:watch:duration:*");
|
|
|
+ List<Live> activeLives = liveService.selectNoEndLiveList();
|
|
|
|
|
|
- if (keys == null || keys.isEmpty()) {
|
|
|
+ if (activeLives == null || activeLives.isEmpty()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 2. 遍历处理每个用户的观看时长
|
|
|
- for (String key : keys) {
|
|
|
+ for (Live live : activeLives) {
|
|
|
try {
|
|
|
- String[] parts = key.split(":");
|
|
|
- if (parts.length != 5) {
|
|
|
+ Long liveId = live.getLiveId();
|
|
|
+
|
|
|
+ // 使用Hash结构获取该直播间所有用户的观看时长
|
|
|
+ String hashKey = "live:watch:duration:hash:" + liveId;
|
|
|
+ Map<Object, Object> userDurations = redisCache.redisTemplate.opsForHash().entries(hashKey);
|
|
|
+
|
|
|
+ if (userDurations == null || userDurations.isEmpty()) {
|
|
|
continue;
|
|
|
}
|
|
|
-
|
|
|
- Long liveId = Long.parseLong(parts[3]);
|
|
|
- Long userId = Long.parseLong(parts[4]);
|
|
|
-
|
|
|
- // 3. 获取观看时长(秒)
|
|
|
- Object durationObj = redisCache.getCacheObject(key);
|
|
|
- if (durationObj == null) {
|
|
|
- continue;
|
|
|
+
|
|
|
+ // 3. 逐个用户处理
|
|
|
+ for (Map.Entry<Object, Object> entry : userDurations.entrySet()) {
|
|
|
+ try {
|
|
|
+ Long userId = Long.parseLong(entry.getKey().toString());
|
|
|
+
|
|
|
+ // 4. 检查并创建完课记录(传null,自动累计直播+回放时长)
|
|
|
+ completionPointsRecordService.checkAndCreateCompletionRecord(liveId, userId, null);
|
|
|
+
|
|
|
+ // 5. 检查是否有新的完课记录待领取,推送弹窗消息(防重复)
|
|
|
+ sendCompletionNotificationOnce(liveId, userId);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理用户完课状态失败, liveId={}, userId={}", liveId, entry.getKey(), e);
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- Long watchDuration = Long.parseLong(durationObj.toString());
|
|
|
-
|
|
|
- // 4. 检查并创建完课记录
|
|
|
- completionPointsRecordService.checkAndCreateCompletionRecord(liveId, userId, watchDuration);
|
|
|
-
|
|
|
- // 5. 检查是否有新的完课记录待领取,推送弹窗消息
|
|
|
- sendCompletionNotification(liveId, userId);
|
|
|
-
|
|
|
+
|
|
|
} catch (Exception e) {
|
|
|
- log.error("处理观看时长失败, key={}", key, e);
|
|
|
+ log.error("处理直播间完课状态失败, liveId={}", live.getLiveId(), e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -80,7 +91,47 @@ public class LiveCompletionPointsTask {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 发送完课通知(通过WebSocket推送弹窗)
|
|
|
+ * 发送完课通知(通过WebSocket推送弹窗) - 防重复版本
|
|
|
+ */
|
|
|
+ private void sendCompletionNotificationOnce(Long liveId, Long userId) {
|
|
|
+ try {
|
|
|
+ // 1. 检查 Redis 是否已推送过(防止每分钟都推送)
|
|
|
+ String notifyKey = "live:completion:notified:" + liveId + ":" + userId;
|
|
|
+ Boolean hasNotified = redisCache.hasKey(notifyKey);
|
|
|
+
|
|
|
+ if (Boolean.TRUE.equals(hasNotified)) {
|
|
|
+ return; // 已经推送过,不再重复推送
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 查询未领取的完课记录
|
|
|
+ List<LiveCompletionPointsRecord> unreceivedRecords =
|
|
|
+ completionPointsRecordService.getUserUnreceivedRecords(liveId, userId);
|
|
|
+
|
|
|
+ if (unreceivedRecords != null && !unreceivedRecords.isEmpty()) {
|
|
|
+ // 3. 构造弹窗消息
|
|
|
+ SendMsgVo sendMsgVo = new SendMsgVo();
|
|
|
+ sendMsgVo.setLiveId(liveId);
|
|
|
+ sendMsgVo.setUserId(userId);
|
|
|
+ sendMsgVo.setCmd("completionPoints");
|
|
|
+ sendMsgVo.setMsg("完成任务!");
|
|
|
+ sendMsgVo.setData(JSONObject.toJSONString(unreceivedRecords.get(0)));
|
|
|
+
|
|
|
+ // 4. 通过WebSocket发送给特定用户
|
|
|
+ webSocketServer.sendCompletionPointsMessage(liveId, userId, sendMsgVo);
|
|
|
+
|
|
|
+ // 5. 记录已推送,24小时后过期(第二天可以再次推送)
|
|
|
+ redisCache.setCacheObject(notifyKey, "1", 24, TimeUnit.HOURS);
|
|
|
+
|
|
|
+ log.info("发送完课积分弹窗通知成功, liveId={}, userId={}, points={}",
|
|
|
+ liveId, userId, unreceivedRecords.get(0).getPointsAwarded());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送完课通知失败, liveId={}, userId={}", liveId, userId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送完课通知(通过WebSocket推送弹窗) - 旧版本(保留)
|
|
|
*/
|
|
|
private void sendCompletionNotification(Long liveId, Long userId) {
|
|
|
try {
|