JavaRush /Java Blog /Random-TL /Pagdaragdag ng Spring Scheduler - "Java project from A to...

Pagdaragdag ng Spring Scheduler - "Java project from A to Z"

Nai-publish sa grupo
Kumusta sa lahat, mahal kong mga kaibigan. Sa nakaraang artikulo , naghanda kami ng kliyente para sa pagtatrabaho sa JavaRush API para sa mga artikulo. Ngayon ay maaari na tayong magsulat ng lohika para sa ating trabaho, na isasagawa tuwing 15 minuto. Eksaktong tulad ng ipinapakita sa diagram na ito: “Java-проект от А до Я”: Добавляем Spring Scheduler - 1Bawat 15 minuto isang trabaho ang ilulunsad (sa aming opinyon, isang paraan lamang sa isang partikular na klase), na isinasagawa sa background ng pangunahing aplikasyon at ginagawa ang sumusunod:
  1. Naghahanap sa lahat ng mga grupo na nasa aming database ng mga bagong artikulo na na-publish pagkatapos ng nakaraang pagpapatupad.

    Tinutukoy ng scheme na ito ang isang mas maliit na bilang ng mga pangkat - ang mga may aktibong user lamang. Sa oras na iyon, tila lohikal ito sa akin, ngunit ngayon naiintindihan ko na kahit na may mga aktibong user na naka-subscribe sa isang partikular na grupo o wala, kailangan mo pa ring panatilihing napapanahon ang pinakabagong artikulo na naproseso ng bot. Maaaring lumitaw ang isang sitwasyon kapag natanggap kaagad ng isang bagong user ang buong bilang ng mga artikulong nai-publish mula nang i-deactivate ang pangkat na ito. Hindi ito inaasahang pag-uugali, at upang maiwasan ito, kailangan nating panatilihin ang mga pangkat na iyon mula sa aming database na kasalukuyang walang kasalukuyang mga aktibong user.
  2. Kung may mga bagong artikulo, bumuo ng mga mensahe para sa lahat ng mga user na aktibong naka-subscribe sa grupong ito. Kung walang mga bagong artikulo, kumpletuhin lang namin ang gawain.

Oo nga pala, nabanggit ko na sa aking TG channel na gumagana na ang bot at nagpapadala ng mga bagong artikulo batay sa mga subscription. Simulan natin ang pagsusulat ng FindNewArtcileService . Ang lahat ng gawain ng paghahanap at pagpapadala ng mga mensahe ay magaganap doon, at ilulunsad lamang ng trabaho ang pamamaraan ng serbisyong ito:

FindNewArticleService:

package com.github.javarushcommunity.jrtb.service;

/**
* Service for finding new articles.
*/
public interface FindNewArticleService {

