JavaRush /Java-Blog /Random-DE /Spring Scheduler hinzufügen – „Java-Projekt von A bis Z“

Spring Scheduler hinzufügen – „Java-Projekt von A bis Z“

Veröffentlicht in der Gruppe Random-DE
Hallo zusammen, meine lieben Freunde. Im vorherigen Artikel haben wir einen Client für die Arbeit mit der JavaRush-API für Artikel vorbereitet. Jetzt können wir Logik für unseren Job schreiben, der alle 15 Minuten ausgeführt wird. Genau wie in diesem Diagramm dargestellt: „Java-Projekt von A bis Z“: Spring Scheduler hinzufügen – 1Alle 15 Minuten wird ein Job gestartet (unserer Meinung nach nur eine Methode in einer bestimmten Klasse), der im Hintergrund der Hauptanwendung ausgeführt wird und Folgendes tut:
  1. Findet in allen Gruppen unserer Datenbank neue Artikel, die nach der vorherigen Ausführung veröffentlicht wurden.

    Dieses Schema legt eine kleinere Anzahl von Gruppen fest – nur solche mit aktiven Benutzern. Damals schien es mir logisch, aber jetzt verstehe ich, dass Sie den neuesten Artikel, den der Bot verarbeitet hat, immer auf dem neuesten Stand halten müssen, unabhängig davon, ob aktive Benutzer eine bestimmte Gruppe abonniert haben oder nicht. Es kann vorkommen, dass ein neuer Benutzer sofort die gesamte Anzahl der seit der Deaktivierung dieser Gruppe veröffentlichten Artikel erhält. Dies ist kein erwartetes Verhalten, und um es zu vermeiden, müssen wir die Gruppen aus unserer Datenbank, die derzeit keine aktiven Benutzer haben, auf dem neuesten Stand halten.
  2. Wenn es neue Artikel gibt, generieren Sie Nachrichten für alle Benutzer, die diese Gruppe aktiv abonniert haben. Wenn es keine neuen Artikel gibt, schließen wir die Arbeit einfach ab.

Übrigens habe ich in meinem TG-Kanal bereits erwähnt , dass der Bot bereits funktioniert und neue Artikel auf Basis von Abonnements versendet. Beginnen wir mit dem Schreiben von FindNewArtcileService . Die gesamte Arbeit zum Suchen und Versenden von Nachrichten wird dort stattfinden, und der Job startet lediglich die Methode dieses Dienstes:

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();
}
Ganz einfach, oder? Das ist das Wesentliche, und die ganze Schwierigkeit wird in der Umsetzung liegen:
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);
   }
}
Hier werden wir alles der Reihe nach behandeln:
  1. Mit groupService finden wir alle Gruppen, die in der Datenbank vorhanden sind.

  2. Dann verteilen wir uns auf alle Gruppen und rufen für jede den im letzten Artikel erstellten Client auf - javaRushPostClient.findNewPosts .

  3. Als nächstes aktualisieren wir mithilfe der setNewArticleId- Methode die Artikel-ID unseres letzten neuen Artikels, sodass unsere Datenbank weiß, dass wir bereits neue Artikel verarbeitet haben.

  4. Und indem wir die Tatsache nutzen, dass GroupSub über eine Sammlung von Benutzern verfügt, gehen wir die aktiven durch und senden Benachrichtigungen über neue Artikel.

Wir werden jetzt nicht darüber diskutieren, was die Botschaft ist, sie ist für uns nicht sehr wichtig. Hauptsache, die Methode funktioniert. Die Logik zum Suchen nach neuen Artikeln und zum Versenden von Benachrichtigungen ist fertig, sodass Sie mit der Erstellung eines Jobs fortfahren können.

Erstellen Sie FindNewArticleJob

Wir haben bereits darüber gesprochen, was SpringScheduler ist, aber wiederholen wir es kurz: Es handelt sich um einen Mechanismus im Spring-Framework zum Erstellen eines Hintergrundprozesses, der zu einem bestimmten, von uns festgelegten Zeitpunkt ausgeführt wird. Was brauchen Sie dafür? Der erste Schritt besteht darin, die Annotation @EnableScheduling zu unserer Spring-Eingabeklasse hinzuzufügen:
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);
   }

}
Der zweite Schritt besteht darin, eine Klasse zu erstellen, sie zum ApplicationContext hinzuzufügen und darin eine Methode zu erstellen, die regelmäßig ausgeführt wird. Wir erstellen ein Jobpaket auf derselben Ebene wie Repository, Service usw. und erstellen dort die Klasse 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));
   }
}
Um diese Klasse zum Anwendungskontext hinzuzufügen, habe ich die Annotation @Component verwendet . Und damit die Methode innerhalb der Klasse weiß, dass sie regelmäßig ausgeführt werden muss, habe ich der Methode eine Anmerkung hinzugefügt: @Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}") . Aber wir legen es in der Datei application.properties fest:
bot.recountNewArticleFixedRate = 900000
Hier ist der Wert in Millisekunden angegeben. Es wird 15 Minuten dauern. Bei dieser Methode ist alles einfach: Ich habe in den Protokollen eine supereinfache Metrik für mich hinzugefügt, um die Suche nach neuen Artikeln zu berechnen, um zumindest grob zu verstehen, wie schnell es funktioniert.

