JavaRush /Blog Java /Random-ES /Agregar Spring Scheduler - "Proyecto Java de la A a la Z"...

Agregar Spring Scheduler - "Proyecto Java de la A a la Z"

Publicado en el grupo Random-ES
Hola a todos, mis queridos amigos. En el artículo anterior , preparamos un cliente para trabajar con la API JavaRush para artículos. Ahora podemos escribir lógica para nuestro trabajo, que se ejecutará cada 15 minutos. Exactamente como se muestra en este diagrama: “Proyecto Java de la A a la Z”: Agregar Spring Scheduler - 1Cada 15 minutos se lanzará un trabajo (en nuestra opinión, solo un método en una clase específica), que se ejecuta en segundo plano de la aplicación principal y hace lo siguiente:
  1. Encuentra en todos los grupos que se encuentran en nuestra base de datos nuevos artículos publicados después de la ejecución anterior.

    Este esquema especifica un número menor de grupos, solo aquellos con usuarios activos. En ese momento me pareció lógico, pero ahora entiendo que independientemente de si hay usuarios activos suscritos a un grupo específico o no, aún es necesario mantener actualizado el último artículo que procesó el bot. Puede surgir una situación en la que un nuevo usuario reciba inmediatamente la cantidad total de artículos publicados desde la desactivación de este grupo. Este no es un comportamiento esperado y, para evitarlo, debemos mantener actualizados aquellos grupos de nuestra base de datos que actualmente no tienen usuarios activos.
  2. Si hay artículos nuevos, genere mensajes para todos los usuarios que estén suscritos activamente a este grupo. Si no hay artículos nuevos, simplemente completamos el trabajo.

Por cierto, ya comenté en mi canal de TG que el bot ya está funcionando y enviando nuevos artículos en base a suscripciones. Comencemos a escribir FindNewArtcileService . Todo el trabajo de búsqueda y envío de mensajes se realizará allí, y el trabajo solo lanzará el método de este servicio:

Buscar nuevo artículo servicio:

package com.github.javarushcommunity.jrtb.service;

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

   /**
    * Find new articles and notify subscribers about it.
    */
   void findNewArticles();
}
Muy sencillo, ¿verdad? Ésta es su esencia, y toda la dificultad estará en la implementación:
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);
   }
}
Aquí nos ocuparemos de todo en orden:
  1. Usando groupService encontramos todos los grupos que hay en la base de datos.

  2. Luego nos dispersamos en todos los grupos y para cada uno llamamos al cliente creado en el último artículo: javaRushPostClient.findNewPosts .

  3. A continuación, utilizando el método setNewArticleId , actualizamos el ID del artículo nuevo más reciente para que nuestra base de datos sepa que ya hemos procesado otros nuevos.

  4. Y aprovechando el hecho de que GroupSub tiene una colección de usuarios, revisamos los activos y enviamos notificaciones sobre nuevos artículos.

No discutiremos cuál es el mensaje ahora, no es muy importante para nosotros. Lo principal es que el método funciona. La lógica para buscar nuevos artículos y enviar notificaciones está lista, por lo que puedes pasar a crear un trabajo.

Crear BuscarNuevoArtículoTrabajo

Ya hemos hablado de qué es SpringScheduler, pero repitámoslo rápidamente: es un mecanismo en el marco de Spring para crear un proceso en segundo plano que se ejecutará en un momento específico que establezcamos. ¿Qué necesitas para esto? El primer paso es agregar la anotación @EnableScheduling a nuestra clase de entrada de primavera:
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);
   }

}
El segundo paso es crear una clase, agregarla a ApplicationContext y crear un método en ella que se ejecutará periódicamente. Creamos un paquete de trabajo al mismo nivel que repositorio, servicio, etc., y allí creamos la clase 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));
   }
}
Para agregar esta clase al contexto de la aplicación utilicé la anotación @Component . Y para que el método dentro de la clase sepa que debe ejecutarse periódicamente, agregué una anotación al método: @Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}") . Pero lo configuramos en el archivo application.properties:
bot.recountNewArticleFixedRate = 900000
Aquí el valor está en milisegundos. Serán 15 minutos. En este método, todo es simple: agregué una métrica súper simple en los registros para calcular la búsqueda de nuevos artículos, para poder comprender al menos aproximadamente qué tan rápido funciona.

