Spring Boot Quartz Scheduler 示例:构建电子邮件调度应用程序

x33g5p2x  于2021-10-17 转载在 Java  
字(9.5k)|赞(0)|评价(0)|浏览(316)

Quartz 是一个用于调度作业的开源 Java 库。它具有非常丰富的功能集,包括但不限于持久作业、事务和集群。

您可以安排作业在一天中的某个时间执行,或者在某个时间间隔定期执行等等。 Quartz 提供了一个流畅的 API 来创建作业和调度它们。

Quartz 作业可以持久化到数据库、缓存或内存中。这与不支持持久作业的 Spring 内置任务调度 API 形成对比。
在本文中,您将通过构建一个简单的 电子邮件调度应用程序 来学习如何使用 Quartz Scheduler 在 Spring Boot 中调度作业。该应用程序将有一个 Rest API,允许客户端稍后安排电子邮件。

我们将使用 MySQL 来保存所有作业和其他与作业相关的数据。

听起来不错?开始吧…

创建应用程序

让我们使用 Spring Boot CLI 引导应用程序。打开终端并输入以下命令 -

spring init -d=web,jpa,mysql,quartz,validation,mail -n=quartz-demo quartz-demo

上述命令将在名为 quartz-demo 的文件夹中生成具有所有指定依赖项的项目。

请注意,您还可以按照以下说明使用 Spring Initializr Web 工具来引导项目 -

  • 打开 http://start.spring.io
  • Artifact 字段中输入 quartz-demo
  • Package 字段中输入 com.example.quartzdemo
  • Dependencies 部分添加 WebJPAMySQLQuartzValidationMail .
  • 点击生成生成并下载项目。

就是这样!您现在可以将项目导入您喜欢的 IDE 并开始工作。

目录结构

以下是完整应用的目录结构,供大家参考。我们将在本文中一一创建所有必需的文件夹和类 -

配置 MySQL 数据库、Quartz 调度器和邮件发送器

让我们配置 Quartz Scheduler、MySQL 数据库和 Spring Mail。 MySQL 数据库将用于存储 Quartz Jobs,Spring Mail 将用于发送电子邮件。

打开 src/main/resources/application.properties 文件并添加以下属性 -

## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url = jdbc:mysql://localhost:3306/quartz_demo?useSSL=false
spring.datasource.username = root
spring.datasource.password = callicoder

## QuartzProperties
spring.quartz.job-store-type = jdbc
spring.quartz.properties.org.quartz.threadPool.threadCount = 5

## MailProperties
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=rajeevc217@gmail.com
spring.mail.password=

spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true

您需要创建一个名为 quartz_demo 的 MySQL 数据库。另外,不要忘记根据您的 MySQL 安装更改 spring.datasource.usernamespring.datasource.password 属性。

我们将使用 Gmail 的 SMTP 服务器发送电子邮件。请在 spring.mail.password 属性中添加您的密码。您还可以在运行时将此属性作为命令行参数传递或将其设置在环境变量中。

请注意,默认情况下禁用 Gmail 的 SMTP 访问。要允许此应用使用您的 Gmail 帐户发送电子邮件 -

  • 前往 https://myaccount.google.com/security?pli=1/#connectedapps
  • 将“允许安全性较低的应用程序”设置为是

所有石英特定的属性都以 spring.quartz 为前缀。您可以在其official documentation中参考 Quartz 支持的完整配置集。要直接设置 Quartz 调度器的配置,可以使用格式 spring.quartz.properties.<quartz_configuration_name>=<value>

创建表

由于我们已经将 Quartz 配置为在数据库中存储作业,我们需要创建 Quartz 用来存储作业和其他与作业相关的元数据的表。

请下载以下 SQL 脚本并在您的 MySQL 数据库中运行它以创建所有 Quartz 特定表。

下载完上面的SQL脚本后,登录MySQL,像这样运行脚本——

mysql> source <PATH_TO_QUARTZ_TABLES.sql>

Quartz Scheduler 的 API 和术语概述

1. 调度器

用于调度、取消调度、添加和删除作业的主要 API。

2. 作业

由代表 Quartz 中“作业”的类实现的接口。它有一个名为 execute() 的方法,您可以在其中编写作业需要执行的工作。

