JavaRush /Blog Java /Random-PL /Dodanie Spring Schedulera - „Projekt Java od A do Z”
Roman Beekeeper
Poziom 35

Dodanie Spring Schedulera - „Projekt Java od A do Z”

Opublikowano w grupie Random-PL
Witam wszystkich, moi drodzy przyjaciele. W poprzednim artykule przygotowaliśmy klienta do pracy z API JavaRush dla artykułów. Teraz możemy napisać logikę dla naszego zadania, które będzie wykonywane co 15 minut. Dokładnie tak, jak pokazano na tym diagramie: „Projekt Java od A do Z”: Dodanie Spring Schedulera - 1Co 15 minut zostanie uruchomione zadanie (naszym zdaniem po prostu metoda w konkretnej klasie), które jest wykonywane w tle głównej aplikacji i wykonuje następujące czynności:
  1. Wyszukuje we wszystkich grupach znajdujących się w naszej bazie nowe artykuły opublikowane po poprzedniej realizacji.

    Ten schemat określa mniejszą liczbę grup - tylko te z aktywnymi użytkownikami. Wtedy wydawało mi się to logiczne, ale teraz rozumiem, że niezależnie od tego, czy w danej grupie są aktywni użytkownicy, czy nie, nadal trzeba na bieżąco aktualizować najnowszy artykuł, który bot przetworzył. Może zaistnieć sytuacja, gdy nowy użytkownik od razu otrzyma całą liczbę artykułów opublikowanych od chwili dezaktywacji tej grupy. Nie jest to oczekiwane zachowanie i aby go uniknąć, musimy pozostawić w naszej bazie danych te grupy, które obecnie nie mają aktywnych użytkowników.
  2. Jeśli pojawiły się nowe artykuły, wygeneruj wiadomości dla wszystkich użytkowników, którzy aktywnie subskrybują tę grupę. Jeśli nie ma nowych artykułów, po prostu kończymy pracę.

Swoją drogą wspominałem już na swoim kanale TG, że bot już działa i wysyła nowe artykuły w oparciu o subskrypcje. Zacznijmy pisać FindNewArtcileService . Tam będą odbywały się wszystkie prace związane z wyszukiwaniem i wysyłaniem wiadomości, a zadanie uruchomi jedynie metodę tej usługi:

Znajdź nową usługę artykułu:

package com.github.javarushcommunity.jrtb.service;

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

   /**
    * Find new articles and notify subscribers about it.
    */
   void findNewArticles();
}
Bardzo proste, prawda? To jest istota tego i cała trudność będzie polegać na wdrożeniu:
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);
   }
}
Tutaj zajmiemy się wszystkim po kolei:
  1. Za pomocą groupService znajdujemy wszystkie grupy znajdujące się w bazie danych.

  2. Następnie rozchodzimy się do wszystkich grup i dla każdej wywołujemy klienta stworzonego w ostatnim artykule - javaRushPostClient.findNewPosts .

  3. Następnie za pomocą metody setNewArticleId aktualizujemy identyfikator artykułu naszego najnowszego nowego artykułu, tak aby nasza baza danych wiedziała, że ​​przetworzyliśmy już nowe.

  4. A korzystając z faktu, że GroupSub posiada kolekcję użytkowników, przeglądamy tych aktywnych i wysyłamy powiadomienia o nowych artykułach.

Nie będziemy teraz rozwodzić się nad tym, jaki jest przekaz, nie jest to dla nas zbyt istotne. Najważniejsze, że metoda działa. Logika wyszukiwania nowych artykułów i wysyłania powiadomień jest gotowa, więc możesz przejść do tworzenia oferty pracy.

Utwórz FindNewArticleJob

Mówiliśmy już o tym, czym jest SpringScheduler, ale powtórzmy to szybko: jest to mechanizm we frameworku Spring służący do tworzenia procesu w tle, który będzie uruchamiany o określonej przez nas godzinie. Czego potrzebujesz do tego? Pierwszym krokiem jest dodanie adnotacji @EnableScheduling do naszej wiosennej klasy wejściowej:
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);
   }

}
Drugim krokiem jest utworzenie klasy, dodanie jej do ApplicationContext i utworzenie w niej metody, która będzie uruchamiana okresowo. Tworzymy pakiet zadań na tym samym poziomie co repozytorium, usługa itd. i tam tworzymy klasę 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));
   }
}
Aby dodać tę klasę do kontekstu aplikacji, użyłem adnotacji @Component . I żeby metoda wewnątrz klasy wiedziała, że ​​trzeba ją uruchamiać okresowo, dodałem do metody adnotację: @Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}") . Ale ustawiliśmy to w pliku application.properties:
bot.recountNewArticleFixedRate = 900000
Tutaj wartość jest wyrażona w milisekundach. To będzie 15 minut. W tej metodzie wszystko jest proste: dodałem dla siebie super prostą metrykę w logach, aby obliczyć wyszukiwanie nowych artykułów, aby chociaż z grubsza zrozumieć, jak szybko to działa.