Probando nueva funcionalidad

Ahora realizaremos la prueba en nuestro bot de prueba. ¿Pero cómo? ¿No eliminaré artículos cada vez para mostrar que han llegado notificaciones? Por supuesto que no. Simplemente editaremos los datos en la base de datos y ejecutaremos la aplicación. Lo probaré en mi servidor de prueba. Para hacer esto, suscribámonos a algún grupo. Cuando se complete la suscripción, el grupo recibirá la identificación actual del último artículo. Vayamos a la base de datos y cambiemos el valor dos artículos atrás. Como resultado, esperamos que haya tantos artículos como establecimos en lastArticleId anteriormente . "Proyecto Java de la A a la Z": agregando Spring Scheduler - 2A continuación, vamos al sitio, clasificamos los artículos en el grupo de proyectos Java ( los nuevos primero ) y vamos al tercer artículo de la lista: "Proyecto Java de la A a la Z": agregando Spring Scheduler - 3Vayamos al artículo inferior y desde la barra de direcciones obtenemos el Id. del artículo: 3313: "Proyecto Java de la A a la Z": agregando Spring Scheduler - 4Siguiente , vaya a MySQL Workbench y cambie el valor de lastArticleId a 3313. Veamos que dicho grupo está en la base de datos: "Proyecto Java de la A a la Z": agregando Spring Scheduler - 5Y para ello ejecutaremos el comando: "Proyecto Java de la A a la Z": agregando Spring Scheduler - 6Y listo, ahora debe esperar hasta el próximo lanzamiento del trabajo para buscar nuevos artículos. Esperamos recibir dos mensajes sobre un nuevo artículo del grupo de proyectos Java. Como dicen, el resultado no se hizo esperar: "Proyecto Java de la A a la Z": agregando Spring Scheduler - 7resulta que el bot funcionó como esperábamos.

Finalizando

Como siempre, actualizamos la versión en pom.xml y agregamos una entrada a RELEASE_NOTES para que se guarde el historial de trabajo y usted siempre pueda regresar y comprender qué ha cambiado. Por tanto, incrementamos la versión en una unidad:
<version>0.7.0-SNAPSHOT</version>
Y actualice RELEASE_NOTES:
## 0.7.0-SNAPSHOT * JRTB-4: capacidad agregada para enviar notificaciones sobre nuevos artículos * JRTB-8: capacidad agregada para configurar un usuario de Telegram inactivo * JRTB-9: capacidad agregada para configurar un usuario activo y/o comenzar a usarlo.
Ahora puedes crear una solicitud de extracción y cargar nuevos cambios. Aquí está la solicitud de extracción con todos los cambios en dos partes: STEP_8 . ¿Que sigue? Parecería que está todo listo y, como decimos, puede entrar en producción, pero aún quedan algunas cosas que quiero hacer. Por ejemplo, configure el trabajo de los administradores para el bot, agréguelos y agregue la capacidad de configurarlos. También es una buena idea revisar el código antes de terminar y ver si hay cosas que se puedan refactorizar. Ya puedo ver la desincronización en el nombre del artículo/publicación. Al final, haremos una retrospectiva de lo que planeamos y de lo que recibimos. ¿Y qué te gustaría hacer en el futuro? Ahora compartiré con ustedes una idea bastante burda que puede ver la luz y verá la luz: crear un iniciador Springboot que tenga todas las funciones para trabajar con un bot de Telegram y buscar artículos. Esto permitirá unificar el enfoque y utilizarlo para otros bots de Telegram. Esto hará que este proyecto sea más accesible para otros y pueda beneficiar a más personas. Esta es una de las ideas. Otra idea es profundizar en el desarrollo de notificaciones. Pero hablaremos de esto un poco más tarde. Gracias a todos por su atención, como siempre: me gusta - suscríbase - campana , destaque nuestro proyecto , comente y califique el artículo. Gracias a todos por leer.

Al principio de este artículo encontrará una lista de todos los materiales de la serie.

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