Hola a todos, mis queridos amigos. Paso a paso nos acercamos a nuestro objetivo: convertirnos en el MVP de nuestro proyecto: JavaRush Telegram Bot. Como dije en el último artículo, solo quedan 5 tareas. Hoy cubriremos dos de ellos. "Proyecto Java de la A a la Z": Agregar un cliente a los artículos - 1Quiero repetir que el proyecto no terminará aquí. Todavía tengo un montón de ideas y visiones sobre cómo debería desarrollarse este proyecto, qué cosas nuevas se le pueden agregar, qué se puede hacer mejor. Antes de MVP, publicaremos un artículo aparte sobre el tema de la refactorización, es decir, sobre cómo mejorar la calidad del código sin cambiar su funcionalidad. En ese momento, todo el proyecto será visible y quedará claro qué y dónde se puede mejorar. En nuestro caso, estaremos protegidos al máximo contra la interrupción de la funcionalidad, porque se han escrito muchas pruebas. También escribiremos una retrospectiva sobre lo que queríamos y lo que obtuvimos al final. Esto es algo muy útil: veamos qué tan correctamente se veía todo hace seis meses. Al menos esto es muy interesante para mí. Si alguien quiere probarse a sí mismo como tester manual, escríbanos y colaboraremos. ¡Hagamos este proyecto mejor juntos! Entonces, aquí están: dos tareas descritas hace seis meses: JRTB-8 y JRTB-9 . Comencé a mirar lo que había que implementar para estas tareas y me di cuenta de que en términos de ejecutar comandos, todo ya estaba listo. Sucede...) Aquí, puedes mirar StartCommand , el método de ejecución :

@Override
public void execute(Update update) {
   String chatId = update.getMessage().getChatId().toString();

   telegramUserService.findByChatId(chatId).ifPresentOrElse(
           user -> {
               user.setActive(true);
               telegramUserService.save(user);
           },
           () -> {
               TelegramUser telegramUser = new TelegramUser();
               telegramUser.setActive(true);
               telegramUser.setChatId(chatId);
               telegramUserService.save(telegramUser);
           });

   sendBotMessageService.sendMessage(chatId, START_MESSAGE);
}
La lógica funciona aquí: si nuestra base de datos ya tiene dicho usuario por chatId, simplemente configuramos el campo active = true para él. Y si no existe tal usuario, creamos uno nuevo. Lo mismo ocurre con el comando /stop en StopCommand :

@Override
public void execute(Update update) {
   telegramUserService.findByChatId(update.getMessage().getChatId().toString())
           .ifPresent(it -> {
               it.setActive(false);
               telegramUserService.save(it);
           });
   sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), STOP_MESSAGE);
}
Se puede ver que al llamar a este comando, solo se establece el campo active = false para el usuario. Y eso es todo: sus suscripciones vivirán y esperarán entre bastidores cuando el usuario decida nuevamente activar el chat con el bot. Y parecería que la tarea ya se ha completado y se puede cerrar. Pero no estaba ahí. La tarea más importante es crear una alerta sobre nuevos artículos en la suscripción. Aquí es donde estas tareas se actualizarán y completarán por completo. Es decir, hasta que no hayamos implementado la notificación de nuevos artículos no se podrá cerrar. Por lo tanto, vamos a ocuparnos de la tarea JRTB-4: crear un cheque cada 20 minutos y notificaciones sobre nuevos artículos. ¡Amigos! ¿Quiere saber inmediatamente cuándo se publica el nuevo código para el proyecto? ¿Cuándo sale un nuevo artículo? Únete a mi canal tg . Allí recopilo mis artículos, mis pensamientos y mi desarrollo de código abierto.

Implementamos JRTB-4

Qué debemos hacer como parte de esta tarea:
  1. Cree un trabajo que irá periódicamente a todos los grupos a los que tengamos suscripciones en la base de datos, ordenará los artículos por fecha de publicación y comprobará si el ID de la última publicación coincide con el valor en GroupSub. Si no coincide, entonces necesita saber exactamente cuántos artículos se han publicado desde la última vez. Actualizamos last_article_id en GroupSub7 al estado actual.

  2. Cuando encontramos una lista de artículos publicados, buscamos todos los usuarios ACTIVOS de estos grupos y les enviamos notificaciones sobre nuevos artículos.

