package com.tourguide.modules.order.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.tourguide.modules.operation.entity.OperationScenicSpot; import com.tourguide.modules.operation.mapper.OperationScenicSpotMapper; import com.tourguide.modules.order.domain.OrderStatus; import com.tourguide.modules.order.entity.TourOrder; import com.tourguide.modules.order.mapper.TourOrderMapper; import com.tourguide.modules.user.entity.GuideReceivingSession; import com.tourguide.modules.user.mapper.GuideRealtimeStatusMapper; import com.tourguide.modules.user.mapper.GuideReceivingSessionMapper; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.List; @Service public class OrderLifecycleScheduler { private static final int BATCH_SIZE = 100; private final TourOrderMapper orderMapper; private final OperationScenicSpotMapper scenicMapper; private final GuideOrderMatchingService matchingService; private final GuideReceivingSessionMapper sessionMapper; private final GuideSessionTransitionService sessionTransitionService; public OrderLifecycleScheduler(TourOrderMapper orderMapper, OperationScenicSpotMapper scenicMapper, GuideOrderMatchingService matchingService, GuideReceivingSessionMapper sessionMapper, GuideSessionTransitionService sessionTransitionService) { this.orderMapper = orderMapper; this.scenicMapper = scenicMapper; this.matchingService = matchingService; this.sessionMapper = sessionMapper; this.sessionTransitionService = sessionTransitionService; } @Scheduled(fixedDelayString = "${tourguide.order.lifecycle-scan-delay-millis:10000}") public void process() { cancelExpiredUnpaidOrders(); retryPendingOrders(); startDueGroupSessions(); } void cancelExpiredUnpaidOrders() { List orders = orderMapper.selectList(new LambdaQueryWrapper() .eq(TourOrder::getStatus, OrderStatus.PENDING_PAYMENT.getCode()) .isNotNull(TourOrder::getScenicSpotId).orderByAsc(TourOrder::getPlacedAt).last("LIMIT " + BATCH_SIZE)); LocalDateTime now = LocalDateTime.now(); for (TourOrder order : orders) { OperationScenicSpot scenic = scenicMapper.selectById(order.getScenicSpotId()); int minutes = scenic == null || scenic.getUnpaidCancelMinutes() == null || scenic.getUnpaidCancelMinutes() < 1 ? 15 : scenic.getUnpaidCancelMinutes(); LocalDateTime baseTime = order.getPlacedAt() == null ? order.getCreatedAt() : order.getPlacedAt(); if (baseTime != null && !baseTime.plusMinutes(minutes).isAfter(now)) orderMapper.cancelUnpaid(order.getId()); } } void retryPendingOrders() { List orders = orderMapper.selectList(new LambdaQueryWrapper() .eq(TourOrder::getStatus, OrderStatus.PENDING_ACCEPT.getCode()) .isNotNull(TourOrder::getPaymentTime).orderByAsc(TourOrder::getPaymentTime).last("LIMIT " + BATCH_SIZE)); for (TourOrder order : orders) matchingService.match(order.getId()); } void startDueGroupSessions() { List sessions = sessionMapper.selectList(new LambdaQueryWrapper() .eq(GuideReceivingSession::getStatus, "RECEIVING") .isNotNull(GuideReceivingSession::getServiceStartAt) .le(GuideReceivingSession::getServiceStartAt, LocalDateTime.now()) .orderByAsc(GuideReceivingSession::getServiceStartAt).last("LIMIT " + BATCH_SIZE)); for (GuideReceivingSession session : sessions) { sessionTransitionService.startIfDue(session); } } }