Testen neuer Funktionen

Jetzt werden wir es mit unserem Testbot testen. Aber wie? Ich werde nicht jedes Mal Artikel löschen, um anzuzeigen, dass Benachrichtigungen eingetroffen sind? Nein, natürlich. Wir bearbeiten einfach die Daten in der Datenbank und starten die Anwendung. Ich werde es auf meinem Testserver testen. Um dies zu tun, abonnieren wir eine Gruppe. Wenn das Abonnement abgeschlossen ist, erhält die Gruppe die aktuelle ID des neuesten Artikels. Gehen wir zur Datenbank und ändern den Wert zwei Artikel zurück. Daher gehen wir davon aus, dass es so viele Artikel geben wird, wie wir lastArticleId auf früher gesetzt haben . „Java-Projekt von A bis Z“: Spring Scheduler hinzufügen – 2Als nächstes gehen wir zur Site, sortieren die Artikel in der Gruppe „Java-Projekte“ – neue zuerst – und gehen zum dritten Artikel aus der Liste: „Java-Projekt von A bis Z“: Spring Scheduler hinzufügen – 3Gehen wir zum unteren Artikel und aus der Adressleiste erhalten wir die Artikel-ID – 3313: „Java-Projekt von A bis Z“: Spring Scheduler hinzufügen – 4Weiter , gehen Sie zu MySQL Workbench und ändern Sie den lastArticleId- Wert auf 3313. Sehen wir uns an, dass sich eine solche Gruppe in der Datenbank befindet: „Java-Projekt von A bis Z“: Spring Scheduler hinzufügen – 5Und dafür führen wir den Befehl aus: „Java-Projekt von A bis Z“: Spring Scheduler hinzufügen – 6Und das war’s, jetzt müssen Sie bis zum nächsten Start des Jobs warten Suche nach neuen Artikeln. Wir erwarten zwei Nachrichten zu einem neuen Artikel von der Java-Projektgruppe. Wie es heißt, ließ das Ergebnis nicht lange auf sich warten: „Java-Projekt von A bis Z“: Spring Scheduler hinzufügen – 7Es stellte sich heraus, dass der Bot wie erwartet funktionierte.

Ende

Wie immer aktualisieren wir die Version in pom.xml und fügen einen Eintrag zu RELEASE_NOTES hinzu, damit der Arbeitsverlauf gespeichert wird und Sie jederzeit zurückgehen und nachvollziehen können, was sich geändert hat. Daher erhöhen wir die Version um eine Einheit:
<version>0.7.0-SNAPSHOT</version>
Und aktualisieren Sie RELEASE_NOTES:
## 0.7.0-SNAPSHOT * JRTB-4: Möglichkeit hinzugefügt, Benachrichtigungen über neue Artikel zu senden * JRTB-8: Möglichkeit hinzugefügt, inaktive Telegrammbenutzer festzulegen * JRTB-9: Möglichkeit hinzugefügt, aktive Benutzer festzulegen und/oder mit deren Verwendung zu beginnen.
Jetzt können Sie eine Pull-Anfrage erstellen und neue Änderungen hochladen. Hier ist der Pull-Request mit allen Änderungen in zwei Teilen: STEP_8 . Was weiter? Es scheint, dass alles fertig ist und, wie wir sagen, in Produktion gehen kann, aber es gibt noch einige Dinge, die ich tun möchte. Konfigurieren Sie beispielsweise die Arbeit von Administratoren für den Bot, fügen Sie sie hinzu und fügen Sie die Möglichkeit hinzu, sie festzulegen. Es ist auch eine gute Idee, den Code vor der Fertigstellung durchzugehen und zu prüfen, ob es Dinge gibt, die umgestaltet werden können. Ich kann bereits die Desynchronisierung in der Benennung von Artikel/Beitrag erkennen. Ganz zum Schluss werden wir einen Rückblick auf das machen, was wir geplant und was wir erhalten haben. Und was möchten Sie in Zukunft tun? Jetzt teile ich mit Ihnen eine ziemlich grobe Idee, die das Licht der Welt erblicken kann und wird: einen Springboot-Starter zu erstellen, der über alle Funktionen für die Arbeit mit einem Telegram-Bot und die Suche nach Artikeln verfügt. Dadurch wird es möglich, den Ansatz zu vereinheitlichen und für andere Telegram-Bots zu nutzen. Dadurch wird dieses Projekt für andere zugänglicher und kann mehr Menschen zugute kommen. Das ist eine der Ideen. Eine weitere Idee besteht darin, tiefer in die Benachrichtigungsentwicklung einzusteigen. Aber darüber reden wir etwas später. Vielen Dank an alle für Ihre Aufmerksamkeit, wie immer: Liken – Abonnieren – Klingeln , Sternchen für unser Projekt , Kommentieren und Bewerten des Artikels! Vielen Dank an alle fürs Lesen.

Eine Liste aller Materialien der Serie finden Sie am Anfang dieses Artikels.

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