liujie
2026-08-21 77649c76ec1c3afcb93e9963243d637665b3a307
backend/src/main/java/com/tourguide/modules/system/service/GuideNotificationService.java
@@ -2,15 +2,20 @@
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;
@@ -19,28 +24,50 @@
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) {
    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<SystemNotice> notices = publishedNotices();
        if (notices.isEmpty()) return 0L;
        Set<Long> readIds = readNoticeIds(guideId, notices);
        return notices.stream().filter(notice -> !readIds.contains(notice.getId())).count();
        long systemUnread = notices.stream().filter(notice -> !readIds.contains(notice.getId())).count();
        Long personalUnread = guideNotificationMapper.selectCount(new LambdaQueryWrapper<GuideNotification>()
                .eq(GuideNotification::getGuideId, guideId)
                .isNull(GuideNotification::getReadAt));
        return systemUnread + personalUnread;
    }
    public List<GuideNotificationResponse> list(Long guideId) {
        List<SystemNotice> notices = publishedNotices();
        Set<Long> readIds = readNoticeIds(guideId, notices);
        return notices.stream()
        List<GuideNotificationResponse> responses = notices.stream()
                .map(notice -> new GuideNotificationResponse(notice, readIds.contains(notice.getId())))
                .collect(Collectors.toList());
        responses.addAll(guideNotificationMapper.selectList(new LambdaQueryWrapper<GuideNotification>()
                        .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, Long noticeId) {
    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<SystemNotice>()
                .eq(SystemNotice::getId, noticeId)
                .eq(SystemNotice::getStatus, 1)
@@ -57,6 +84,95 @@
        readMapper.insert(read);
    }
    public void notifyOrderAssigned(TourOrder order) {
        if (order == null || order.getGuideId() == null) return;
        String title = "您有新的讲解订单";
        String content = "景区:" + valueOrUnknown(order.getScenicName())
                + ";汇合点:" + valueOrUnknown(order.getMeetingPointName())
                + ";服务类型:" + valueOrUnknown(order.getPackageName(), order.getOrderType())
                + ";人数:" + (order.getPeopleCount() == null ? 0 : order.getPeopleCount())
                + "。请及时接单。";
        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;
        create(order.getGuideId(), "ORDER_CANCELLED", "取消提醒", "您有一个被取消的讲解订单,请查看",
                order.getId(), orderTargetUrl(order.getId()));
    }
    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<GuideNotification>()
                .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<SystemNotice> publishedNotices() {
        return noticeMapper.selectList(new LambdaQueryWrapper<SystemNotice>()
                .eq(SystemNotice::getStatus, 1)