Spring 与 Quartz 的完整集成以实现实时通知电子邮件 [英] Full Spring integration with Quartz to implement real time notification e-mails

查看:31
本文介绍了Spring 与 Quartz 的完整集成以实现实时通知电子邮件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在使用 Spring Boot 开发一个应用程序,让用户可以创建约会.所以基本上约会有一个 startDateTime 和一个 endDateTime 字段 + 一个电子邮件.约会的创建在MySql数据库的约会表中新增一行.

I'm currently developing an application with spring boot that let users to create an appointment. So basically the appointment has a startDateTime and an endDateTime fields + an e-mail. The creation of an appointment adds a new line in the appointment table of a MySql database.

我想要做的是在数据库中定义的 startDateTime 前一小时通过电子邮件通知用户.我寻找了一种解决方案,但找不到.我发现工作(春季批次)可以做到这一点,但工作依赖于频率检查(天、周、月),我正在寻找的是实时通知.欢迎任何有关实现此类任务的解决方案的帮助或指导.

What I want to do is to notify the user one hour before the startDateTime defined in the database with an e-mail. I looked for a solution but couldn't find one. I found that jobs (spring batch) can do this, but jobs rely on a frequency check (days, weeks, months) what I'm looking for is a real-time notification. Any help or guidance for a solution to realise such task is welcomed.

问候

推荐答案

您可以使用调度库,例如 石英,提供与 Spring 框架的轻松集成.

You can use a scheduling library such as quartz, providing easy integration with Spring framework.

在您的数据库中保存约会后,将在所需时间(例如开始日期前一小时)安排发送电子邮件"作业.

After an appointment is saved in your database, a "send-email" job will be scheduled for the desirable time (one hour before start date for instance).

发送电子邮件"作业必须实现 org.quartz.Job,更具体地说是 execute 方法,您可以在其中使用 Autowired SendEmailService 实现.

A "send-email" job must implement org.quartz.Job and more specifically execute method where you can use your Autowired SendEmailService implementation.

您可以在下面找到一个(几乎)完整的示例,说明如何在代码中实现此类要求.

Below you can find a (almost) complete example of how such a requirement could be implemented in code.

更新 - 安排作业的代码

首先我们定义一个SchedulingService接口.

First we define a SchedulingService interface.

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().

Then in AppointmentServiceImpl we have a method createAppointment() which calls 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);
    }
}

Class SendEmailJobJob 接口的实现,负责使用相关服务发送邮件.

Class SendEmailJob is an implementation of Job interface and responsible for sending emails using relevant services.

更新 - 将参数从调度方法传递到实际作业执行的代码

为了传递参数,正在使用 jobDataMap.例如:

For passing parameters, jobDataMap is being used. For instance:

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

注意:Appointment 对象也可以从调度方法传递到实际作业执行中,你可以直接传递:

Note: Appointment object could also been passed from scheduling method to actual job execution, you can just pass:

jobDetail.getJobDataMap().put("appointment", appointment);

并得到:

// Retrieving passed parameters
Appointment appointment = (Appointment) jobDataMap.get("appointment");

更新 - 配置代码

Bean scheduler 在负责 Quartz 初始化的 @Configuration 类中定义.

Bean scheduler is defined in a @Configuration class responsible for Quartz initialization.

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实例.

Our quartz.properties file lives in resources/properties folder. Note that job persistence database is an Oracle instance.

# 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中添加的方法):

The final step is to call scheduler methods in application context initialization as following (please note added methods in 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 配置是如何定义的,使用 Spring Boot 或传统Spring MVC 配置.

Note: SchedulingContextListener should be registered in servletContext in application initialization, depending on how Spring configuration is defined, either using Spring Boot or traditional Spring MVC Configuration.

希望有所帮助.

这篇关于Spring 与 Quartz 的完整集成以实现实时通知电子邮件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