package com.tcm.training.admin.live.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.tcm.training.admin.app.notice.service.AppSystemNoticeDispatchCommand; import com.tcm.training.admin.app.notice.service.AppSystemNoticeDispatchService; import com.tcm.training.admin.app.notice.service.AppSystemNoticeType; import com.tcm.training.admin.app.settings.entity.AppUserNotificationSetting; import com.tcm.training.admin.app.settings.mapper.AppUserNotificationSettingMapper; import com.tcm.training.admin.live.entity.LiveCourse; import com.tcm.training.admin.live.entity.LiveReserve; import com.tcm.training.admin.live.entity.LiveSession; import com.tcm.training.admin.live.mapper.LiveCourseMapper; import com.tcm.training.admin.live.mapper.LiveReserveMapper; import com.tcm.training.admin.live.mapper.LiveSessionMapper; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 直播提醒通知分发服务。 */ @Service public class LiveRemindDispatchService { private final LiveSessionMapper liveSessionMapper; private final LiveReserveMapper liveReserveMapper; private final LiveCourseMapper liveCourseMapper; private final AppUserNotificationSettingMapper appUserNotificationSettingMapper; private final AppSystemNoticeDispatchService appSystemNoticeDispatchService; /** * 注入直播提醒分发所需依赖。 */ public LiveRemindDispatchService(LiveSessionMapper liveSessionMapper, LiveReserveMapper liveReserveMapper, LiveCourseMapper liveCourseMapper, AppUserNotificationSettingMapper appUserNotificationSettingMapper, AppSystemNoticeDispatchService appSystemNoticeDispatchService) { this.liveSessionMapper = liveSessionMapper; this.liveReserveMapper = liveReserveMapper; this.liveCourseMapper = liveCourseMapper; this.appUserNotificationSettingMapper = appUserNotificationSettingMapper; this.appSystemNoticeDispatchService = appSystemNoticeDispatchService; } /** * 定时扫描即将开播的预约用户,并按用户设置的提前提醒时间发放通知。 */ @Scheduled(fixedDelayString = "${app.live.remind-dispatch-interval-ms:60000}") public void dispatchScheduledReminds() { LocalDateTime now = LocalDateTime.now(); List sessions = liveSessionMapper.selectList(new LambdaQueryWrapper() .eq(LiveSession::getDeleted, 0) .eq(LiveSession::getLiveStatus, 0) .isNotNull(LiveSession::getStartTime) .gt(LiveSession::getStartTime, now) .orderByAsc(LiveSession::getStartTime) .orderByAsc(LiveSession::getId)); for (LiveSession session : sessions) { dispatchRemindsForSession(session, now, false); } } /** * 手动触发指定场次的直播提醒发放。 */ public long dispatchManualRemind(LiveSession session) { if (session == null) { return 0L; } return dispatchRemindsForSession(session, LocalDateTime.now(), true); } /** * 根据预约记录与提醒时间配置发放直播提醒。 */ private long dispatchRemindsForSession(LiveSession session, LocalDateTime now, boolean ignoreReminderWindow) { if (session == null || session.getId() == null || session.getStartTime() == null) { return 0L; } List reserves = liveReserveMapper.selectList(new LambdaQueryWrapper() .eq(LiveReserve::getSessionId, session.getId()) .eq(LiveReserve::getIsCancel, 0)); if (reserves.isEmpty()) { return 0L; } LiveCourse liveCourse = liveCourseMapper.selectById(session.getLiveCourseId()); long dispatchedCount = 0L; for (LiveReserve reserve : reserves) { if (reserve.getUserId() == null) { continue; } Integer advanceMinutes = resolveAdvanceMinutes(reserve.getUserId()); if (!ignoreReminderWindow && !isWithinReminderWindow(session.getStartTime(), now, advanceMinutes)) { continue; } AppSystemNoticeDispatchCommand command = new AppSystemNoticeDispatchCommand(); command.setAppUserId(reserve.getUserId()); command.setNoticeTypeCode(AppSystemNoticeType.LIVE_REMIND); command.setTitle("直播提醒"); command.setContent(defaultCourseTitle(liveCourse) + " 即将开播"); command.setReferenceType("LIVE_SESSION"); command.setReferenceId(session.getId()); command.setRequireUserSetting(Boolean.TRUE); Map payload = new LinkedHashMap(); payload.put("courseId", session.getLiveCourseId() == null ? "" : String.valueOf(session.getLiveCourseId())); payload.put("sessionId", String.valueOf(session.getId())); payload.put("startTime", String.valueOf(session.getStartTime())); payload.put("courseTitle", defaultCourseTitle(liveCourse)); command.setPayload(payload); appSystemNoticeDispatchService.createAndPush(command); dispatchedCount++; } return dispatchedCount; } /** * 读取用户设置的提前提醒分钟数,未配置时默认按 60 分钟处理。 */ private Integer resolveAdvanceMinutes(Long appUserId) { AppUserNotificationSetting setting = appUserNotificationSettingMapper.selectOne(new LambdaQueryWrapper() .eq(AppUserNotificationSetting::getAppUserId, appUserId) .eq(AppUserNotificationSetting::getDeleted, 0) .last("limit 1")); if (setting == null || setting.getAdvanceNoticeMinutes() == null || setting.getAdvanceNoticeMinutes().intValue() <= 0) { return 60; } return setting.getAdvanceNoticeMinutes(); } /** * 判断当前时间是否已进入当前用户对应的提醒发送窗口。 */ private boolean isWithinReminderWindow(LocalDateTime startTime, LocalDateTime now, Integer advanceMinutes) { if (startTime == null || now == null) { return false; } LocalDateTime remindTime = startTime.minusMinutes(advanceMinutes == null ? 60L : advanceMinutes.longValue()); return !now.isBefore(remindTime) && now.isBefore(startTime); } /** * 返回安全的直播课程标题。 */ private String defaultCourseTitle(LiveCourse liveCourse) { if (liveCourse == null || liveCourse.getTitle() == null) { return "您预约的直播"; } return liveCourse.getTitle(); } }