我目前正在使用Spring Boot开发一个应用程序,该应用程序允许用户创建约会。因此,基本上,约会具有startDateTime和endDateTime字段以及一封电子邮件。约会的创建在MySql数据库的约会表中添加了新行。
我想做的是在数据库中定义的startDateTime之前一小时通过电子邮件通知用户。我正在寻找解决方案,但找不到。我发现作业(Spring批处理)可以做到这一点,但是作业依赖于频率检查(几天,几周,几个月),我要寻找的是实时通知。欢迎为实现此类任务的解决方案提供任何帮助或指导。
问候
您可以使用调度库(例如quartz)来提供与Spring框架的轻松集成。
将约会保存在数据库中后,将在所需的时间(例如,开始日期前一小时)安排“发送电子邮件”作业。
“发送电子邮件”作业必须实现org.quartz.Job,更具体地讲execute,您可以在其中使用Autowired SendEmailService实现的方法。
org.quartz.Job
execute
Autowired
SendEmailService
您可以在下面找到(几乎)完整的示例,以了解如何在代码中实现这种要求。
更新-计划作业的代码
首先,我们定义一个SchedulingService接口。
SchedulingService
public interface SchedulingService { startScheduler() throws SchedulerException; void standbyScheduler() throws SchedulerException; void shutdownScheduler() throws SchedulerException; void scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException; }
和相关的执行。
@Service public class SchedulingServiceImpl implements SchedulingService { @Autowired private Scheduler scheduler; @Override public void startScheduler() throws SchedulerException { if (!scheduler.isStarted()) { scheduler.start(); } } @Override public void standbyScheduler() throws SchedulerException { if (!scheduler.isInStandbyMode()) { scheduler.standby(); } } @Override public void shutdownScheduler() throws SchedulerException { if (!scheduler.isShutdown()) { scheduler.shutdown(); } } @Override public void scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException { scheduler.scheduleJob(jobDetail, trigger); } }
然后,AppointmentServiceImpl我们有一个createAppointment()调用的方法scheduleSendEmailJob()。
AppointmentServiceImpl
createAppointment()
scheduleSendEmailJob()
@Service public class AppointmentServiceImpl implements AppointmentService { @Autowired private SchedulingService schedulingService; public void createAppointment(Appointment appointment) throws SchedulerException { // Save appointment to database // ... // Schedule send email job if appointment has been successfully saved scheduleSendEmailJob(appointment); return; } private void scheduleSendEmailJob(Appointment appointment) throws SchedulerException { JobDetail jobDetail = JobBuilder.newJob().ofType(SendEmailJob.class) .storeDurably() .withIdentity(UuidUtils.generateId(), "APPOINTMENT_NOTIFICATIONS") .withDescription("Send email notification for appointment") .build(); jobDetail.getJobDataMap().put("appointmentId", appointment.getId()); Date scheduleDate = appointment.computeDesiredScheduleDate(); String cronExpression = convertDateToCronExpression(scheduleDate); CronTrigger trigger = TriggerBuilder.newTrigger().forJob(jobDetail) .withIdentity(UuidUtils.generateId(), "APPOINTMENT_NOTIFICATIONS") .withDescription("Trigger description") .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)) .build(); schedulingService.scheduleJob(jobDetail, trigger); } private String convertDateToCronExpression(Date date) { Calendar calendar = new GregorianCalendar(); if (date == null) return null; calendar.setTime(date); int year = calendar.get(java.util.Calendar.YEAR); int month = calendar.get(java.util.Calendar.MONTH) + 1; int day = calendar.get(java.util.Calendar.DAY_OF_MONTH); int hour = calendar.get(java.util.Calendar.HOUR_OF_DAY); int minute = calendar.get(java.util.Calendar.MINUTE); return String.format("0 %d %d %d %d ? %d", minute, hour, day, month, year); } }
类SendEmailJob是Job接口的实现,负责使用相关服务发送电子邮件。
SendEmailJob
Job
更新-将参数从调度方法传递到实际作业执行的代码
为了传递参数,正在使用jobDataMap。例如:
public class SendEmailJob implements Job { @Autowired private AppointmentService appointmentService; @Autowired private SendEmailService sendEmailService; @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap(); // Retrieving passed parameters Long appointmentId = (Long) jobDataMap.get("appointmentId"); Appointment appointment = appointmentService.findById(appointmentId); // Send email sendEmailService.sendEmail(appointment); } }
注意: 约会对象也可以从调度方法传递到实际的作业执行,您可以传递:
jobDetail.getJobDataMap().put("appointment", appointment);
得到:
// Retrieving passed parameters Appointment appointment = (Appointment) jobDataMap.get("appointment");
更新-配置代码
Bean scheduler是在@Configuration负责Quartz初始化的类中定义的。
scheduler
@Configuration
SchedulingConfiguration 类定义为:
SchedulingConfiguration
@Configuration public class SchedulingConfiguration { @Autowired private ApplicationContext applicationContext; @Bean public Scheduler scheduler() throws SchedulerException, IOException { StdSchedulerFactory factory = new StdSchedulerFactory(); factory.initialize(new ClassPathResource("properties/quartz.properties").getInputStream()); Scheduler scheduler = factory.getScheduler(); scheduler.setJobFactory(springBeanJobFactory()); return scheduler; } @Bean public SpringBeanJobFactory springBeanJobFactory() { AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); jobFactory.setApplicationContext(applicationContext); return jobFactory; } }
我们的quartz.properties文件位于resources/properties文件夹中。请注意,作业持久性数据库是Oracle实例。
quartz.properties
resources/properties
# Configure Main Scheduler Properties org.quartz.scheduler.instanceName = AppScheduler org.quartz.scheduler.instanceId = AUTO # Configure ThreadPool org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 10 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true # Configure JobStore org.quartz.jobStore.misfireThreshold = 60000 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate org.quartz.jobStore.tablePrefix = APP.QRTZ_ org.quartz.jobStore.useProperties = false org.quartz.jobStore.dataSource = appDs org.quartz.jobStore.isClustered = true org.quartz.jobStore.clusterCheckinInterval = 20000 # Configure Datasources org.quartz.dataSource.appDs.driver = oracle.jdbc.driver.OracleDriver org.quartz.dataSource.appDs.URL = jdbc:oracle:thin:@dbsrv:1521:appdb org.quartz.dataSource.appDs.user = db_user org.quartz.dataSource.appDs.password = db_pwd org.quartz.dataSource.appDs.maxConnections = 5 org.quartz.dataSource.appDs.validationQuery = select 0 from dual
最后一步是在应用程序上下文初始化中调用调度程序方法,如下所示(请注意在中添加的方法SchedulingService):
public class SchedulingContextListener implements ServletContextListener { private static final Logger logger = LogManager.getLogger(SchedulingContextListener.class); private SchedulingService schedulingService(ServletContextEvent sce) { WebApplicationContext springContext = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()); return springContext.getBean(SchedulingService.class); } @Override public void contextInitialized(ServletContextEvent sce) { try { this.schedulingService(sce).startScheduler(); } catch (SchedulerException e) { logger.error("Error while Scheduler is being started", e); } } @Override public void contextDestroyed(ServletContextEvent sce) { try { this.schedulingService(sce).shutdownScheduler(); } catch (SchedulerException e) { logger.error("Error while Scheduler is being shutdown", e); } } }
注意: SchedulingContextListener应该servletContext在应用程序初始化中注册,这取决于使用Spring Boot或传统的Spring MVC Configuration定义Spring配置的方式。
SchedulingContextListener
servletContext
希望能有所帮助。