JavaRush /Java Blog /Random-IT /Aggiunta Spring Scheduler - "Progetto Java dalla A alla Z...
Roman Beekeeper
Livello 35

Aggiunta Spring Scheduler - "Progetto Java dalla A alla Z"

Pubblicato nel gruppo Random-IT
Ciao a tutti, miei cari amici. Nell'articolo precedente abbiamo preparato un client per lavorare con l'API JavaRush per gli articoli. Ora possiamo scrivere la logica per il nostro lavoro, che verrà eseguito ogni 15 minuti. Esattamente come mostrato in questo diagramma: “Progetto Java dalla A alla Z”: aggiunta di Spring Scheduler - 1ogni 15 minuti verrà lanciato un lavoro (a nostro avviso, solo un metodo in una classe specifica), che viene eseguito in background dell'applicazione principale e fa quanto segue:
  1. Trova in tutti i gruppi presenti nel nostro database i nuovi articoli pubblicati dopo l'esecuzione precedente.

    Questo schema specifica un numero minore di gruppi, solo quelli con utenti attivi. Allora mi sembrava logico, ma ora capisco che, indipendentemente dal fatto che ci siano utenti attivi iscritti a un gruppo specifico o meno, è comunque necessario mantenere aggiornato l'ultimo articolo elaborato dal bot. Può verificarsi una situazione in cui un nuovo utente riceve immediatamente l'intero numero di articoli pubblicati dalla disattivazione di questo gruppo. Questo non è un comportamento previsto e, per evitarlo, dobbiamo mantenere aggiornati i gruppi del nostro database che attualmente non hanno utenti attivi.
  2. Se sono presenti nuovi articoli, genera messaggi per tutti gli utenti che sono attivamente iscritti a questo gruppo. Se non ci sono nuovi articoli, completiamo semplicemente il lavoro.

A proposito, ho già detto nel mio canale TG che il bot è già funzionante e invia nuovi articoli in base agli abbonamenti. Iniziamo a scrivere FindNewArtcileService . Tutto il lavoro di ricerca e invio di messaggi si svolgerà lì e il lavoro avvierà solo il metodo di questo servizio:

TrovaNuovoArticoloServizio:

package com.github.javarushcommunity.jrtb.service;

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

   /**
    * Find new articles and notify subscribers about it.
    */
   void findNewArticles();
}
Molto semplice, vero? Questa è la sua essenza e tutta la difficoltà sarà nell'implementazione:
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);
   }
}
Qui tratteremo tutto in ordine:
  1. Utilizzando groupService troviamo tutti i gruppi presenti nel database.

  2. Quindi ci disperdiamo in tutti i gruppi e per ciascuno chiamiamo il client creato nell'ultimo articolo: javaRushPostClient.findNewPosts .

  3. Successivamente, utilizzando il metodo setNewArticleId , aggiorniamo l'ID dell'articolo del nostro ultimo nuovo articolo in modo che il nostro database sappia che ne abbiamo già elaborati di nuovi.

  4. E sfruttando il fatto che GroupSub ha una raccolta di utenti, esaminiamo quelli attivi e inviamo notifiche sui nuovi articoli.

Non discuteremo quale sia il messaggio adesso, non è molto importante per noi. L'importante è che il metodo funzioni. La logica per la ricerca di nuovi articoli e l'invio di notifiche è pronta, quindi puoi passare alla creazione di un lavoro.

Crea TrovaNuovoArticoloLavoro

Abbiamo già parlato di cos'è SpringScheduler, ma ripetiamolo velocemente: è un meccanismo del framework Spring per creare un processo in background che verrà eseguito in un momento specifico da noi impostato. Di cosa hai bisogno per questo? Il primo passo è aggiungere l' annotazione @EnableScheduling alla nostra classe di input 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);
   }

}
Il secondo passaggio consiste nel creare una classe, aggiungerla all'ApplicationContext e creare al suo interno un metodo che verrà eseguito periodicamente. Creiamo un pacchetto di lavoro allo stesso livello di repository, servizio e così via, e lì creiamo 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));
   }
}
Per aggiungere questa classe al contesto dell'applicazione ho utilizzato l' annotazione @Component . E affinché il metodo all'interno della classe sappia che deve essere eseguito periodicamente, ho aggiunto un'annotazione al metodo: @Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}") . Ma lo impostiamo nel file application.properties:
bot.recountNewArticleFixedRate = 900000
Qui il valore è in millisecondi. Saranno 15 minuti. Con questo metodo tutto è semplice: ho aggiunto nei log una metrica semplicissima per calcolare la ricerca di nuovi articoli, in modo da capire almeno approssimativamente quanto velocemente funziona.