Testowanie nowej funkcjonalności

Teraz będziemy testować na naszym bocie testowym. Ale jak? Nie będę za każdym razem usuwać artykułów, aby pokazać, że dotarły powiadomienia? Oczywiście nie. Po prostu zmodyfikujemy dane w bazie i uruchomimy aplikację. Przetestuję to na moim serwerze testowym. Aby to zrobić, zapiszmy się do jakiejś grupy. Po zakończeniu subskrypcji grupa otrzyma aktualny identyfikator najnowszego artykułu. Przejdźmy do bazy danych i zmieńmy wartość dwa artykuły wstecz. W rezultacie spodziewamy się, że będzie tyle artykułów, ile wcześniej ustawiliśmy lastArticleId . „Projekt Java od A do Z”: Dodanie Spring Scheduler - 2Następnie wchodzimy na stronę, sortujemy artykuły w grupie Projekty Java - najpierw nowe - i przechodzimy do trzeciego artykułu z listy: "Java-проект от А до Я": Добавляем Spring Scheduler - 3Przejdźmy do artykułu na dole i z paska adresu otrzymamy identyfikator artykułu - 3313: "Java-проект от А до Я": Добавляем Spring Scheduler - 4Dalej , przejdź do MySQL Workbench i zmień wartość lastArticleId na 3313. Zobaczmy, czy taka grupa znajduje się w bazie danych: "Java-проект от А до Я": Добавляем Spring Scheduler - 5I dla niej wykonamy polecenie: "Java-проект от А до Я": Добавляем Spring Scheduler - 6I to wszystko, teraz musisz poczekać do następnego uruchomienia zadania, aby szukać nowych artykułów. Spodziewamy się otrzymać dwie wiadomości dotyczące nowego artykułu z grupy projektów Java. Jak mówią, na wynik nie trzeba było długo czekać: "Java-проект от А до Я": Добавляем Spring Scheduler - 7okazuje się, że bot zadziałał tak, jak się spodziewaliśmy.

Kończący się

Jak zawsze aktualizujemy wersję w pom.xml i dodajemy wpis do RELEASE_NOTES, aby historia pracy została zapisana i zawsze można było wrócić i zrozumieć, co się zmieniło. Dlatego zwiększamy wersję o jedną jednostkę:
<version>0.7.0-SNAPSHOT</version>
I zaktualizuj RELEASE_NOTES:
## 0.7.0-SNAPSHOT * JRTB-4: dodana możliwość wysyłania powiadomień o nowych artykułach * JRTB-8: dodana możliwość ustawienia nieaktywnego użytkownika telegramu * JRTB-9: dodana możliwość ustawienia aktywnego użytkownika i/lub rozpoczęcia korzystania z niego.
Teraz możesz utworzyć żądanie ściągnięcia i przesłać nowe zmiany. Oto żądanie ściągnięcia ze wszystkimi zmianami w dwóch częściach: STEP_8 . Co dalej? Wydawać by się mogło, że wszystko jest już gotowe i jak to się mówi, może wejść do produkcji, jednak jest jeszcze kilka rzeczy, które chcę zrobić. Przykładowo skonfiguruj pracę administratorów dla bota, dodaj ich i dodaj możliwość ich ustawiania. Dobrym pomysłem jest także przejrzenie kodu przed jego zakończeniem i sprawdzenie, czy są elementy, które można poddać refaktoryzacji. Już widzę desynchronizację w nazewnictwie artykułu/postu. Na sam koniec zrobimy retrospektywę tego, co zaplanowaliśmy i co otrzymaliśmy. A co chciałbyś robić w przyszłości? Teraz podzielę się z Wami dość prymitywnym pomysłem, który może ujrzeć światło dzienne i ujrzy światło dzienne: stworzyć starter typu springboot, który miałby wszystkie funkcje potrzebne do pracy z botem telegramowym i wyszukiwania artykułów. Umożliwi to ujednolicenie podejścia i wykorzystanie go w przypadku innych botów telegramowych. Dzięki temu projekt ten będzie bardziej dostępny dla innych i może przynieść korzyści większej liczbie osób. To jeden z pomysłów. Innym pomysłem jest głębsze zagłębienie się w rozwój powiadomień. Ale o tym porozmawiamy trochę później. Dziękuję wszystkim za uwagę, jak zwykle: lajk - subskrybuj - dzwonek , gwiazdka dla naszego projektu , komentarz i ocena artykułu! Dziękuję wszystkim za przeczytanie.

Lista wszystkich materiałów wchodzących w skład serii znajduje się na początku artykułu.

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