Java: 11
SpringBoot: 2.7.5
Часть 1: Узнайте о Quartz Scheduler
Часть 2: Реализация планировщика с Quartz Scheduler с помощью SpringBoot
часть 3:Реализуйте отправку оповещений в определенное время в пользователь QuartzScheduler
В последней части, части 3, я реализую отправку уведомлений в определенное время для каждого пользователя, что и было конечной целью
Проблема началась с отправки уведомлений в любое время для каждого пользователя.
Подумайте об этом, я скажу, что есть пользователь, который хочет получать уведомления в 2 часа ночи, и пользователь, который хочет получать уведомления в 3:30 утра.
Если вы используете традиционный планировщик, вам нужно найти пользователя, которому нужно отправлять уведомление каждую минуту (Если нет, прокомментируйте)
Однако, если пользователей много, это не может быть обрабатывается таким образом
- Что, если работа не закончится через минуту?
- Что делать, если работа не удалась?
Столкнувшись с этими проблемами, я пришел к использованию Quartz Scheduler
В примере использовать Rest для создания заданий, Но также могу реализовать их через CronJob. если вам нужно
Обзор
в этой части я создам две конечные точки
- Создать задание
(POST) /jobs/{groupName}/{jobName}/{minutes} - Выбрать активированную вакансию
(GET) /jobs
прочтите пример, вы можете изменить его достаточно для своего проекта
Обратитесь к Часть 1 и Часть 2для базовой настройки и принципов работы
Создать запрос
Во-первых, я реализую запрос, создающий задание
Все запросы заданий расширяются и реализуются BaseJobRequest
Задание с таким именем не может существовать в одной и той же группе
-›если это сделать, возникает ошибка, но я реализовал ее для перезаписи
@Getter @Setter public abstract class BaseJobRequest { @NotNull(message = "name cannot be Null") private String name; @NotNull(message = "group cannot be Null") private String group; private String description; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BaseJobRequest that = (BaseJobRequest) o; return name.equals(that.name) && group.equals(that.group); } @Override public int hashCode() { return Objects.hash(name, group); } public void setName(String name) { this.name = name; } public void setGroup(String group) { this.group = group; } public void setDescription(String description) { this.description = description; } }
Теперь реализуйте SampleJobRequest,
который включает время начала, fromUserId и поток контента
: content
доставляется userId
в startTime
@Getter public class SampleJobRequest extends BaseJobRequest { private final UUID userId; // userId private final LocalTime startTime; // Desired start time private final String content; private SampleJobRequest(Builder builder) { super.setName(builder.name); super.setGroup(builder.group); super.setDescription(builder.description); userId = builder.userId; startTime = builder.startTime; content = builder.content; } public static Builder builder() { return new Builder(); } public static final class Builder { private @NotNull(message = "name cannot be Null") String name; private @NotNull(message = "group cannot be Null") String group; private String description; private UUID userId; private LocalTime startTime; private String content; private Builder() { } public static Builder builder() { return new Builder(); } public Builder name(@NotNull(message = "name cannot be Null") String val) { name = val; return this; } public Builder group(@NotNull(message = "group cannot be Null") String val) { group = val; return this; } public Builder description(String val) { description = val; return this; } public Builder userId(UUID val) { userId = val; return this; } public Builder startTime(LocalTime val) { startTime = val; return this; } public Builder content(String val) { content = val; return this; } public SampleJobRequest build() { return new SampleJobRequest(this); } } }
реализовать действие, которое запускается при запуске триггера
см. часть 2 для DI (внедрение зависимостей) и использовать Component
(требуется AutoWiringSpringBeanJobFactory)
@Slf4j @Component @PersistJobDataAfterExecution @DisallowConcurrentExecution public class SampleJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); log.info("userId : " + jobDataMap.get("userId")); log.info("content : " + jobDataMap.get("content")); log.info("[send Message] " + jobDataMap.get("content") + " to " + jobDataMap.get("userId")); } }
реализовать QuartzHandler
, чтобы помочь вам создавать задания, создавать триггеры и проверять списки заданий
Инициализировать планировщик при запуске
@Slf4j @Configuration @RequiredArgsConstructor public class QuartzHandler { private final Scheduler scheduler; @PostConstruct public void init() { try { // Initialization (DB) scheduler.clear(); // add Listener scheduler.getListenerManager() .addJobListener(new JobsListener()); scheduler.getListenerManager() .addTriggerListener(new TriggersListener()); } catch (Exception e) { e.printStackTrace(); } } public <T extends Job> void addJob(Class<? extends Job> job, SampleJobRequest request, Map params) throws SchedulerException { final JobDetail jobDetail = buildJobDetail(job, request.getName(), request.getGroup(), request.getDescription(), params); final Trigger trigger = buildTrigger(request.getName(), request.getGroup(), request.getStartTime()); registerJobInScheduler(jobDetail, trigger); } public List<JobResponse> findAllActivatedJob() { List<JobResponse> result = new ArrayList<>(); try { for (String groupName : scheduler.getJobGroupNames()) { log.info("groupName : " + groupName); for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) { List<Trigger> trigger = (List<Trigger>) scheduler.getTriggersOfJob(jobKey); result.add(JobResponse.builder() .jobName(jobKey.getName()) .groupName(jobKey.getGroup()) .scheduleTime(trigger.get(0).getStartTime().toString()).build()); } } } catch (SchedulerException e) { e.printStackTrace(); } return result; } private void registerJobInScheduler(final JobDetail jobDetail, final Trigger trigger) throws SchedulerException { if (scheduler.checkExists(jobDetail.getKey())) { scheduler.deleteJob(jobDetail.getKey()); scheduler.scheduleJob(jobDetail, trigger); } else { scheduler.scheduleJob(jobDetail, trigger); } } public <T extends Job> JobDetail buildJobDetail( Class<? extends Job> job, final String jobName, final String group, String jobDescription, Map<String, Object> params) { JobDataMap jobDataMap = new JobDataMap(); if (params != null) { jobDataMap.putAll(params); } return JobBuilder.newJob(job) .withIdentity(jobName, group) .withDescription(jobDescription) .usingJobData(jobDataMap) .build(); } private Trigger buildTrigger(final String name, final String group, final LocalTime startTime) { SimpleTriggerFactoryBean triggerFactory = new SimpleTriggerFactoryBean(); triggerFactory.setName(name); triggerFactory.setGroup(group); triggerFactory.setStartTime(localTimeToDate(startTime)); triggerFactory.setRepeatCount(0); triggerFactory.setRepeatInterval(0); triggerFactory.afterPropertiesSet(); return triggerFactory.getObject(); } private Date localTimeToDate(final LocalTime startTime) { Instant instant = startTime.atDate(LocalDate .of(LocalDate.now().getYear(), LocalDate.now().getMonth(), LocalDate.now().getDayOfMonth())) .atZone(ZoneId.systemDefault()).toInstant(); return Date.from(instant); } }
Теперь я собираюсь реализовать две конечные точки, о которых упоминал ранее
Как упоминалось ранее, если задание с тем же именем создается в той же группе, оно будет перезаписано (удобно для смены места работы)
@Slf4j @RestController @RequiredArgsConstructor public class QuartzController { private final QuartzHandler quartzHandler; @PostMapping("/jobs/{group}/{name}/{minutes}") public ResponseEntity<Void> createJob(@PathVariable final String group, @PathVariable final String name, @PathVariable final Integer minutes) throws Exception { quartzHandler.addJob(SampleJob.class, SampleJobRequest.builder() .group(group) .name(name) .userId(UUID.randomUUID()) .content("[Send Message] at " + LocalDateTime.now()) .startTime(LocalTime.now().plusMinutes(minutes)).build()); return ResponseEntity.status(HttpStatus.CREATED).build(); } @GetMapping("/jobs") public ResponseEntity<List<JobResponse>>findAllJob() throws Exception { return ResponseEntity.ok().body(quartzHandler.findAllActivatedJob()); } } @Builder @Getter @RequiredArgsConstructor @NoArgsConstructor(force = true) public class JobResponse { private final String jobName; private final String groupName; private final String scheduleTime; }
вы можете увидеть код в моем gitHub