package com.tourguide.modules.system.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.tourguide.common.exception.BusinessException; import com.tourguide.modules.order.entity.TourOrder; import com.tourguide.modules.system.dto.GuideNotificationResponse; import com.tourguide.modules.system.entity.GuideNoticeRead; import com.tourguide.modules.system.entity.GuideNotification; import com.tourguide.modules.system.entity.SystemNotice; import com.tourguide.modules.system.mapper.GuideNotificationMapper; import com.tourguide.modules.system.mapper.GuideNoticeReadMapper; import com.tourguide.modules.system.mapper.SystemNoticeMapper; import com.tourguide.modules.user.service.GuidePushDeviceService; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @Service public class GuideNotificationService { private final SystemNoticeMapper noticeMapper; private final GuideNoticeReadMapper readMapper; private final GuideNotificationMapper guideNotificationMapper; private final GuidePushDeviceService pushDeviceService; public GuideNotificationService(SystemNoticeMapper noticeMapper, GuideNoticeReadMapper readMapper, GuideNotificationMapper guideNotificationMapper, GuidePushDeviceService pushDeviceService) { this.noticeMapper = noticeMapper; this.readMapper = readMapper; this.guideNotificationMapper = guideNotificationMapper; this.pushDeviceService = pushDeviceService; } public long unreadCount(Long guideId) { List notices = publishedNotices(); Set readIds = readNoticeIds(guideId, notices); long systemUnread = notices.stream().filter(notice -> !readIds.contains(notice.getId())).count(); Long personalUnread = guideNotificationMapper.selectCount(new LambdaQueryWrapper() .eq(GuideNotification::getGuideId, guideId) .isNull(GuideNotification::getReadAt)); return systemUnread + personalUnread; } public List list(Long guideId) { List notices = publishedNotices(); Set readIds = readNoticeIds(guideId, notices); List responses = notices.stream() .map(notice -> new GuideNotificationResponse(notice, readIds.contains(notice.getId()))) .collect(Collectors.toList()); responses.addAll(guideNotificationMapper.selectList(new LambdaQueryWrapper() .eq(GuideNotification::getGuideId, guideId) .orderByDesc(GuideNotification::getCreatedAt)) .stream().map(GuideNotificationResponse::new).collect(Collectors.toList())); responses.sort(Comparator.comparing(GuideNotificationResponse::getTime, Comparator.nullsLast(Comparator.reverseOrder()))); return responses; } public void markRead(Long guideId, String notificationId) { if (notificationId != null && notificationId.startsWith("guide:")) { markPersonalRead(guideId, parseId(notificationId, "guide:")); return; } Long noticeId = parseId(notificationId, "system:"); SystemNotice notice = noticeMapper.selectOne(new LambdaQueryWrapper() .eq(SystemNotice::getId, noticeId) .eq(SystemNotice::getStatus, 1) .last("LIMIT 1")); if (notice == null) throw new BusinessException(404, "Notification not found"); Long count = readMapper.selectCount(new LambdaQueryWrapper() .eq(GuideNoticeRead::getGuideId, guideId) .eq(GuideNoticeRead::getNoticeId, noticeId)); if (count > 0) return; GuideNoticeRead read = new GuideNoticeRead(); read.setGuideId(guideId); read.setNoticeId(noticeId); read.setReadAt(LocalDateTime.now()); readMapper.insert(read); } public void notifyOrderAssigned(TourOrder order) { if (order == null || order.getGuideId() == null) return; String title = "接单提醒"; String content = "您有一个新的讲解订单,请查看"; String targetUrl = orderTargetUrl(order.getId()); create(order.getGuideId(), "ORDER_ASSIGNED", title, content, order.getId(), targetUrl); pushDeviceService.push(order.getGuideId(), title, content, targetUrl); } public void notifyVisitorCancelledOrder(TourOrder order) { if (order == null || order.getGuideId() == null) return; String title = "取消提醒"; String content = "您有一个讲解订单被取消,请查看"; String targetUrl = orderTargetUrl(order.getId()); create(order.getGuideId(), "ORDER_CANCELLED", title, content, order.getId(), targetUrl); pushDeviceService.push(order.getGuideId(), title, content, targetUrl); } public void notifyOrderCancelled(TourOrder order) { if (order == null || order.getGuideId() == null) return; String title = "取消提醒"; String content = "您有一个讲解订单被取消,请查看"; String targetUrl = orderTargetUrl(order.getId()); create(order.getGuideId(), "ORDER_CANCELLED", title, content, order.getId(), targetUrl); pushDeviceService.push(order.getGuideId(), title, content, targetUrl); } public void notifyOrderReviewed(TourOrder order) { if (order == null || order.getGuideId() == null) return; String title = "评价提醒"; String content = "您有一条新的订单评价,请查看"; String targetUrl = orderTargetUrl(order.getId()); create(order.getGuideId(), "ORDER_REVIEWED", title, content, order.getId(), targetUrl); pushDeviceService.push(order.getGuideId(), title, content, targetUrl); } public boolean pushServiceReminder(TourOrder order) { if (order == null || order.getGuideId() == null) return false; String title = "请准备前往汇合点"; String content = "您的讲解服务即将开始,请提前到达 " + valueOrUnknown(order.getMeetingPointName()) + "。"; return pushDeviceService.push(order.getGuideId(), title, content, orderTargetUrl(order.getId())); } public void notifyAuditResult(Long guideId, boolean approved) { create(guideId, approved ? "AUDIT_APPROVED" : "AUDIT_REJECTED", "系统提醒", approved ? "您的资料审核已通过" : "您的资料审核未通过", null, null); } private void create(Long guideId, String type, String title, String content, Long orderId, String targetUrl) { if (guideId == null) return; GuideNotification notification = new GuideNotification(); notification.setGuideId(guideId); notification.setNotificationType(type); notification.setTitle(title); notification.setContent(content); notification.setOrderId(orderId); notification.setTargetUrl(targetUrl); notification.setCreatedAt(LocalDateTime.now()); guideNotificationMapper.insert(notification); } private String orderTargetUrl(Long orderId) { return orderId == null ? null : "/pages/order-detail/index?id=" + orderId; } private String valueOrUnknown(String primary, String fallback) { if (primary != null && !primary.trim().isEmpty()) return primary; return valueOrUnknown(fallback); } private String valueOrUnknown(String value) { return value == null || value.trim().isEmpty() ? "未知" : value; } private void markPersonalRead(Long guideId, Long notificationId) { GuideNotification notification = guideNotificationMapper.selectOne(new LambdaQueryWrapper() .eq(GuideNotification::getId, notificationId) .eq(GuideNotification::getGuideId, guideId) .last("LIMIT 1")); if (notification == null) throw new BusinessException(404, "Notification not found"); if (notification.getReadAt() != null) return; notification.setReadAt(LocalDateTime.now()); guideNotificationMapper.updateById(notification); } private Long parseId(String value, String prefix) { String raw = value == null ? "" : value; if (raw.startsWith(prefix)) raw = raw.substring(prefix.length()); try { return Long.valueOf(raw); } catch (NumberFormatException exception) { throw new BusinessException(400, "Notification id is invalid"); } } private List publishedNotices() { return noticeMapper.selectList(new LambdaQueryWrapper() .eq(SystemNotice::getStatus, 1) .isNotNull(SystemNotice::getPublishTime) .le(SystemNotice::getPublishTime, LocalDateTime.now()) .orderByDesc(SystemNotice::getPublishTime)); } private Set readNoticeIds(Long guideId, List notices) { if (notices.isEmpty()) return Collections.emptySet(); List noticeIds = notices.stream().map(SystemNotice::getId).collect(Collectors.toList()); return readMapper.selectList(new LambdaQueryWrapper() .eq(GuideNoticeRead::getGuideId, guideId) .in(GuideNoticeRead::getNoticeId, noticeIds)) .stream().map(GuideNoticeRead::getNoticeId).collect(Collectors.toSet()); } }