Para hacer esto, usaremos algo como Spring Scheduler. Este es un mecanismo en Spring Framework, con él puedes crear tareas que se ejecutarán en un momento específico. Ya sea cada 15-20-40 minutos, o todos los jueves a las 15:30 o alguna otra opción. También se les llama papel de calco del inglés: joba. Mientras realizamos esta tarea, deliberadamente dejaré un defecto en la búsqueda de nuevos artículos. Es bastante raro y solo apareció en una situación en la que probé manualmente el funcionamiento de esta tarea. Para hacer esto necesita escribir un cliente para buscar artículos. Para ello utilizaremos la API Swagger que ya conocemos . Hay un post-controlador. Sólo nos interesa buscar una colección de artículos utilizando ciertos filtros:
/api/1.0/rest/posts Obtener publicaciones por filtros
Trabajaremos con esta solicitud. ¿Qué necesitamos en él? Obtenga una lista de artículos que pertenecen a un grupo específico y deben ordenarse por fecha de publicación. De esta forma podemos tomar los últimos 15 artículos y comprobar si se han publicado nuevas publicaciones en función del últimoArticleId de nuestra base de datos. Si los hubiera, los transmitiremos para su procesamiento y envío al usuario. Entonces necesitamos escribir JavaRushPostClient .

Escribimos JavaRushPostClient

Aquí no intentaremos cubrir todas las solicitudes que nos enviaron en la API y crearemos solo la que necesitemos. Con esto conseguimos dos objetivos a la vez:
  1. Aceleramos el proceso de redacción de nuestra solicitud.

  2. Dejamos este trabajo a aquellos que quieran ayudar a nuestra comunidad y decidan probarse a sí mismos como desarrolladores. Haré tareas para esto que se puedan completar después del MVP.

Hagamoslo. Para consultar la sección Modelos en Swagger UI, crearemos los siguientes DTO:"Proyecto Java de la A a la Z": Agregar un cliente a los artículos - 2

Información de usuario base:


package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Data;

/**
* DTO, which represents base user information.
*/
@Data
public class BaseUserInfo {
   private String city;
   private String country;
   private String displayName;
   private Integer id;
   private String job;
   private String key;
   private Integer level;
   private String pictureUrl;
   private String position;
   private UserPublicStatus publicStatus;
   private String publicStatusMessage;
   private Integer rating;
   private Integer userId;
}

Idioma:


package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* DTO, which represents languages.
*/
public enum Language {
   UNKNOWN,
   ENGLISH,
   GERMAN,
   SPANISH,
   HINDI,
   FRENCH,
   PORTUGUESE,
   POLISH,
   BENGALI,
   PUNJABI,
   CHINESE,
   ITALIAN,
   INDONESIAN,
   MARATHI,
   TAMIL,
   TELUGU,
   JAPANESE,
   KOREAN,
   URDU,
   TAIWANESE,
   NETHERLANDS,
   RUSSIAN,
   UKRAINIAN
}

Me gustaInformación:


package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* DTO, which represents like's information.
*/
public class LikesInfo {

   private Integer count;
   private LikeStatus status;
}

Me gustaEstado:


package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* DTO, which represents like's status.
*/
public enum LikeStatus {

   UNKNOWN,
   LIKE,
   HOT,
   FOLLOW,
   FAVORITE,
   SOLUTION,
   HELPFUL,
   ARTICLE,
   OSCAR,
   DISLIKE,
   WRONG,
   SPAM,
   ABUSE,
   FOUL,
   TROLLING,
   OFFTOPIC,
   DUPLICATE,
   DIRTY,
   OUTDATED,
   BORING,
   UNCLEAR,
   HARD,
   EASY,
   FAKE,
   SHAM,
   AWFUL
}

Tipo de mensaje:


package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* DTO, which represents post types.
*/
public enum PostType {
   UNKNOWN, USUAL, INNER_LINK, OUTER_LINK
}

Estado público del usuario:


package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* DTO, which represents user public status.
*/
public enum UserPublicStatus {
   UNKNOWN,
   BEGINNER,
   ACTIVE,
   STRONG,
   GRADUATED,
   INTERNSHIP_IN_PROGRESS,
   INTERNSHIP_COMPLETED,
   RESUME_COMPLETED,
   LOOKING_FOR_JOB,
   HAVE_JOB;
}

VisibilityStatus:
package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* DTO, which represents visibility status.
*/
public enum VisibilityStatus {
   UNKNOWN,
   RESTRICTED,
   PUBLIC,
   PROTECTED,
   PRIVATE,
   DISABLED,
   DELETED
}
Basándonos en todos estos DTO, escribamos una clase principal para recibir artículos:

Información de publicación:


package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Data;

/**
* DTO, which represents post information.
*/
@Data
public class PostInfo {

