package com.tcm.training.admin.app.live.support;
|
|
import com.tcm.training.admin.live.entity.LiveCourse;
|
import com.tcm.training.admin.live.entity.LiveSession;
|
import lombok.Getter;
|
import lombok.Setter;
|
import org.springframework.stereotype.Component;
|
|
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
/**
|
* APP 养生直播展示态解析工具。
|
*/
|
@Component
|
public class AppLiveCourseDisplaySupport {
|
|
public static final int SESSION_STATUS_NOT_STARTED = 0;
|
public static final int SESSION_STATUS_LIVING = 1;
|
public static final int SESSION_STATUS_ENDED = 2;
|
public static final String DISPLAY_STATUS_NOT_STARTED = "NOT_STARTED";
|
public static final String DISPLAY_STATUS_LIVING = "LIVING";
|
public static final String DISPLAY_STATUS_ENDED = "ENDED";
|
|
/**
|
* 统一解析一门直播课程在当前时刻的展示态快照。
|
*/
|
public CourseDisplaySnapshot resolveDisplay(List<LiveSession> sessions,
|
LocalDateTime now,
|
long endedCooldownMinutes) {
|
List<LiveSession> safeSessions = sessions == null ? Collections.<LiveSession>emptyList() : sessions;
|
CourseDisplaySnapshot snapshot = new CourseDisplaySnapshot();
|
LiveSession latestStartedSession = findLatestStartedSession(safeSessions, now);
|
snapshot.setLatestStartedSession(latestStartedSession);
|
LiveSession currentSession = resolveCurrentSession(latestStartedSession);
|
snapshot.setCurrentSession(currentSession);
|
if (currentSession != null) {
|
snapshot.setDisplayStatus(DISPLAY_STATUS_LIVING);
|
return snapshot;
|
}
|
|
LiveSession latestEndedSession = resolveLatestEndedSession(safeSessions, latestStartedSession);
|
snapshot.setLatestEndedSession(latestEndedSession);
|
snapshot.setLatestEndedAt(resolveEndedAt(latestEndedSession));
|
if (isInEndedCooldown(latestEndedSession, now, endedCooldownMinutes)) {
|
snapshot.setDisplayStatus(DISPLAY_STATUS_ENDED);
|
snapshot.setEndedCooldown(true);
|
return snapshot;
|
}
|
|
LiveSession nextSession = findNextSession(safeSessions, now);
|
snapshot.setNextSession(nextSession);
|
if (nextSession != null) {
|
snapshot.setDisplayStatus(DISPLAY_STATUS_NOT_STARTED);
|
return snapshot;
|
}
|
|
snapshot.setDisplayStatus(DISPLAY_STATUS_ENDED);
|
return snapshot;
|
}
|
|
/**
|
* 将直播场次按课程 ID 聚合,便于首页与详情复用。
|
*/
|
public Map<Long, List<LiveSession>> groupSessionsByCourseId(List<LiveSession> sessions) {
|
Map<Long, List<LiveSession>> sessionMap = new HashMap<Long, List<LiveSession>>();
|
if (sessions == null || sessions.isEmpty()) {
|
return sessionMap;
|
}
|
for (LiveSession session : sessions) {
|
List<LiveSession> courseSessions = sessionMap.get(session.getLiveCourseId());
|
if (courseSessions == null) {
|
courseSessions = new ArrayList<LiveSession>();
|
sessionMap.put(session.getLiveCourseId(), courseSessions);
|
}
|
courseSessions.add(session);
|
}
|
return sessionMap;
|
}
|
|
/**
|
* 选择课程宣传图首图作为直播态展示图。
|
*/
|
public String resolveLiveImage(LiveCourse course) {
|
if (course == null) {
|
return null;
|
}
|
if (course.getBannerImgs() != null) {
|
for (String bannerImage : course.getBannerImgs()) {
|
String normalized = trimToNull(bannerImage);
|
if (normalized != null) {
|
return normalized;
|
}
|
}
|
}
|
return trimToNull(course.getCoverImg());
|
}
|
|
/**
|
* 解析直播中或结束冷却期的展示图。
|
*/
|
public String resolveLivingDisplayImage(String coverImage, String liveImage) {
|
return liveImage == null ? coverImage : liveImage;
|
}
|
|
/**
|
* 解析未开始或完全结束态的展示图。
|
*/
|
public String resolveStaticDisplayImage(String coverImage, String liveImage) {
|
return coverImage == null ? liveImage : coverImage;
|
}
|
|
/**
|
* 获取直播结束时间,优先使用实际结束时间。
|
*/
|
public LocalDateTime resolveEndedAt(LiveSession session) {
|
if (session == null) {
|
return null;
|
}
|
if (session.getActualEndTime() != null) {
|
return session.getActualEndTime();
|
}
|
return session.getStartTime();
|
}
|
|
/**
|
* 获取直播中场次的排序时间,优先使用实际开播时间。
|
*/
|
public LocalDateTime resolveCurrentSessionTime(LiveSession session) {
|
if (session == null) {
|
return null;
|
}
|
if (session.getActualStartTime() != null) {
|
return session.getActualStartTime();
|
}
|
return session.getStartTime();
|
}
|
|
/**
|
* 判断最近结束场次是否仍处于冷却窗口。
|
*/
|
public boolean isInEndedCooldown(LiveSession latestEndedSession,
|
LocalDateTime now,
|
long endedCooldownMinutes) {
|
LocalDateTime endedAt = resolveEndedAt(latestEndedSession);
|
if (endedAt == null) {
|
return false;
|
}
|
return endedAt.plusMinutes(endedCooldownMinutes).isAfter(now);
|
}
|
|
/**
|
* 归一化字符串,空白值统一转为 null。
|
*/
|
public String trimToNull(String value) {
|
if (value == null) {
|
return null;
|
}
|
String trimmed = value.trim();
|
return trimmed.isEmpty() ? null : trimmed;
|
}
|
|
/**
|
* 将可空整型归一化为默认值 0。
|
*/
|
public int defaultNumber(Integer value) {
|
return value == null ? 0 : value.intValue();
|
}
|
|
/**
|
* 比较两个时间值,null 视为最弱排序值。
|
*/
|
public int compareSessionTime(LocalDateTime left, LocalDateTime right) {
|
if (left == null && right == null) {
|
return 0;
|
}
|
if (left == null) {
|
return 1;
|
}
|
if (right == null) {
|
return -1;
|
}
|
return left.compareTo(right);
|
}
|
|
/**
|
* 选择时间线上最近一场已经开始的场次。
|
*/
|
private LiveSession findLatestStartedSession(List<LiveSession> sessions, LocalDateTime now) {
|
LiveSession result = null;
|
for (LiveSession session : sessions) {
|
if (session.getStartTime() == null || session.getStartTime().isAfter(now)) {
|
continue;
|
}
|
if (result == null || compareStartedSession(result, session) < 0) {
|
result = session;
|
}
|
}
|
return result;
|
}
|
|
/**
|
* 将时间线上最近一场已开始场次解析为当前直播场次。
|
*/
|
private LiveSession resolveCurrentSession(LiveSession latestStartedSession) {
|
if (latestStartedSession == null) {
|
return null;
|
}
|
if (defaultNumber(latestStartedSession.getLiveStatus()) != SESSION_STATUS_LIVING) {
|
return null;
|
}
|
return latestStartedSession;
|
}
|
|
/**
|
* 解析最近一场已结束场次,优先使用时间线上的最新已开始场次。
|
*/
|
private LiveSession resolveLatestEndedSession(List<LiveSession> sessions, LiveSession latestStartedSession) {
|
if (latestStartedSession != null && defaultNumber(latestStartedSession.getLiveStatus()) == SESSION_STATUS_ENDED) {
|
return latestStartedSession;
|
}
|
LiveSession result = null;
|
for (LiveSession session : sessions) {
|
if (defaultNumber(session.getLiveStatus()) != SESSION_STATUS_ENDED) {
|
continue;
|
}
|
if (result == null || compareEndedSession(result, session) < 0) {
|
result = session;
|
}
|
}
|
return result;
|
}
|
|
/**
|
* 选择下一场待开播的场次。
|
*/
|
private LiveSession findNextSession(List<LiveSession> sessions, LocalDateTime now) {
|
LiveSession result = null;
|
for (LiveSession session : sessions) {
|
if (defaultNumber(session.getLiveStatus()) != SESSION_STATUS_NOT_STARTED) {
|
continue;
|
}
|
if (session.getStartTime() == null || !session.getStartTime().isAfter(now)) {
|
continue;
|
}
|
if (result == null || compareSessionTime(session.getStartTime(), result.getStartTime()) < 0) {
|
result = session;
|
}
|
}
|
return result;
|
}
|
|
/**
|
* 比较两场已开始场次的时间线先后,开始时间更晚者优先。
|
*/
|
private int compareStartedSession(LiveSession left, LiveSession right) {
|
int startTimeCompare = compareSessionTime(resolveCurrentSessionTime(left), resolveCurrentSessionTime(right));
|
if (startTimeCompare != 0) {
|
return startTimeCompare;
|
}
|
return compareSessionId(left, right);
|
}
|
|
/**
|
* 比较两场已结束场次的时间线先后,结束时间更晚者优先。
|
*/
|
private int compareEndedSession(LiveSession left, LiveSession right) {
|
int endedAtCompare = compareSessionTime(resolveEndedAt(left), resolveEndedAt(right));
|
if (endedAtCompare != 0) {
|
return endedAtCompare;
|
}
|
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);
|
}
|
|
/**
|
* 课程展示态快照。
|
*/
|
@Getter
|
@Setter
|
public static class CourseDisplaySnapshot {
|
|
private String displayStatus;
|
private LiveSession currentSession;
|
private LiveSession latestStartedSession;
|
private LiveSession latestEndedSession;
|
private LiveSession nextSession;
|
private LocalDateTime latestEndedAt;
|
private boolean endedCooldown;
|
}
|
}
|