如何用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
相关产品推荐
相关产品推荐