   private BaseUserInfo authorInfo;
   private Integer commentsCount;
   private String content;
   private Long createdTime;
   private String description;
   private GroupInfo groupInfo;
   private Integer id;
   private String key;
   private Language language;
   private LikesInfo likesInfo;
   private GroupInfo originalGroupInfo;
   private String pictureUrl;
   private Double rating;
   private Integer ratingCount;
   private String title;
   private PostType type;
   private Long updatedTime;
   private UserDiscussionInfo userDiscussionInfo;
   private Integer views;
   private VisibilityStatus visibilityStatus;

}
Ahora creemos una interfaz con la que trabajar y su implementación. Solo necesitaremos un método para trabajar con artículos:

Cliente JavaRushPost:


package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo;

import java.util.List;

/**
* Client for Javarush Open API corresponds to Posts.
*/
public interface JavaRushPostClient {

   /**
    * Find new posts since lastPostId in provided group.
    *
    * @param groupId provided group ID.
    * @param lastPostId provided last post ID.
    * @return the collection of the new {@link PostInfo}.
    */
   List<PostInfo> findNewPosts(Integer groupId, Integer lastPostId);
}
findNewPosts toma dos argumentos: el ID del grupo y el último ID del artículo que el bot ya publicó. Por lo tanto, se transmitirán todos aquellos artículos que fueron publicados con posterioridad al artículo con lastPostId . Y su implementación:

package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo;
import kong.unirest.GenericType;
import kong.unirest.Unirest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class JavaRushPostClientImpl implements JavaRushPostClient {

   private final String javarushApiPostPath;

   public JavaRushPostClientImpl(@Value("${javarush.api.path}") String javarushApi) {
       this.javarushApiPostPath = javarushApi + "/posts";
   }

   @Override
   public List<PostInfo> findNewPosts(Integer groupId, Integer lastPostId) {
       List<PostInfo> lastPostsByGroup = Unirest.get(javarushApiPostPath)
               .queryString("order", "NEW")
               .queryString("groupKid", groupId)
               .queryString("limit", 15)
               .asObject(new GenericType<List<PostInfo>>() {
               }).getBody();
       List<PostInfo> newPosts = new ArrayList<>();
       for (PostInfo post : lastPostsByGroup) {
           if (lastPostId.equals(post.getId())) {
               return newPosts;
           }
           newPosts.add(post);
       }
       return newPosts;
   }
}
Agregamos varios filtros a la solicitud:
  • orden = NUEVO - para que la lista contenga primero los nuevos;
  • groupKid = groupId: busca solo ciertos grupos;
  • límite = 15: limitamos el número de artículos por solicitud. Nuestra frecuencia es de 15 a 20 minutos y esperamos que durante este tiempo no se escriban MÁS de 15 (!).
A continuación, cuando hayamos encontrado artículos, revisamos la lista y buscamos otros nuevos. El algoritmo es simple e intuitivo. Si quieres mejorarlo escribe). Escribamos una prueba simple para este cliente:

package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;

import static com.github.javarushcommunity.jrtb.javarushclient.JavaRushGroupClientTest.JAVARUSH_API_PATH;

@DisplayName("Integration-level testing for JavaRushPostClient")
class JavaRushPostClientTest {

   private final JavaRushPostClient postClient = new JavaRushPostClientImpl(JAVARUSH_API_PATH);

   @Test
   public void shouldProperlyGetNew15Posts() {
       //when
       List<PostInfo> newPosts = postClient.findNewPosts(30, 2935);

       //then
       Assertions.assertEquals(15, newPosts.size());
   }
}
Esta es una prueba muy simple que verifica si existe alguna comunicación con el cliente o no. Encuentra 15 artículos nuevos en el grupo de proyectos Java, porque le doy el ID del primer artículo de este grupo, y ya hay más de 15... ¡Ya son 22! Ni siquiera pensé que habría tantos. ¿Cómo me enteré rápidamente? ¿Crees que fue a contarlos? No) Utilicé un swager y miré la cantidad de artículos de un grupo determinado. Por cierto, puedes lucir así en otros... ¿Y cuántos artículos hay en el grupo ALEATORIO?... Te lo digo ahora: ¡hay 1062! Cantidad seria.

Fin de la primera parte

Aquí hemos agregado trabajo con el cliente por artículo. Ya hemos hecho todo, esta vez creo que todo debería ser sencillo y rápido. En el próximo artículo agregaremos Spring Scheduler y escribiremos FindNewArticleService . Bueno, como siempre, dale me gusta, suscríbete, toca el timbre , dale una estrella a nuestro proyecto , escribe comentarios y califica el artículo. Gracias a todos por leer, ¡hasta pronto!

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