JavaRush /Blog Java /Random-FR /Ajout de Spring Scheduler - "Projet Java de A à Z"
Roman Beekeeper
Niveau 35

Ajout de Spring Scheduler - "Projet Java de A à Z"

Publié dans le groupe Random-FR
Bonjour à tous, mes chers amis. Dans l'article précédent , nous avons préparé un client pour travailler avec l'API JavaRush pour les articles. Nous pouvons maintenant écrire une logique pour notre travail, qui sera exécuté toutes les 15 minutes. Exactement comme le montre ce diagramme : « Projet Java de A à Z » : Ajout de Spring Scheduler - 1toutes les 15 minutes, une tâche sera lancée (à notre avis, juste une méthode dans une classe spécifique), qui est exécutée en arrière-plan de l'application principale et effectue les tâches suivantes :
  1. Trouve dans tous les groupes qui se trouvent dans notre base de données les nouveaux articles publiés après l'exécution précédente.

    Ce schéma spécifie un plus petit nombre de groupes - uniquement ceux comportant des utilisateurs actifs. À l'époque, cela me paraissait logique, mais maintenant je comprends que, qu'il y ait ou non des utilisateurs actifs abonnés à un groupe spécifique, vous devez toujours maintenir à jour le dernier article traité par le bot. Une situation peut survenir lorsqu'un nouvel utilisateur reçoit immédiatement la totalité des articles publiés depuis la désactivation de ce groupe. Ce n'est pas un comportement attendu, et pour l'éviter, nous devons maintenir à jour les groupes de notre base de données qui n'ont actuellement pas d'utilisateurs actifs.
  2. S'il y a de nouveaux articles, générez des messages pour tous les utilisateurs activement abonnés à ce groupe. S'il n'y a pas de nouveaux articles, nous terminons simplement le travail.

D'ailleurs, j'ai déjà mentionné dans ma chaîne TG que le bot fonctionne déjà et envoie de nouveaux articles basés sur des abonnements. Commençons par écrire FindNewArtcileService . Tout le travail de recherche et d'envoi de messages s'y déroulera, et le travail ne fera que lancer la méthode de ce service :

Rechercher un nouveau service d'article :

package com.github.javarushcommunity.jrtb.service;

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

   /**
    * Find new articles and notify subscribers about it.
    */
   void findNewArticles();
}
Très simple, non ? C'est son essence, et toute la difficulté sera dans la mise en œuvre :
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);
   }
}
Ici, nous traiterons de tout dans l'ordre :
  1. En utilisant groupService, nous trouvons tous les groupes présents dans la base de données.

  2. Ensuite, nous nous répartissons dans tous les groupes et pour chacun nous appelons le client créé dans le dernier article - javaRushPostClient.findNewPosts .

  3. Ensuite, à l'aide de la méthode setNewArticleId , nous mettons à jour l'ID de notre dernier nouvel article afin que notre base de données sache que nous en avons déjà traité de nouveaux.

  4. Et en utilisant le fait que GroupSub dispose d'un ensemble d'utilisateurs, nous passons en revue les utilisateurs actifs et envoyons des notifications sur les nouveaux articles.

Nous ne discuterons pas du message pour le moment, ce n’est pas très important pour nous. L’essentiel est que la méthode fonctionne. La logique de recherche de nouveaux articles et d'envoi de notifications est prête, vous pouvez donc passer à la création d'un travail.

Créer un travail FindNewArticle

Nous avons déjà parlé de ce qu'est SpringScheduler, mais répétons-le rapidement : il s'agit d'un mécanisme du framework Spring permettant de créer un processus en arrière-plan qui s'exécutera à une heure précise que nous fixons. De quoi avez-vous besoin pour cela ? La première étape consiste à ajouter l' annotation @EnableScheduling à notre classe d'entrée Spring :
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);
   }

}
La deuxième étape consiste à créer une classe, à l'ajouter à ApplicationContext et à y créer une méthode qui sera exécutée périodiquement. Nous créons un package de tâches au même niveau que le référentiel, le service, etc., et là nous créons la classe 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));
   }
}
Pour ajouter cette classe au contexte d'application, j'ai utilisé l' annotation @Component . Et pour que la méthode à l'intérieur de la classe sache qu'elle doit être exécutée périodiquement, j'ai ajouté une annotation à la méthode : @Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}") . Mais nous l'avons défini dans le fichier application.properties :
bot.recountNewArticleFixedRate = 900000
Ici, la valeur est en millisecondes. Ce sera 15 minutes. Dans cette méthode, tout est simple : j'ai ajouté une métrique super simple pour moi-même dans les logs pour calculer la recherche de nouveaux articles, afin de comprendre au moins approximativement à quelle vitesse cela fonctionne.