3. 作业详情

JobDetail 表示 Job 的一个实例。它还包含 JobDataMap 形式的附加数据,该数据在执行时传递给 Job。

每个 JobDetail 都由一个 JobKey 标识,它由一个 name 和一个 group 组成。该名称在组内必须是唯一的。

4. 触发器

顾名思义,触发器定义了执行给定作业的时间表。一个 Job 可以有多个 Trigger,但一个 Trigger 只能关联一个 Job。

每个触发器都由一个 TriggerKey 标识,它由一个 name 和一个 group 组成。该名称在组内必须是唯一的。

就像 JobDetails 一样,触发器也可以向 Job 发送参数/数据。

5. JobBuilder

JobBuilder 是一个流畅的构建器风格的 API,用于构建 JobDetail 实例。

6. 触发器构建器

TriggerBuilder 用于实例化触发器。

创建 REST API 以在 Quartz 中动态调度电子邮件作业

好的!现在让我们创建一个 REST API 来动态安排 Quartz 中的电子邮件作业。所有的 Jobs 都会被持久化到数据库中,并按照指定的时间表执行。

在编写 API 之前,让我们创建将用作 scheduleEmail API 的请求和响应负载的 DTO 类 -

ScheduleEmailRequest
package com.example.quartzdemo.payload;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.time.ZoneId;

public class ScheduleEmailRequest {
    @Email
    @NotEmpty
    private String email;

    @NotEmpty
    private String subject;

    @NotEmpty
    private String body;

    @NotNull
    private LocalDateTime dateTime;

    @NotNull
    private ZoneId timeZone;
	
	// Getters and Setters (Omitted for brevity)
}
ScheduleEmailResponse
package com.example.quartzdemo.payload;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class ScheduleEmailResponse {
    private boolean success;
    private String jobId;
    private String jobGroup;
    private String message;

    public ScheduleEmailResponse(boolean success, String message) {
        this.success = success;
        this.message = message;
    }

    public ScheduleEmailResponse(boolean success, String jobId, String jobGroup, String message) {
        this.success = success;
        this.jobId = jobId;
        this.jobGroup = jobGroup;
        this.message = message;
    }

    // Getters and Setters (Omitted for brevity)
}

ScheduleEmail Rest API

以下控制器定义了 /scheduleEmail REST API,用于在 Quartz 中安排电子邮件作业 -

package com.example.quartzdemo.controller;

import com.example.quartzdemo.job.EmailJob;
import com.example.quartzdemo.payload.ScheduleEmailRequest;
import com.example.quartzdemo.payload.ScheduleEmailResponse;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.UUID;

@RestController
public class EmailJobSchedulerController {
    private static final Logger logger = LoggerFactory.getLogger(EmailJobSchedulerController.class);

    @Autowired
    private Scheduler scheduler;

    @PostMapping("/scheduleEmail")
    public ResponseEntity<ScheduleEmailResponse> scheduleEmail(@Valid @RequestBody ScheduleEmailRequest scheduleEmailRequest) {
        try {
            ZonedDateTime dateTime = ZonedDateTime.of(scheduleEmailRequest.getDateTime(), scheduleEmailRequest.getTimeZone());
            if(dateTime.isBefore(ZonedDateTime.now())) {
                ScheduleEmailResponse scheduleEmailResponse = new ScheduleEmailResponse(false,
                        "dateTime must be after current time");
                return ResponseEntity.badRequest().body(scheduleEmailResponse);
            }

            JobDetail jobDetail = buildJobDetail(scheduleEmailRequest);
            Trigger trigger = buildJobTrigger(jobDetail, dateTime);
            scheduler.scheduleJob(jobDetail, trigger);

            ScheduleEmailResponse scheduleEmailResponse = new ScheduleEmailResponse(true,
                    jobDetail.getKey().getName(), jobDetail.getKey().getGroup(), "Email Scheduled Successfully!");
            return ResponseEntity.ok(scheduleEmailResponse);
        } catch (SchedulerException ex) {
            logger.error("Error scheduling email", ex);

            ScheduleEmailResponse scheduleEmailResponse = new ScheduleEmailResponse(false,
                    "Error scheduling email. Please try later!");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(scheduleEmailResponse);
        }
    }

