You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Spring Batch、Quartz/Scheduler实现预订状态更新后定时邮件通知?求示例

解决方案思路

你的场景核心是延迟任务处理:当预订状态更新为特定值时,需在更新时间+1小时后触发邮件通知。Quartz天生适配这类需要精确延迟、持久化任务(防止服务重启丢失)的场景;如果允许批量扫描处理,Spring Scheduler + Spring Batch的组合实现更简单。以下是两种方案的完整示例:


方案一:Spring + Quartz 实现单条预订精确延迟通知

1. 核心依赖(Maven)

<dependencies>
    <!-- Spring 上下文支持 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>5.3.20</version>
    </dependency>
    <!-- Quartz 调度框架 -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.3.2</version>
    </dependency>
    <!-- Spring 邮件 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
</dependencies>

2. 定义Quartz任务类

@Component
public class BookingNotificationJob implements Job {

    @Autowired
    private JavaMailSender mailSender;
    @Autowired
    private BookingRepository bookingRepo;
    @Autowired
    private CustomerRepository customerRepo;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 从任务参数中获取预订ID
        Long bookingId = (Long) context.getJobDetail().getJobDataMap().get("bookingId");
        
        // 查询关联数据
        Booking booking = bookingRepo.findById(bookingId).orElseThrow();
        Customer customer = customerRepo.findById(booking.getCustomerId()).orElseThrow();
        
        // 发送通知邮件
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(customer.getEmail());
        message.setSubject("预订状态更新通知");
        message.setText(String.format("尊敬的%s:您的预订(编号%s)已完成状态更新,请留意后续进展。",
                customer.getName(), booking.getBookingNo()));
        mailSender.send(message);
    }
}

3. 任务调度服务(触发延迟任务)

在预订状态更新时调用此服务,添加1小时后的执行任务:

@Service
public class QuartzJobScheduler {

    @Autowired
    private Scheduler scheduler;

    public void scheduleNotification(Long bookingId, Date triggerTime) throws SchedulerException {
        // 定义任务详情
        JobDetail jobDetail = JobBuilder.newJob(BookingNotificationJob.class)
                .withIdentity("booking-notify-" + bookingId)
                .usingJobData("bookingId", bookingId)
                .storeDurably()
                .build();
        
        // 定义延迟触发规则
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("booking-trigger-" + bookingId)
                .startAt(triggerTime)
                // 处理任务错过触发时间的情况:立即执行
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withMisfireHandlingInstructionFireNow())
                .build();
        
        // 提交任务到调度器
        scheduler.scheduleJob(jobDetail, trigger);
    }
}

4. 状态更新时触发任务

在业务服务中集成调度逻辑:

@Service
public class BookingService {

    @Autowired
    private BookingRepository bookingRepo;
    @Autowired
    private QuartzJobScheduler jobScheduler;

    public void updateBookingStatus(Long bookingId, String targetStatus) throws SchedulerException {
        Booking booking = bookingRepo.findById(bookingId).orElseThrow();
        booking.setStatus(targetStatus);
        Date updateTime = new Date();
        booking.setUpdateTime(updateTime);
        bookingRepo.save(booking);
        
        // 仅当状态为目标值时,触发1小时后的通知任务
        if ("CONFIRMED".equals(targetStatus)) {
            Date triggerTime = new Date(updateTime.getTime() + 3600 * 1000);
            jobScheduler.scheduleNotification(bookingId, triggerTime);
        }
    }
}

方案二:Spring Scheduler + Spring Batch 批量处理待通知任务

如果不需要单条记录的精确延迟,允许定期扫描批量处理,可采用此方案:

1. Batch任务配置

@Configuration
@EnableBatchProcessing
public class BookingNotificationBatchConfig {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    @Autowired
    private BookingRepository bookingRepo;
    @Autowired
    private JavaMailSender mailSender;
    @Autowired
    private CustomerRepository customerRepo;

    // 读取器:查询已到通知时间、未发送通知的预订
    @Bean
    public ItemReader<Booking> bookingReader() {
        return new RepositoryItemReaderBuilder<Booking>()
                .repository(bookingRepo)
                .methodName("findByStatusAndUpdateTimeBeforeAndNotificationSentFalse")
                .arguments("CONFIRMED", new Date(System.currentTimeMillis() - 3600 * 1000))
                .name("bookingReader")
                .build();
    }

    // 处理器:发送邮件并标记通知状态
    @Bean
    public ItemProcessor<Booking, Booking> bookingProcessor() {
        return booking -> {
            Customer customer = customerRepo.findById(booking.getCustomerId()).orElseThrow();
            SimpleMailMessage message = new SimpleMailMessage();
            message.setTo(customer.getEmail());
            message.setSubject("预订状态更新通知");
            message.setText(String.format("尊敬的%s:您的预订(编号%s)已完成状态更新,请留意后续进展。",
                    customer.getName(), booking.getBookingNo()));
            mailSender.send(message);
            booking.setNotificationSent(true);
            return booking;
        };
    }

    // 写入器:更新预订的通知状态
    @Bean
    public ItemWriter<Booking> bookingWriter() {
        return bookings -> bookingRepo.saveAll(bookings);
    }

    // 步骤定义
    @Bean
    public Step notificationStep() {
        return stepBuilderFactory.get("notificationStep")
                .<Booking, Booking>chunk(10)
                .reader(bookingReader())
                .processor(bookingProcessor())
                .writer(bookingWriter())
                .build();
    }

    // Job定义
    @Bean
    public Job notificationJob() {
        return jobBuilderFactory.get("notificationJob")
                .start(notificationStep())
                .build();
    }
}

2. Scheduler定期触发Batch任务

@Component
public class BatchScheduler {

    @Autowired
    private JobLauncher jobLauncher;
    @Autowired
    private Job notificationJob;

    // 每5分钟扫描一次待通知的预订
    @Scheduled(cron = "0 */5 * * * ?")
    public void runNotificationBatch() throws Exception {
        JobParameters params = new JobParametersBuilder()
                .addLong("timestamp", System.currentTimeMillis())
                .toJobParameters();
        jobLauncher.run(notificationJob, params);
    }
}

场景选择建议
  • 若需要单条预订的精确延迟触发(每条任务的延迟时间独立),优先选Quartz方案,它支持任务持久化,服务重启后不会丢失未执行的任务。
  • 若允许批量扫描处理(比如每5分钟处理一次所有到点的预订),选Spring Scheduler + Batch方案,实现更简单,运维成本更低。

内容的提问来源于stack exchange,提问作者Jason

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 14:45:45