Tester de nouvelles fonctionnalités

Nous allons maintenant tester sur notre robot de test. Mais comment? Je ne supprimerai pas les articles à chaque fois pour montrer que les notifications sont arrivées ? Bien sûr que non. Nous allons simplement modifier les données dans la base de données et lancer l'application. Je vais le tester sur mon serveur de test. Pour ce faire, abonnez-vous à un groupe. Une fois l'abonnement terminé, le groupe recevra l'identifiant actuel du dernier article. Allons dans la base de données et modifions la valeur de deux articles. En conséquence, nous nous attendons à ce qu’il y ait autant d’articles que nous avons défini lastArticleId plus tôt . "Projet Java de A à Z" : Ajout de Spring Scheduler - 2Ensuite, nous allons sur le site, trions les articles dans le groupe des projets Java - les nouveaux en premier - et passons au troisième article de la liste : "Projet Java de A à Z" : Ajout de Spring Scheduler - 3Allons à l'article du bas et depuis la barre d'adresse nous obtenons l'article Id - 3313 : "Projet Java de A à Z" : Ajout de Spring Scheduler - 4Suivant , allez dans MySQL Workbench et changez la valeur lastArticleId en 3313. Voyons qu'un tel groupe est dans la base de données : "Projet Java de A à Z" : Ajout de Spring Scheduler - 5Et pour cela nous allons exécuter la commande : "Projet Java de A à Z" : Ajout de Spring Scheduler - 6Et c'est tout, vous devez maintenant attendre le prochain lancement du travail pour rechercher de nouveaux articles. Nous nous attendons à recevoir deux messages concernant un nouvel article du groupe de projets Java. Comme on dit, le résultat ne s’est pas fait attendre : "Projet Java de A à Z" : Ajout de Spring Scheduler - 7il s’avère que le bot a fonctionné comme prévu.

Fin

Comme toujours, nous mettons à jour la version dans pom.xml et ajoutons une entrée à RELEASE_NOTES afin que l'historique de travail soit enregistré et que vous puissiez toujours revenir en arrière et comprendre ce qui a changé. Par conséquent, nous incrémentons la version d’une unité :
<version>0.7.0-SNAPSHOT</version>
Et mettez à jour RELEASE_NOTES :
## 0.7.0-SNAPSHOT * JRTB-4 : possibilité supplémentaire d'envoyer des notifications sur les nouveaux articles * JRTB-8 : possibilité supplémentaire de définir un utilisateur de télégramme inactif * JRTB-9 : possibilité supplémentaire de définir un utilisateur actif et/ou de commencer à l'utiliser.
Vous pouvez désormais créer une pull request et télécharger de nouvelles modifications. Voici la pull request avec toutes les modifications en deux parties : STEP_8 . Et après? Il semblerait que tout soit prêt et, comme on dit, que cela puisse entrer en production, mais il y a encore certaines choses que je veux faire. Par exemple, configurez le travail des administrateurs pour le bot, ajoutez-les et ajoutez la possibilité de les définir. C'est également une bonne idée de parcourir le code avant de terminer et de voir s'il y a des éléments qui peuvent être refactorisés. Je vois déjà la désynchronisation dans le nommage de l'article/post. A la toute fin, nous ferons une rétrospective de ce que nous avons prévu et de ce que nous avons reçu. Et qu’aimeriez-vous faire à l’avenir ? Je vais maintenant partager avec vous une idée assez grossière qui peut et verra le jour : créer un starter springboot qui aurait toutes les fonctionnalités pour travailler avec un robot télégramme et rechercher des articles. Cela permettra d’unifier l’approche et de l’utiliser pour d’autres robots télégrammes. Cela rendra ce projet plus accessible aux autres et pourra bénéficier à plus de personnes. C'est une des idées. Une autre idée consiste à approfondir le développement des notifications. Mais nous en reparlerons un peu plus tard. Merci à tous pour votre attention, comme d'habitude : likez - abonnez-vous - cloche , star de notre projet , commentez et notez l'article ! Merci à tous d'avoir lu.

Une liste de tous les matériaux de la série se trouve au début de cet article.

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