| | |
| | | |
| | | 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; |
| | |
| | | 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) |
| | |
| | | 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) |