Testare nuove funzionalità

Ora testeremo sul nostro bot di prova. Ma come? Non eliminerò ogni volta gli articoli per dimostrare che sono arrivate le notifiche? Ovviamente no. Modificheremo semplicemente i dati nel database e avvieremo l'applicazione. Lo proverò sul mio server di prova. Per fare ciò, iscriviamoci a qualche gruppo. Una volta completata l'iscrizione, al gruppo verrà assegnato l'ID corrente dell'ultimo articolo. Andiamo al database e modifichiamo il valore due articoli indietro. Di conseguenza, prevediamo che ci saranno tanti articoli quanti ne abbiamo impostati lastArticleId in precedenza . "Progetto Java dalla A alla Z": Aggiunta di Spring Scheduler - 2Successivamente, andiamo al sito, ordiniamo gli articoli nel gruppo dei progetti Java - prima quelli nuovi - e andiamo al terzo articolo dall'elenco: "Progetto Java dalla A alla Z": Aggiunta di Spring Scheduler - 3Andiamo all'articolo in fondo e dalla barra degli indirizzi otteniamo l'ID articolo - 3313: "Progetto Java dalla A alla Z": Aggiunta di Spring Scheduler - 4Avanti , vai su MySQL Workbench e modifica il valore lastArticleId in 3313. Vediamo che un gruppo di questo tipo è nel database: "Progetto Java dalla A alla Z": Aggiunta di Spring Scheduler - 5E per questo eseguiremo il comando: "Progetto Java dalla A alla Z": Aggiunta di Spring Scheduler - 6E questo è tutto, ora devi aspettare fino al prossimo avvio del lavoro per cercare nuovi articoli. Ci aspettiamo di ricevere due messaggi su un nuovo articolo dal gruppo dei progetti Java. Come si suol dire, il risultato non si è fatto attendere: "Progetto Java dalla A alla Z": Aggiunta di Spring Scheduler - 7il bot ha funzionato come ci aspettavamo.

Fine

Come sempre, aggiorniamo la versione in pom.xml e aggiungiamo una voce a RELEASE_NOTES in modo che la cronologia del lavoro venga salvata e tu possa sempre tornare indietro e capire cosa è cambiato. Pertanto, incrementiamo la versione di un'unità:
<version>0.7.0-SNAPSHOT</version>
E aggiorna RELEASE_NOTES:
## 0.7.0-SNAPSHOT * JRTB-4: aggiunta la possibilità di inviare notifiche sui nuovi articoli * JRTB-8: aggiunta la possibilità di impostare un utente Telegram inattivo * JRTB-9: aggiunta la possibilità di impostare un utente attivo e/o iniziare a usarlo.
Ora puoi creare una richiesta pull e caricare nuove modifiche. Ecco la richiesta pull con tutte le modifiche in due parti: STEP_8 . Qual è il prossimo? Sembrerebbe che tutto sia pronto e, come diciamo, può andare in produzione, ma ci sono ancora alcune cose che voglio fare. Ad esempio, configura il lavoro degli amministratori per il bot, aggiungili e aggiungi la possibilità di impostarli. È anche una buona idea esaminare il codice prima di finire e vedere se ci sono cose che possono essere sottoposte a refactoring. Posso già vedere la desincronizzazione nella denominazione dell'articolo/post. Alla fine faremo una retrospettiva di ciò che abbiamo pianificato e di ciò che abbiamo ricevuto. E cosa ti piacerebbe fare in futuro? Ora condividerò con voi un'idea abbastanza grezza che può e vedrà la luce: creare uno starter springboot che abbia tutte le funzionalità per lavorare con un bot di Telegram e cercare articoli. Ciò consentirà di unificare l’approccio e utilizzarlo per altri bot di Telegram. Ciò renderà questo progetto più accessibile agli altri e potrà portare benefici a più persone. Questa è una delle idee. Un'altra idea è approfondire lo sviluppo delle notifiche. Ma di questo ne parleremo un po' più tardi. Grazie a tutti per l'attenzione, come al solito: mettete mi piace, iscrivetevi, fate un campanello , contrassegnate il nostro progetto , commentate e votate l'articolo! Grazie a tutti per aver letto.

Un elenco di tutti i materiali della serie si trova all'inizio di questo articolo.

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