    private JobDetail buildJobDetail(ScheduleEmailRequest scheduleEmailRequest) {
        JobDataMap jobDataMap = new JobDataMap();

        jobDataMap.put("email", scheduleEmailRequest.getEmail());
        jobDataMap.put("subject", scheduleEmailRequest.getSubject());
        jobDataMap.put("body", scheduleEmailRequest.getBody());

        return JobBuilder.newJob(EmailJob.class)
                .withIdentity(UUID.randomUUID().toString(), "email-jobs")
                .withDescription("Send Email Job")
                .usingJobData(jobDataMap)
                .storeDurably()
                .build();
    }

    private Trigger buildJobTrigger(JobDetail jobDetail, ZonedDateTime startAt) {
        return TriggerBuilder.newTrigger()
                .forJob(jobDetail)
                .withIdentity(jobDetail.getKey().getName(), "email-triggers")
                .withDescription("Send Email Trigger")
                .startAt(Date.from(startAt.toInstant()))
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withMisfireHandlingInstructionFireNow())
                .build();
    }
}

Spring Boot 内置了对 Quartz 的支持。它使用我们在 application.properties 文件中提供的配置自动创建一个 Quartz Scheduler bean。这就是为什么我们可以直接在控制器中注入 Scheduler

/scheduleEmail API 中,

我们首先验证请求体
*
然后,使用包含收件人电子邮件、主题和正文的 JobDataMap 构建一个 JobDetail 实例。我们创建的 JobDetailEmailJob 类型。我们将在下一节中定义 EmailJob
*
接下来,我们构建一个 Trigger 实例,定义何时应执行 Job。
*
最后,我们使用 scheduler.scheduleJob() API 调度作业。

创建 Quartz 作业以发送电子邮件

现在让我们定义发送实际电子邮件的作业。 Spring Boot 为 Quartz Scheduler 的 Job 接口提供了一个包装器,称为 QuartzJobBean。这允许您将 Quartz 作业创建为 Spring bean,您可以在其中自动装配其他 bean。

让我们通过扩展 QuartzJobBean 来创建我们的 EmailJob -

package com.example.quartzdemo.job;

import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.nio.charset.StandardCharsets;

@Component
public class EmailJob extends QuartzJobBean {
    private static final Logger logger = LoggerFactory.getLogger(EmailJob.class);

    @Autowired
    private JavaMailSender mailSender;

    @Autowired
    private MailProperties mailProperties;
    
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        logger.info("Executing Job with key {}", jobExecutionContext.getJobDetail().getKey());

        JobDataMap jobDataMap = jobExecutionContext.getMergedJobDataMap();
        String subject = jobDataMap.getString("subject");
        String body = jobDataMap.getString("body");
        String recipientEmail = jobDataMap.getString("email");

        sendMail(mailProperties.getUsername(), recipientEmail, subject, body);
    }

    private void sendMail(String fromEmail, String toEmail, String subject, String body) {
        try {
            logger.info("Sending Email to {}", toEmail);
            MimeMessage message = mailSender.createMimeMessage();

            MimeMessageHelper messageHelper = new MimeMessageHelper(message, StandardCharsets.UTF_8.toString());
            messageHelper.setSubject(subject);
            messageHelper.setText(body, true);
            messageHelper.setFrom(fromEmail);
            messageHelper.setTo(toEmail);

            mailSender.send(message);
        } catch (MessagingException ex) {
            logger.error("Failed to send email to {}", toEmail);
        }
    }
}

运行应用程序并测试 API

是时候运行应用程序并观看实况了。打开你的终端,进入项目的根目录并输入以下命令来运行它——

mvn spring-boot:run -Dspring.mail.password=<YOUR_SMTP_PASSWORD>

如果您已经在 application.properties 文件中设置了密码,则不需要传递 spring.mail.password 命令行参数。

默认情况下,应用程序将在端口 8080 上启动。现在让我们使用 /scheduleEmail API 安排一封电子邮件 -

而且,在这里我在指定的时间收到电子邮件:-)

结论

我希望你喜欢这篇文章。您可以在 Github Repository 中找到该项目的完整源代码。如果你觉得它有用,可以考虑在 Github 上给这个项目打个星。

相关文章