package com.tcm.training.admin.live.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.tcm.training.admin.live.entity.LiveSession; import com.tcm.training.admin.live.mapper.LiveSessionMapper; import com.tcm.training.admin.live.vo.LiveSessionStatusVO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @Service public class LiveSessionStatusSyncService { private static final Logger log = LoggerFactory.getLogger(LiveSessionStatusSyncService.class); private final LiveSessionMapper liveSessionMapper; private final LiveSessionAppService liveSessionAppService; /** * 注入直播场次查询能力与状态流转服务。 */ public LiveSessionStatusSyncService(LiveSessionMapper liveSessionMapper, LiveSessionAppService liveSessionAppService) { this.liveSessionMapper = liveSessionMapper; this.liveSessionAppService = liveSessionAppService; } /** * 扫描已到开播时间的未开始场次,并自动切换为直播中。 */ @Scheduled(fixedDelayString = "${app.live.session-status-sync-interval-ms:5000}") public void syncDueSessions() { LocalDateTime now = LocalDateTime.now(); List dueSessions = liveSessionMapper.selectList(new LambdaQueryWrapper() .eq(LiveSession::getDeleted, 0) .eq(LiveSession::getLiveStatus, 0) .isNotNull(LiveSession::getStartTime) .le(LiveSession::getStartTime, now) .orderByAsc(LiveSession::getStartTime) .orderByAsc(LiveSession::getId)); int startedCount = 0; if (dueSessions != null && !dueSessions.isEmpty()) { List effectiveDueSessions = pickEffectiveDueSessions(dueSessions); for (LiveSession dueSession : effectiveDueSessions) { // 定时任务与人工开播共用同一套状态迁移逻辑,避免状态口径分叉。 LiveSessionStatusVO statusVO = liveSessionAppService.autoStart(dueSession.getId(), now); if (statusVO == null) { log.warn("live session auto start skipped, sessionId={}, courseId={}", dueSession.getId(), dueSession.getLiveCourseId()); continue; } startedCount++; } if (startedCount > 0) { log.info("live session auto start finished, scanned={}, effective={}, started={}", dueSessions.size(), effectiveDueSessions.size(), startedCount); } } reconcileLivingCourses(now); } /** * 同一课程只挑一条最应被自动开播的场次,避免历史过期开播记录被批量拉起后又互相收敛。 */ private List pickEffectiveDueSessions(List dueSessions) { if (dueSessions == null || dueSessions.isEmpty()) { return dueSessions; } Map effectiveSessionMap = new LinkedHashMap(); for (LiveSession dueSession : dueSessions) { if (dueSession == null || dueSession.getLiveCourseId() == null) { continue; } LiveSession current = effectiveSessionMap.get(dueSession.getLiveCourseId()); if (current == null || compareDueSession(current, dueSession) < 0) { effectiveSessionMap.put(dueSession.getLiveCourseId(), dueSession); } } return new java.util.ArrayList(effectiveSessionMap.values()); } /** * 自动开播候选按计划开播时间排序,时间更晚者优先;同时间取 ID 更大的最新记录。 */ private int compareDueSession(LiveSession left, LiveSession right) { LocalDateTime leftTime = left == null ? null : left.getStartTime(); LocalDateTime rightTime = right == null ? null : right.getStartTime(); if (leftTime == null && rightTime == null) { return compareSessionId(left, right); } if (leftTime == null) { return -1; } if (rightTime == null) { return 1; } int timeCompare = leftTime.compareTo(rightTime); if (timeCompare != 0) { return timeCompare; } return compareSessionId(left, right); } private int compareSessionId(LiveSession left, LiveSession right) { Long leftId = left == null ? null : left.getId(); Long rightId = right == null ? null : right.getId(); if (leftId == null && rightId == null) { return 0; } if (leftId == null) { return -1; } if (rightId == null) { return 1; } return leftId.compareTo(rightId); } /** * 定时收敛历史残留的直播中场次,避免同课程出现多条或过期直播中状态。 */ private void reconcileLivingCourses(LocalDateTime now) { List livingSessions = liveSessionMapper.selectList(new LambdaQueryWrapper() .eq(LiveSession::getDeleted, 0) .eq(LiveSession::getLiveStatus, 1) .orderByAsc(LiveSession::getLiveCourseId) .orderByAsc(LiveSession::getId)); if (livingSessions == null || livingSessions.isEmpty()) { return; } int endedCount = autoEndExpiredLivingSessions(livingSessions, now); if (endedCount > 0) { livingSessions = liveSessionMapper.selectList(new LambdaQueryWrapper() .eq(LiveSession::getDeleted, 0) .eq(LiveSession::getLiveStatus, 1) .orderByAsc(LiveSession::getLiveCourseId) .orderByAsc(LiveSession::getId)); if (livingSessions == null || livingSessions.isEmpty()) { return; } } Set courseIds = new LinkedHashSet(); for (LiveSession livingSession : livingSessions) { if (livingSession == null || livingSession.getLiveCourseId() == null) { continue; } courseIds.add(livingSession.getLiveCourseId()); } int repairedCount = 0; for (Long courseId : courseIds) { repairedCount += liveSessionAppService.reconcileCourseLivingSessions(courseId, now); } if (repairedCount > 0) { log.info("live session reconcile finished, livingCourses={}, repaired={}", courseIds.size(), repairedCount); } } /** * 自动结束已超过配置时长的直播中场次。 */ private int autoEndExpiredLivingSessions(List livingSessions, LocalDateTime now) { if (livingSessions == null || livingSessions.isEmpty()) { return 0; } int endedCount = 0; int skippedCount = 0; for (LiveSession livingSession : livingSessions) { if (!shouldAutoEnd(livingSession, now)) { if (livingSession == null || livingSession.getDurationSeconds() == null || livingSession.getDurationSeconds().intValue() <= 0 || resolveSessionStartTime(livingSession) == null) { skippedCount++; } continue; } LiveSessionStatusVO statusVO = liveSessionAppService.autoEnd(livingSession.getId(), now); if (statusVO != null) { endedCount++; } } if (endedCount > 0 || skippedCount > 0) { log.info("live session auto end finished, scanned={}, ended={}, skipped={}", livingSessions.size(), endedCount, skippedCount); } return endedCount; } /** * 判断直播中场次是否已到自动结束时间。 */ private boolean shouldAutoEnd(LiveSession session, LocalDateTime now) { if (session == null) { return false; } Integer durationSeconds = session.getDurationSeconds(); if (durationSeconds == null || durationSeconds.intValue() <= 0) { return false; } LocalDateTime startTime = resolveSessionStartTime(session); if (startTime == null) { return false; } return !startTime.plusSeconds(durationSeconds.longValue()).isAfter(now); } /** * 优先使用实际开播时间,没有时回退到计划开播时间。 */ private LocalDateTime resolveSessionStartTime(LiveSession session) { if (session == null) { return null; } if (session.getActualStartTime() != null) { return session.getActualStartTime(); } return session.getStartTime(); } }