| | |
| | | package com.tcm.training.admin.app.notice.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.tcm.training.admin.app.auth.service.AppAuthSessionService; |
| | | import com.tcm.training.admin.app.notice.entity.AppSystemNotice; |
| | | import com.tcm.training.admin.app.notice.entity.AppUserActivityNotice; |
| | | import com.tcm.training.admin.app.notice.entity.AppUserNoticeReadRecord; |
| | | import com.tcm.training.admin.app.notice.mapper.AppSystemNoticeMapper; |
| | | import com.tcm.training.admin.app.notice.mapper.AppUserActivityNoticeMapper; |
| | | import com.tcm.training.admin.app.notice.mapper.AppUserNoticeReadRecordMapper; |
| | | import com.tcm.training.admin.app.notice.vo.AppActivityNoticeDetailVO; |
| | | import com.tcm.training.admin.app.notice.vo.AppActivityNoticeListItemVO; |
| | | import com.tcm.training.admin.app.notice.vo.AppNoticeDetailVO; |
| | | import com.tcm.training.admin.app.notice.vo.AppNoticeItemVO; |
| | | import com.tcm.training.admin.app.settings.entity.AppUserNotificationSetting; |
| | | import com.tcm.training.admin.app.settings.mapper.AppUserNotificationSettingMapper; |
| | | import com.tcm.training.admin.common.PageResponse; |
| | | import com.tcm.training.admin.live.entity.CoursePurchaseOrder; |
| | | import com.tcm.training.admin.live.entity.HealthCourse; |
| | | import com.tcm.training.admin.live.entity.HealthCourseMemberJoinRecord; |
| | |
| | | private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm"); |
| | | private static final int USER_STATUS_ENABLED = 1; |
| | | private static final int USER_DELETED_NO = 0; |
| | | private static final int RECORD_DELETED_NO = 0; |
| | | private static final int COURSE_STATUS_ENABLED = 1; |
| | | private static final int LIVE_COURSE_STATUS_ENABLED = 1; |
| | | private static final int LIVE_STATUS_PENDING = 0; |
| | |
| | | private final HealthCourseMemberJoinRecordMapper healthCourseMemberJoinRecordMapper; |
| | | private final AppUserNotificationSettingMapper appUserNotificationSettingMapper; |
| | | private final AppUserNoticeReadRecordMapper appUserNoticeReadRecordMapper; |
| | | private final AppUserActivityNoticeMapper appUserActivityNoticeMapper; |
| | | private final AppSystemNoticeMapper appSystemNoticeMapper; |
| | | |
| | | /** |
| | | * 注入 APP 通知列表所需依赖。 |
| | |
| | | HealthCourseVideoMapper healthCourseVideoMapper, |
| | | HealthCourseMemberJoinRecordMapper healthCourseMemberJoinRecordMapper, |
| | | AppUserNotificationSettingMapper appUserNotificationSettingMapper, |
| | | AppUserNoticeReadRecordMapper appUserNoticeReadRecordMapper) { |
| | | AppUserNoticeReadRecordMapper appUserNoticeReadRecordMapper, |
| | | AppUserActivityNoticeMapper appUserActivityNoticeMapper, |
| | | AppSystemNoticeMapper appSystemNoticeMapper) { |
| | | this.appAuthSessionService = appAuthSessionService; |
| | | this.userMapper = userMapper; |
| | | this.systemSettingsMapper = systemSettingsMapper; |
| | |
| | | this.healthCourseMemberJoinRecordMapper = healthCourseMemberJoinRecordMapper; |
| | | this.appUserNotificationSettingMapper = appUserNotificationSettingMapper; |
| | | this.appUserNoticeReadRecordMapper = appUserNoticeReadRecordMapper; |
| | | this.appUserActivityNoticeMapper = appUserActivityNoticeMapper; |
| | | this.appSystemNoticeMapper = appSystemNoticeMapper; |
| | | } |
| | | |
| | | /** |
| | |
| | | for (String noticeType : NOTICE_TYPES) { |
| | | saveReadRecord(userId, noticeType, now); |
| | | } |
| | | markAllActivityRead(userId, now); |
| | | } |
| | | |
| | | /** |
| | |
| | | Long userId = requireCurrentEnabledUser().getId(); |
| | | String normalizedNoticeType = normalizeNoticeType(noticeType); |
| | | saveReadRecord(userId, normalizedNoticeType, LocalDateTime.now()); |
| | | if (NOTICE_TYPE_ACTIVITY.equals(normalizedNoticeType)) { |
| | | markAllActivityRead(userId, LocalDateTime.now()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取当前登录用户指定类型通知详情。 |
| | | */ |
| | | public AppNoticeDetailVO getNoticeDetail(String noticeType) { |
| | | AppUser user = requireCurrentEnabledUser(); |
| | | String normalizedNoticeType = normalizeNoticeType(noticeType); |
| | | if (NOTICE_TYPE_ACTIVITY.equals(normalizedNoticeType)) { |
| | | throw new IllegalArgumentException("活动通知请使用活动通知详情接口"); |
| | | } |
| | | AppUserNotificationSetting setting = getNotificationSetting(user.getId()); |
| | | Map<String, LocalDateTime> readTimeMap = getReadTimeMap(user.getId()); |
| | | if (NOTICE_TYPE_SERVICE.equals(normalizedNoticeType)) { |
| | | return buildServiceNoticeDetail(user, readTimeMap.get(normalizedNoticeType)); |
| | | } |
| | | if (NOTICE_TYPE_MEMBER_EXPIRE.equals(normalizedNoticeType)) { |
| | | return buildMemberExpireNoticeDetail(user, readTimeMap.get(normalizedNoticeType)); |
| | | } |
| | | if (NOTICE_TYPE_LIVE_RESERVE.equals(normalizedNoticeType)) { |
| | | return buildLiveReserveNoticeDetail(user.getId(), setting, readTimeMap.get(normalizedNoticeType)); |
| | | } |
| | | if (NOTICE_TYPE_ORDER.equals(normalizedNoticeType)) { |
| | | return buildOrderNoticeDetail(user.getId(), setting, readTimeMap.get(normalizedNoticeType)); |
| | | } |
| | | if (NOTICE_TYPE_COURSE_UPDATE.equals(normalizedNoticeType)) { |
| | | return buildCourseUpdateNoticeDetail(user.getId(), setting, readTimeMap.get(normalizedNoticeType)); |
| | | } |
| | | throw new IllegalArgumentException("通知类型不合法"); |
| | | } |
| | | |
| | | /** |
| | | * 获取当前登录用户的活动通知分页列表。 |
| | | */ |
| | | public PageResponse<AppActivityNoticeListItemVO> pageActivityNotices(int page, int pageSize) { |
| | | Long userId = requireCurrentEnabledUser().getId(); |
| | | Page<AppSystemNotice> result = appSystemNoticeMapper.selectPage( |
| | | new Page<AppSystemNotice>(Math.max(page, 1), Math.max(pageSize, 1)), |
| | | new LambdaQueryWrapper<AppSystemNotice>() |
| | | .eq(AppSystemNotice::getAppUserId, userId) |
| | | .eq(AppSystemNotice::getNoticeTypeCode, AppSystemNoticeType.ACTIVITY_NOTICE) |
| | | .eq(AppSystemNotice::getDeleted, RECORD_DELETED_NO) |
| | | .orderByDesc(AppSystemNotice::getCreatedAt) |
| | | .orderByDesc(AppSystemNotice::getId) |
| | | ); |
| | | List<AppActivityNoticeListItemVO> list = new ArrayList<AppActivityNoticeListItemVO>(); |
| | | for (AppSystemNotice record : result.getRecords()) { |
| | | ActivityNotice activityNotice = requireActivityNotice(record.getReferenceId()); |
| | | if (activityNotice == null) { |
| | | continue; |
| | | } |
| | | list.add(toActivityListItem(record, activityNotice)); |
| | | } |
| | | return PageResponse.of(list, result.getTotal(), result.getCurrent(), result.getSize()); |
| | | } |
| | | |
| | | /** |
| | | * 获取当前登录用户的活动通知详情。 |
| | | */ |
| | | public AppActivityNoticeDetailVO getActivityNoticeDetail(Long id) { |
| | | AppSystemNotice record = requireActivitySystemNoticeRecord(requireCurrentEnabledUser().getId(), id); |
| | | ActivityNotice notice = requireActivityNotice(record.getReferenceId()); |
| | | AppActivityNoticeDetailVO vo = new AppActivityNoticeDetailVO(); |
| | | vo.setId(record.getId()); |
| | | vo.setTitle(trimToEmpty(notice.getTitle())); |
| | | vo.setContentHtml(trimToEmpty(notice.getContentHtml())); |
| | | vo.setCreatedAt(formatTime(chooseTime(record.getCreatedAt(), record.getPushedAt(), notice.getCreatedAt()))); |
| | | vo.setRead(Boolean.valueOf(record.getReadAt() != null)); |
| | | return vo; |
| | | } |
| | | |
| | | /** |
| | | * 将当前登录用户的一条活动通知标记为已读。 |
| | | */ |
| | | @Transactional |
| | | public void markActivityRead(Long id) { |
| | | AppSystemNotice record = requireActivitySystemNoticeRecord(requireCurrentEnabledUser().getId(), id); |
| | | record.setReadAt(LocalDateTime.now()); |
| | | appSystemNoticeMapper.updateById(record); |
| | | } |
| | | |
| | | /** |
| | |
| | | "/static/image/notice/service.png", |
| | | calculateUnreadBadge(noticeTime, lastReadAt) |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * 构建系统客服通知详情。 |
| | | */ |
| | | private AppNoticeDetailVO buildServiceNoticeDetail(AppUser user, LocalDateTime lastReadAt) { |
| | | SystemSettings settings = systemSettingsMapper.selectOne(new LambdaQueryWrapper<SystemSettings>() |
| | | .eq(SystemSettings::getDeleted, 0) |
| | | .orderByDesc(SystemSettings::getId) |
| | | .last("limit 1")); |
| | | String phone = trimToEmpty(settings == null ? null : settings.getServicePhone()); |
| | | String onlineTime = trimToEmpty(settings == null ? null : settings.getServiceOnlineTime()); |
| | | StringBuilder content = new StringBuilder(); |
| | | content.append("<p>如需帮助,请联系系统客服。</p>"); |
| | | if (!phone.isEmpty()) { |
| | | content.append("<p>客服电话:").append(phone).append("</p>"); |
| | | } |
| | | if (!onlineTime.isEmpty()) { |
| | | content.append("<p>服务时间:").append(onlineTime).append("</p>"); |
| | | } |
| | | LocalDateTime noticeTime = settings == null ? null : settings.getUpdatedAt(); |
| | | return buildNoticeDetailVO(NOTICE_TYPE_SERVICE, "系统客服", content.toString(), noticeTime, !isUnread(noticeTime, lastReadAt)); |
| | | } |
| | | |
| | | /** |
| | |
| | | "/static/image/notice/lingdang.png", |
| | | badge |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * 构建会员到期提醒详情。 |
| | | */ |
| | | private AppNoticeDetailVO buildMemberExpireNoticeDetail(AppUser user, LocalDateTime lastReadAt) { |
| | | LocalDateTime expireAt = user.getMemberExpireAt(); |
| | | LocalDateTime noticeTime = null; |
| | | String content = "<p>暂无会员到期提醒</p>"; |
| | | if (expireAt != null) { |
| | | int remindDays = resolveMemberExpireRemindDays(); |
| | | LocalDateTime remindTime = expireAt.minusDays(remindDays); |
| | | if (!LocalDateTime.now().isBefore(remindTime)) { |
| | | noticeTime = expireAt; |
| | | content = "<p>您的会员有效期不足" + remindDays + "天,为避免影响你的学习体验,请及时续费。</p>" |
| | | + "<p>会员到期时间:" + formatTime(expireAt) + "</p>"; |
| | | } |
| | | } |
| | | return buildNoticeDetailVO(NOTICE_TYPE_MEMBER_EXPIRE, "会员到期提醒", content, noticeTime, !isUnread(noticeTime, lastReadAt)); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 构建直播提醒详情。 |
| | | */ |
| | | private AppNoticeDetailVO buildLiveReserveNoticeDetail(Long userId, |
| | | AppUserNotificationSetting setting, |
| | | LocalDateTime lastReadAt) { |
| | | if (!isEnabled(setting == null ? null : setting.getLiveReservationEnabled())) { |
| | | return buildNoticeDetailVO( |
| | | NOTICE_TYPE_LIVE_RESERVE, |
| | | "直播提醒", |
| | | "<p>直播预约通知已关闭</p>", |
| | | null, |
| | | Boolean.TRUE |
| | | ); |
| | | } |
| | | NotificationSnapshot snapshot = resolveLiveReserveSnapshot(userId, setting); |
| | | String content = snapshot == null ? "<p>暂无直播预约提醒</p>" : wrapParagraph(snapshot.getContent()); |
| | | LocalDateTime noticeTime = snapshot == null ? null : snapshot.getNoticeTime(); |
| | | return buildNoticeDetailVO(NOTICE_TYPE_LIVE_RESERVE, "直播提醒", content, noticeTime, !isUnread(noticeTime, lastReadAt)); |
| | | } |
| | | |
| | | /** |
| | | * 构建活动通知。 |
| | | */ |
| | | private AppNoticeItemVO buildActivityNotice(AppUserNotificationSetting setting, LocalDateTime lastReadAt) { |
| | |
| | | 0 |
| | | ); |
| | | } |
| | | List<ActivityNotice> notices = activityNoticeMapper.selectList(new LambdaQueryWrapper<ActivityNotice>() |
| | | .eq(ActivityNotice::getDeleted, 0) |
| | | .orderByDesc(ActivityNotice::getCreatedAt) |
| | | .orderByDesc(ActivityNotice::getId)); |
| | | if (notices == null || notices.isEmpty()) { |
| | | Long userId = requireCurrentEnabledUser().getId(); |
| | | List<AppSystemNotice> noticeRecords = appSystemNoticeMapper.selectList(new LambdaQueryWrapper<AppSystemNotice>() |
| | | .eq(AppSystemNotice::getAppUserId, userId) |
| | | .eq(AppSystemNotice::getNoticeTypeCode, AppSystemNoticeType.ACTIVITY_NOTICE) |
| | | .eq(AppSystemNotice::getDeleted, RECORD_DELETED_NO) |
| | | .orderByDesc(AppSystemNotice::getCreatedAt) |
| | | .orderByDesc(AppSystemNotice::getId)); |
| | | if (noticeRecords == null || noticeRecords.isEmpty()) { |
| | | return buildNoticeItem( |
| | | NOTICE_TYPE_ACTIVITY, |
| | | "活动通知", |
| | |
| | | 0 |
| | | ); |
| | | } |
| | | ActivityNotice latest = notices.get(0); |
| | | AppSystemNotice latestRecord = noticeRecords.get(0); |
| | | ActivityNotice latest = requireActivityNotice(latestRecord.getReferenceId()); |
| | | int unreadCount = 0; |
| | | for (ActivityNotice notice : notices) { |
| | | if (isUnread(notice.getCreatedAt(), lastReadAt)) { |
| | | for (AppSystemNotice noticeRecord : noticeRecords) { |
| | | if (noticeRecord.getReadAt() == null) { |
| | | unreadCount++; |
| | | } |
| | | } |
| | |
| | | NOTICE_TYPE_ACTIVITY, |
| | | "活动通知", |
| | | trimToEmpty(latest.getTitle()), |
| | | latest.getCreatedAt(), |
| | | chooseTime(latestRecord.getCreatedAt(), latestRecord.getPushedAt(), latest.getCreatedAt()), |
| | | "/static/image/notice/activity.png", |
| | | unreadCount |
| | | ); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 构建订单状态更新详情。 |
| | | */ |
| | | private AppNoticeDetailVO buildOrderNoticeDetail(Long userId, |
| | | AppUserNotificationSetting setting, |
| | | LocalDateTime lastReadAt) { |
| | | if (!isEnabled(setting == null ? null : setting.getOrderStatusUpdateEnabled())) { |
| | | return buildNoticeDetailVO(NOTICE_TYPE_ORDER, "订单状态更新", "<p>订单状态更新通知已关闭</p>", null, Boolean.TRUE); |
| | | } |
| | | NotificationSnapshot latestSnapshot = resolveOrderSnapshot(userId); |
| | | String content = latestSnapshot == null ? "<p>暂无订单状态更新</p>" : wrapParagraph(latestSnapshot.getContent()); |
| | | LocalDateTime noticeTime = latestSnapshot == null ? null : latestSnapshot.getNoticeTime(); |
| | | return buildNoticeDetailVO(NOTICE_TYPE_ORDER, "订单状态更新", content, noticeTime, !isUnread(noticeTime, lastReadAt)); |
| | | } |
| | | |
| | | /** |
| | | * 构建课程更新通知。 |
| | | */ |
| | | private AppNoticeItemVO buildCourseUpdateNotice(Long userId, |
| | |
| | | } |
| | | |
| | | /** |
| | | * 构建课程更新通知详情。 |
| | | */ |
| | | private AppNoticeDetailVO buildCourseUpdateNoticeDetail(Long userId, |
| | | AppUserNotificationSetting setting, |
| | | LocalDateTime lastReadAt) { |
| | | if (!isEnabled(setting == null ? null : setting.getCourseUpdateEnabled())) { |
| | | return buildNoticeDetailVO(NOTICE_TYPE_COURSE_UPDATE, "课程更新通知", "<p>课程更新通知已关闭</p>", null, Boolean.TRUE); |
| | | } |
| | | NotificationSnapshot latestSnapshot = resolveCourseUpdateSnapshot(userId); |
| | | String content = latestSnapshot == null ? "<p>暂无课程更新通知</p>" : wrapParagraph(latestSnapshot.getContent()); |
| | | LocalDateTime noticeTime = latestSnapshot == null ? null : latestSnapshot.getNoticeTime(); |
| | | return buildNoticeDetailVO(NOTICE_TYPE_COURSE_UPDATE, "课程更新通知", content, noticeTime, !isUnread(noticeTime, lastReadAt)); |
| | | } |
| | | |
| | | /** |
| | | * 组装统一通知列表项。 |
| | | */ |
| | | private AppNoticeItemVO buildNoticeItem(String type, |
| | |
| | | itemVO.setBadge(Integer.valueOf(Math.max(unreadCount, 0))); |
| | | itemVO.setHasRedDot(Boolean.valueOf(unreadCount > 0)); |
| | | return itemVO; |
| | | } |
| | | |
| | | /** |
| | | * 组装统一通知详情。 |
| | | */ |
| | | private AppNoticeDetailVO buildNoticeDetailVO(String type, |
| | | String title, |
| | | String contentHtml, |
| | | LocalDateTime noticeTime, |
| | | Boolean read) { |
| | | AppNoticeDetailVO detailVO = new AppNoticeDetailVO(); |
| | | detailVO.setType(type); |
| | | detailVO.setTitle(title); |
| | | detailVO.setContentHtml(trimToEmpty(contentHtml)); |
| | | detailVO.setCreatedAt(formatTime(noticeTime)); |
| | | detailVO.setRead(read); |
| | | return detailVO; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | record.setLastReadAt(readAt); |
| | | appUserNoticeReadRecordMapper.updateById(record); |
| | | } |
| | | |
| | | /** |
| | | * 将当前用户全部活动通知标记为已读。 |
| | | */ |
| | | private void markAllActivityRead(Long userId, LocalDateTime readAt) { |
| | | appSystemNoticeMapper.update( |
| | | null, |
| | | new LambdaUpdateWrapper<AppSystemNotice>() |
| | | .eq(AppSystemNotice::getAppUserId, userId) |
| | | .eq(AppSystemNotice::getNoticeTypeCode, AppSystemNoticeType.ACTIVITY_NOTICE) |
| | | .eq(AppSystemNotice::getDeleted, RECORD_DELETED_NO) |
| | | .isNull(AppSystemNotice::getReadAt) |
| | | .set(AppSystemNotice::getReadAt, readAt) |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * 校验当前用户可访问的活动通知记录。 |
| | | */ |
| | | private AppUserActivityNotice requireActivityNoticeRecord(Long userId, Long id) { |
| | | AppUserActivityNotice record = appUserActivityNoticeMapper.selectOne(new LambdaQueryWrapper<AppUserActivityNotice>() |
| | | .eq(AppUserActivityNotice::getId, id) |
| | | .eq(AppUserActivityNotice::getAppUserId, userId) |
| | | .eq(AppUserActivityNotice::getDeleted, RECORD_DELETED_NO) |
| | | .last("limit 1")); |
| | | if (record == null) { |
| | | throw new IllegalArgumentException("通知不存在"); |
| | | } |
| | | requireActivityNotice(record.getActivityNoticeId()); |
| | | return record; |
| | | } |
| | | |
| | | /** |
| | | * 校验当前用户可访问的统一活动通知记录。 |
| | | */ |
| | | private AppSystemNotice requireActivitySystemNoticeRecord(Long userId, Long id) { |
| | | AppSystemNotice record = appSystemNoticeMapper.selectOne(new LambdaQueryWrapper<AppSystemNotice>() |
| | | .eq(AppSystemNotice::getId, id) |
| | | .eq(AppSystemNotice::getAppUserId, userId) |
| | | .eq(AppSystemNotice::getNoticeTypeCode, AppSystemNoticeType.ACTIVITY_NOTICE) |
| | | .eq(AppSystemNotice::getDeleted, RECORD_DELETED_NO) |
| | | .last("limit 1")); |
| | | if (record == null) { |
| | | throw new IllegalArgumentException("通知不存在"); |
| | | } |
| | | requireActivityNotice(record.getReferenceId()); |
| | | return record; |
| | | } |
| | | |
| | | /** |
| | | * 校验活动通知主表记录仍然有效。 |
| | | */ |
| | | private ActivityNotice requireActivityNotice(Long activityNoticeId) { |
| | | if (activityNoticeId == null) { |
| | | return null; |
| | | } |
| | | ActivityNotice notice = activityNoticeMapper.selectOne(new LambdaQueryWrapper<ActivityNotice>() |
| | | .eq(ActivityNotice::getId, activityNoticeId) |
| | | .eq(ActivityNotice::getDeleted, RECORD_DELETED_NO) |
| | | .last("limit 1")); |
| | | if (notice == null) { |
| | | throw new IllegalArgumentException("通知不存在"); |
| | | } |
| | | return notice; |
| | | } |
| | | |
| | | /** |
| | | * 组装活动通知列表项。 |
| | | */ |
| | | private AppActivityNoticeListItemVO toActivityListItem(AppSystemNotice record, ActivityNotice notice) { |
| | | AppActivityNoticeListItemVO itemVO = new AppActivityNoticeListItemVO(); |
| | | itemVO.setId(record.getId()); |
| | | itemVO.setTitle(trimToEmpty(notice.getTitle())); |
| | | itemVO.setCreatedAt(formatTime(chooseTime(record.getCreatedAt(), record.getPushedAt(), notice.getCreatedAt()))); |
| | | itemVO.setRead(Boolean.valueOf(record.getReadAt() != null)); |
| | | return itemVO; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 解析直播提醒最新快照。 |
| | | */ |
| | | private NotificationSnapshot resolveLiveReserveSnapshot(Long userId, AppUserNotificationSetting setting) { |
| | | List<LiveReserve> reserves = liveReserveMapper.selectList(new LambdaQueryWrapper<LiveReserve>() |
| | | .eq(LiveReserve::getUserId, userId) |
| | | .eq(LiveReserve::getIsCancel, 0) |
| | | .orderByDesc(LiveReserve::getReserveTime) |
| | | .orderByDesc(LiveReserve::getId)); |
| | | if (reserves == null || reserves.isEmpty()) { |
| | | return null; |
| | | } |
| | | List<Long> sessionIds = new ArrayList<Long>(); |
| | | for (LiveReserve reserve : reserves) { |
| | | if (reserve.getSessionId() != null) { |
| | | sessionIds.add(reserve.getSessionId()); |
| | | } |
| | | } |
| | | if (sessionIds.isEmpty()) { |
| | | return null; |
| | | } |
| | | List<LiveSession> sessions = liveSessionMapper.selectList(new LambdaQueryWrapper<LiveSession>() |
| | | .in(LiveSession::getId, sessionIds) |
| | | .eq(LiveSession::getDeleted, 0)); |
| | | if (sessions == null || sessions.isEmpty()) { |
| | | return null; |
| | | } |
| | | Map<Long, LiveSession> sessionMap = new HashMap<Long, LiveSession>(); |
| | | for (LiveSession session : sessions) { |
| | | sessionMap.put(session.getId(), session); |
| | | } |
| | | List<Long> courseIds = new ArrayList<Long>(); |
| | | for (LiveSession session : sessions) { |
| | | if (session.getLiveCourseId() != null) { |
| | | courseIds.add(session.getLiveCourseId()); |
| | | } |
| | | } |
| | | Map<Long, LiveCourse> courseMap = getLiveCourseMap(courseIds); |
| | | NotificationSnapshot snapshot = null; |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | Integer advanceMinutes = setting == null ? null : setting.getAdvanceNoticeMinutes(); |
| | | int normalizedAdvanceMinutes = advanceMinutes == null || advanceMinutes.intValue() <= 0 ? 60 : advanceMinutes.intValue(); |
| | | for (LiveReserve reserve : reserves) { |
| | | LiveSession session = sessionMap.get(reserve.getSessionId()); |
| | | if (session == null || session.getStartTime() == null || session.getLiveStatus() == null) { |
| | | continue; |
| | | } |
| | | if (session.getLiveStatus().intValue() != LIVE_STATUS_PENDING) { |
| | | continue; |
| | | } |
| | | LocalDateTime remindTime = session.getStartTime().minusMinutes(normalizedAdvanceMinutes); |
| | | if (now.isBefore(remindTime)) { |
| | | continue; |
| | | } |
| | | LiveCourse liveCourse = courseMap.get(session.getLiveCourseId()); |
| | | String title = trimToEmpty(liveCourse == null ? null : liveCourse.getTitle()); |
| | | String content = title.isEmpty() ? "您预约的直播即将开播。" : "您预约的【" + title + "】即将开播。"; |
| | | NotificationSnapshot current = new NotificationSnapshot(content, session.getStartTime()); |
| | | if (snapshot == null || compareTime(current.getNoticeTime(), snapshot.getNoticeTime()) > 0) { |
| | | snapshot = current; |
| | | } |
| | | } |
| | | return snapshot; |
| | | } |
| | | |
| | | /** |
| | | * 解析订单状态更新最新快照。 |
| | | */ |
| | | private NotificationSnapshot resolveOrderSnapshot(Long userId) { |
| | | NotificationSnapshot latestSnapshot = null; |
| | | List<PointsExchangeOrder> exchangeOrders = pointsExchangeOrderMapper.selectList(new LambdaQueryWrapper<PointsExchangeOrder>() |
| | | .eq(PointsExchangeOrder::getClinicAppUserId, userId) |
| | | .eq(PointsExchangeOrder::getDeleted, 0) |
| | | .orderByDesc(PointsExchangeOrder::getUpdatedAt) |
| | | .orderByDesc(PointsExchangeOrder::getId)); |
| | | for (PointsExchangeOrder order : exchangeOrders) { |
| | | LocalDateTime noticeTime = chooseTime(order.getShippedAt(), order.getUpdatedAt(), order.getCreatedAt()); |
| | | NotificationSnapshot snapshot = new NotificationSnapshot(buildExchangeOrderContent(order), noticeTime); |
| | | if (latestSnapshot == null || compareTime(snapshot.getNoticeTime(), latestSnapshot.getNoticeTime()) > 0) { |
| | | latestSnapshot = snapshot; |
| | | } |
| | | } |
| | | List<MemberPurchaseOrder> memberOrders = memberPurchaseOrderMapper.selectList(new LambdaQueryWrapper<MemberPurchaseOrder>() |
| | | .eq(MemberPurchaseOrder::getAppUserId, userId) |
| | | .eq(MemberPurchaseOrder::getDeleted, 0) |
| | | .orderByDesc(MemberPurchaseOrder::getUpdatedAt) |
| | | .orderByDesc(MemberPurchaseOrder::getId)); |
| | | for (MemberPurchaseOrder order : memberOrders) { |
| | | LocalDateTime noticeTime = chooseTime(order.getPaidAt(), order.getUpdatedAt(), order.getCreatedAt()); |
| | | NotificationSnapshot snapshot = new NotificationSnapshot("您的会员订单状态已更新", noticeTime); |
| | | if (latestSnapshot == null || compareTime(snapshot.getNoticeTime(), latestSnapshot.getNoticeTime()) > 0) { |
| | | latestSnapshot = snapshot; |
| | | } |
| | | } |
| | | List<CoursePurchaseOrder> courseOrders = coursePurchaseOrderMapper.selectList(new LambdaQueryWrapper<CoursePurchaseOrder>() |
| | | .eq(CoursePurchaseOrder::getAppUserId, userId) |
| | | .eq(CoursePurchaseOrder::getDeleted, 0) |
| | | .orderByDesc(CoursePurchaseOrder::getUpdatedAt) |
| | | .orderByDesc(CoursePurchaseOrder::getId)); |
| | | for (CoursePurchaseOrder order : courseOrders) { |
| | | LocalDateTime noticeTime = chooseTime(order.getPaidAt(), order.getUpdatedAt(), order.getCreatedAt()); |
| | | String title = trimToEmpty(order.getProductNameSnapshot()); |
| | | String content = title.isEmpty() ? "您的课程订单状态已更新" : "您的【" + title + "】订单状态已更新"; |
| | | NotificationSnapshot snapshot = new NotificationSnapshot(content, noticeTime); |
| | | if (latestSnapshot == null || compareTime(snapshot.getNoticeTime(), latestSnapshot.getNoticeTime()) > 0) { |
| | | latestSnapshot = snapshot; |
| | | } |
| | | } |
| | | return latestSnapshot; |
| | | } |
| | | |
| | | /** |
| | | * 解析课程更新通知最新快照。 |
| | | */ |
| | | private NotificationSnapshot resolveCourseUpdateSnapshot(Long userId) { |
| | | List<Long> ownedCourseIds = resolveOwnedCourseIds(userId); |
| | | if (ownedCourseIds.isEmpty()) { |
| | | return null; |
| | | } |
| | | List<HealthCourse> courses = healthCourseMapper.selectList(new LambdaQueryWrapper<HealthCourse>() |
| | | .in(HealthCourse::getId, ownedCourseIds) |
| | | .eq(HealthCourse::getDeleted, 0) |
| | | .eq(HealthCourse::getStatus, COURSE_STATUS_ENABLED)); |
| | | if (courses == null || courses.isEmpty()) { |
| | | return null; |
| | | } |
| | | List<HealthCourseVideo> videos = healthCourseVideoMapper.selectList(new LambdaQueryWrapper<HealthCourseVideo>() |
| | | .in(HealthCourseVideo::getCourseId, ownedCourseIds) |
| | | .eq(HealthCourseVideo::getDeleted, 0) |
| | | .orderByDesc(HealthCourseVideo::getUpdatedAt) |
| | | .orderByDesc(HealthCourseVideo::getId)); |
| | | Map<Long, HealthCourse> courseMap = new HashMap<Long, HealthCourse>(); |
| | | for (HealthCourse course : courses) { |
| | | courseMap.put(course.getId(), course); |
| | | } |
| | | NotificationSnapshot latestSnapshot = null; |
| | | for (HealthCourseVideo video : videos) { |
| | | HealthCourse course = courseMap.get(video.getCourseId()); |
| | | if (course == null) { |
| | | continue; |
| | | } |
| | | LocalDateTime noticeTime = chooseTime(video.getUpdatedAt(), course.getUpdatedAt(), course.getCreatedAt()); |
| | | String title = trimToEmpty(course.getTitle()); |
| | | String content = title.isEmpty() ? "您的课程有更新" : "你的【" + title + "】有更新"; |
| | | NotificationSnapshot snapshot = new NotificationSnapshot(content, noticeTime); |
| | | if (latestSnapshot == null || compareTime(snapshot.getNoticeTime(), latestSnapshot.getNoticeTime()) > 0) { |
| | | latestSnapshot = snapshot; |
| | | } |
| | | } |
| | | return latestSnapshot; |
| | | } |
| | | |
| | | /** |
| | | * 将纯文本内容包装为段落,便于 APP 统一使用 rich-text 渲染。 |
| | | */ |
| | | private String wrapParagraph(String content) { |
| | | String safeContent = trimToEmpty(content); |
| | | if (safeContent.isEmpty()) { |
| | | return "<p>暂无通知内容</p>"; |
| | | } |
| | | return "<p>" + safeContent + "</p>"; |
| | | } |
| | | |
| | | /** |
| | | * 判断通知开关是否开启。 |
| | | */ |
| | | private boolean isEnabled(Integer value) { |