   /**
    * Find new articles and notify subscribers about it.
    */
   void findNewArticles();
}
Napakasimple, tama? Ito ang kakanyahan nito, at ang lahat ng kahirapan ay nasa pagpapatupad:
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.javarushclient.JavaRushPostClient;
import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo;
import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class FindNewArticleServiceImpl implements FindNewArticleService {

   public static final String JAVARUSH_WEB_POST_FORMAT = "https://javarush.com/groups/posts/%s";

   private final GroupSubService groupSubService;
   private final JavaRushPostClient javaRushPostClient;
   private final SendBotMessageService sendMessageService;

   @Autowired
   public FindNewArticleServiceImpl(GroupSubService groupSubService,
                                    JavaRushPostClient javaRushPostClient,
                                    SendBotMessageService sendMessageService) {
       this.groupSubService = groupSubService;
       this.javaRushPostClient = javaRushPostClient;
       this.sendMessageService = sendMessageService;
   }


   @Override
   public void findNewArticles() {
       groupSubService.findAll().forEach(gSub -> {
           List<PostInfo> newPosts = javaRushPostClient.findNewPosts(gSub.getId(), gSub.getLastArticleId());

           setNewLastArticleId(gSub, newPosts);

           notifySubscribersAboutNewArticles(gSub, newPosts);
       });
   }

   private void notifySubscribersAboutNewArticles(GroupSub gSub, List<PostInfo> newPosts) {
       Collections.reverse(newPosts);
       List<String> messagesWithNewArticles = newPosts.stream()
               .map(post -> String.format("✨Вышла новая статья <b>%s</b> в группе <b>%s</b>.✨\n\n" +
                               "<b>Описание:</b> %s\n\n" +
                               "<b>Ссылка:</b> %s\n",
                       post.getTitle(), gSub.getTitle(), post.getDescription(), getPostUrl(post.getKey())))
               .collect(Collectors.toList());

       gSub.getUsers().stream()
               .filter(TelegramUser::isActive)
               .forEach(it -> sendMessageService.sendMessage(it.getChatId(), messagesWithNewArticles));
   }

   private void setNewLastArticleId(GroupSub gSub, List<PostInfo> newPosts) {
       newPosts.stream().mapToInt(PostInfo::getId).max()
               .ifPresent(id -> {
                   gSub.setLastArticleId(id);
                   groupSubService.save(gSub);
               });
   }

   private String getPostUrl(String key) {
       return String.format(JAVARUSH_WEB_POST_FORMAT, key);
   }
}
Dito ay haharapin natin ang lahat sa pagkakasunud-sunod:
  1. Gamit ang groupService nakita namin ang lahat ng mga grupo na nasa database.

  2. Pagkatapos ay kumalat kami sa lahat ng mga grupo at para sa bawat isa ay tinawag namin ang kliyente na nilikha sa huling artikulo - javaRushPostClient.findNewPosts .

  3. Susunod, gamit ang setNewArticleId method , ina-update namin ang article ID ng aming pinakabagong bagong artikulo para malaman ng aming database na naproseso na namin ang mga bago.

  4. At gamit ang katotohanan na ang GroupSub ay may koleksyon ng mga user, dumaan kami sa mga aktibo at nagpapadala ng mga abiso tungkol sa mga bagong artikulo.

Hindi namin tatalakayin kung ano ang mensahe ngayon, hindi ito napakahalaga para sa amin. Ang pangunahing bagay ay gumagana ang pamamaraan. Ang lohika para sa paghahanap ng mga bagong artikulo at pagpapadala ng mga abiso ay handa na, kaya maaari kang magpatuloy sa paglikha ng trabaho.

Lumikha ng FindNewArticleJob

Мы уже говорor о том, что такое SpringScheduler, но повторим еще раз быстро: это механизм в Spring фреймворке для создания фонового процесса, который будет выполняться в определенное время, задаваемого нами. What нужно для этого? Первый этап — добавить аннотацию @EnableScheduling к нашему входному классу для спринга:
package com.github.javarushcommunity.jrtb;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling
@SpringBootApplication
public class JavarushTelegramBotApplication {

   public static void main(String[] args) {
       SpringApplication.run(JavarushTelegramBotApplication.class, args);
   }

}
Второй этап — создать класс, добавить его в ApplicationContext и создать в нем метод, который будет запускаться периодически. Создаем пакет job на одном уровне с repository, service и так далее и там создаем класс FindNewArticleJob:
package com.github.javarushcommunity.jrtb.job;

import com.github.javarushcommunity.jrtb.service.FindNewArticleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;

/**
* Job for finding new articles.
*/
@Slf4j
@Component
public class FindNewArticlesJob {

   private final FindNewArticleService findNewArticleService;

   @Autowired
   public FindNewArticlesJob(FindNewArticleService findNewArticleService) {
       this.findNewArticleService = findNewArticleService;
   }

   @Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}")
   public void findNewArticles() {
       LocalDateTime start = LocalDateTime.now();

       log.info("Find new article job started.");

       findNewArticleService.findNewArticles();

       LocalDateTime end = LocalDateTime.now();

       log.info("Find new articles job finished. Took seconds: {}",
               end.toEpochSecond(ZoneOffset.UTC) - start.toEpochSecond(ZoneOffset.UTC));
   }
}
Whatбы добавить этот класс в Application Context, я использовал аннотацию @Component. А чтобы метод внутри класса знал, что ему нужно запускаться периодически, я добавил к методу аннотацию: @Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}"). А вот Howое meaning будет — его мы задаем уже в application.properties файле:
bot.recountNewArticleFixedRate = 900000
Здесь meaning указано в миллисекундах. Это будет 15 minutes. В этом методе все просто: я для себя в логах добавил супер простую метрику для подсчета поиска новых статей, чтобы хоть примерно представлять насколько быстро работает.

Тестируем новый функционал

Теперь будем тестировать на нашем тестовом боте. Но How? Не буду же я удалять каждый раз статьи, чтобы показать, что уведомления пришли? Нет, конечно. Просто будем править данные в БД и запускать приложение. Тестировать я буду на своем тестовом енве. Для этого подпишемся на Howую-то группу. Когда подписка будет оформлена, группе будет поставлен актуальный ID последней статьи. Пойдем в базу и поменяем meaning на две статьи назад. В итоге ожидаем, что будет столько статей, на сколько раньше поставим lastArticleId."Java-проект от А до Я": Добавляем Spring Scheduler - 2Далее идем на сайт, сортируем статьи в группе Java-проекты — сначала новые — и заходим в третью статью из списка:"Java-проект от А до Я": Добавляем Spring Scheduler - 3Зайдем в нижнюю статью и из addressной строки получим article Id — 3313:"Java-проект от А до Я": Добавляем Spring Scheduler - 4Далее идем в MySQL Workbench и меняем meaning lastArticleId на 3313. Посмотрим, что такая группа есть в базе:"Java-проект от А до Я": Добавляем Spring Scheduler - 5И для нее выполним команду:"Java-проект от А до Я": Добавляем Spring Scheduler - 6И все, теперь нужно подождать до следующего запуска джобы по поиску новых статей. Ожидаем, что придет два messages о новой статье из группы Java-проекты. Как говорится, результат не заставил себя ждать:"Java-проект от А до Я": Добавляем Spring Scheduler - 7Получается, что бот отработал так, How мы и ожидали.

Окончание

Как и всегда — обновляем версию в pom.xml и добавляем запись в RELEASE_NOTES, чтобы история работы сохранилась и всегда можно было вернуться и понять, что изменилось. Поэтому инкрементируем на одну единицу версию:
<version>0.7.0-SNAPSHOT</version>
И обновляем RELEASE_NOTES:
## 0.7.0-SNAPSHOT * JRTB-4: added ability to send notifications about new articles * JRTB-8: added ability to set inactive telegram user * JRTB-9: added ability to set active user and/or start using it.
Теперь уж можно создавать пулл-реквест и заливать новые изменения. Вот пулл-реквест со всеми изменениями за две части: STEP_8. What дальше? Уже казалось бы все готово и, How говорят у нас, может выходить в продакшен, но есть еще некоторые вещи, которые хочется сделать. Например, настроить работу админов у бота, добавить их и добавить возможность задавать их. Также перед окончанием хорошо бы пройтись по codeу и посмотреть, нет ли вещей, которые можно отрефакторить. Я вот уже вижу рассинхрон в именовании article/post. В самом конце сделаем ретроспективу того, что мы планировали и что получor. И что хочется сделать в будущем. Now поделюсь с вами достаточно сырой идеей, которая может и увидит свет: сделать springboot starter, который имел бы всю функциональность по работе с телеграм-ботом и поиском статей. Это даст возможность унифицировать подход и использовать его для других телеграм-ботов. Таким образом этот проект станет более доступным для других и сможет принести пользу большему числу людей. Это одна из идей. Другая идея — идти в глубь разработки нотификаций. Но об этом мы поговорим несколько позже. Всем спасибо за внимание, с вас How обычно: лайк - подписка - колокольчик, звезду нашему проекту, комментарий и оценить статью! Всем спасибо за прочтение.

Список всех материалов серии в начале этой статьи.

Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION