JavaRush /Blog Java /Random-FR /Nous ajoutons la possibilité de s'abonner à un groupe d'a...
Roman Beekeeper
Niveau 35

Nous ajoutons la possibilité de s'abonner à un groupe d'articles. (Partie 1) - "Projet Java de A à Z"

Publié dans le groupe Random-FR
Bonjour! Aujourd'hui, nous allons ajouter un abonnement à un groupe d'articles dans JavaRush. Cela correspond au problème JRTB-5 sur GitHub. Laissez-moi vous expliquer : JavaRush a une section appelée Articles , et elle contient des groupes d'articles. L'idée est de recevoir des notifications concernant un nouvel article d'un ou plusieurs groupes via un robot télégramme.« Projet Java de A à Z » : Ajout de la possibilité de s'abonner à un groupe d'articles.  Partie 1 - 1

Ajouter JRTB-5

Disons que je suis intéressé par les articles du groupe Success Stories . Par conséquent, je souhaite m'abonner aux mises à jour de ce groupe et recevoir à chaque fois un lien vers une nouvelle publication. Dans le cadre de cette tâche, nous devons apprendre à utiliser l'API ouverte pour travailler avec des groupes dans JavaRush. Juste à ce moment-là, une telle chose arriva. Voici un lien vers une description de l'API ouverte .
Amis! Voulez-vous savoir immédiatement quand un nouveau code pour un projet ou un nouvel article est publié ? Rejoignez ma chaîne tg . Là, je rassemble mes articles, mes réflexions et mon développement open source.

Qu’est-ce que le fanfaronnade ? Voyons cela maintenant

Nous n'avons pas encore parlé de fanfaronnade. Pour ceux qui ne le savent pas, je vais vous expliquer brièvement : c'est un endroit où vous pouvez ouvertement regarder l'API d'un serveur et essayer de lui faire quelques requêtes. Généralement, un fanfaron regroupe les demandes possibles. Dans notre cas, il existe trois groupes : forum-question , groupe , post . Dans chaque groupe, il y aura une ou plusieurs requêtes indiquant toutes les données nécessaires pour construire cette requête (c'est-à-dire quels paramètres supplémentaires peuvent être transmis, quoi en faire, quelle méthode http, etc.). Je vous conseille de lire et de regarder davantage sur ce sujet, car c'est la partie du développement que presque chacun d'entre vous rencontrera. Pour le comprendre, découvrons combien de groupes il existe dans JavaRush. Pour ce faire, développez le groupe group-controller et sélectionnez Get request /api/1.0/rest/groups/count . Cela nous renverra le nombre de groupes dans JavaRush. Regardons : « Projet Java de A à Z » : Ajout de la possibilité de s'abonner à un groupe d'articles.  Partie 1 - 2L'image montre que cette requête prend en charge plusieurs paramètres (requête, type, filtre). Pour tester cette requête, vous devez trouver le bouton Essayer , après quoi ces paramètres peuvent être configurés : « Projet Java de A à Z » : Ajout de la possibilité de s'abonner à un groupe d'articles.  Partie 1 à 3Vous pouvez également y configurer le type, le filtre et la requête (c'est en fait intéressant ici : ce sera une recherche de texte dans un groupe). Mais pour l’instant, exécutons-le sans aucune restriction et voyons combien de groupes il y a dans JavaRush. Pour ce faire, cliquez sur Exécuter. Juste en dessous il y aura une réponse (dans la section Réponse du serveur) à cette requête : « Projet Java de A à Z » : Ajout de la possibilité de s'abonner à un groupe d'articles.  Partie 1 à 4Nous voyons qu'il y a 30 groupes au total , cette requête a été complétée en 95 ms et il y a un ensemble de quelques en-têtes dans la réponse. Essayons ensuite de configurer certains paramètres. Sélectionnons le paramètre de type égal à la valeur COMPANY et voyons comment le résultat change : « Projet Java de A à Z » : Ajout de la possibilité de s'abonner à un groupe d'articles.  Partie 1 à 5il y en a 4. Comment vérifier cela ? C'est simple : vous pouvez aller sur le site Web, trouver la section article, sélectionner tous les groupes et y ajouter le filtre approprié ( https://javarush.com/groups/all?type=COMPANY ). Et oui, effectivement, il n'y en a que 4. Même s'il y en a en réalité trois :D « Projet Java de A à Z » : Ajout de la possibilité de s'abonner à un groupe d'articles.  Partie 1 à 6Pour l'instant, ça correspond. D’ailleurs, si l’on vérifie les universités, il n’y en a pas encore. Juste pour le plaisir, voyez ce qui se passe si vous définissez filter = MY dans un navigateur sur lequel vous êtes connecté à Javarush et non connecté. En savoir plus sur le fanfaronnade – dans cet article sur Habré .

Écrire un client pour l'API Javarush pour les groupes

Maintenant, sur la base de l'API ouverte, nous allons écrire un client Java capable de faire des requêtes, de recevoir des réponses et de savoir exactement quels objets arriveront. Nous reprendrons également des objets du swagger, de la section Modèles (tout en bas de la page). Créons un nouveau package et appelons-le javarushclient à côté de service, référentiel. À l'avenir, nous le déplacerons dans une bibliothèque distincte au sein de l'organisation de la communauté Javarush et l'utiliserons exclusivement comme dépendance. Tout d'abord, vous devez ajouter Unitrest, une bibliothèque permettant de créer des requêtes http vers l'API JavaRush :
<dependency>
  <groupId>com.konghq</groupId>
  <artifactId>unirest-java</artifactId>
  <version>${unirest.version}</version>
</dependency>
Et mettez la version dans le bloc propriétés :
<unirest.version>3.11.01</unirest.version>
Une fois que nous avons une dépendance, nous pouvons commencer à ajouter du code. Créons un client pour les groupes JavaRushGroupClient et une implémentation dans la classe JavaRushGroupClientImpl. Mais vous devez d'abord créer des DTO (objets de transfert de données), c'est-à-dire des classes dont les objets transporteront toutes les données nécessaires au client. Tous les modèles peuvent être consultés dans le swagger. Tout en bas, il y a une section Modèles , dans laquelle vous pouvez les compter. Voici à quoi ressemble GroupDiscussionInfo dans le swagger : Dans le package javarushclient, nous allons créer un « Projet Java de A à Z » : Ajout de la possibilité de s'abonner à un groupe d'articles.  Partie 1 à 7package dto , auquel nous ajouterons, en fonction des données du swagger, les classes suivantes :
  • StatutMeGroupInfo :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    /**
    * Member group status.
    */
    public enum MeGroupInfoStatus {
       UNKNOWN, CANDIDATE, INVITEE, MEMBER, EDITOR, MODERATOR, ADMINISTRATOR, BANNED
    }

  • MeGroupInfo :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    import lombok.Data;
    
    /**
    * Group information related to authorized user. If there is no user - will be null.
    */
    @Data
    public class MeGroupInfo {
       private MeGroupInfoStatus status;
       private Integer userGroupId;
    }

  • GroupeInfoType :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    /**
    * Group Info type;
    */
    public enum GroupInfoType {
       UNKNOWN, CITY, COMPANY, COLLEGE, TECH, SPECIAL, COUNTRY
    }

  • Informations sur la discussion utilisateur :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    import lombok.Data;
    
    /**
    * DTO for User discussion info.
    */
    @Data
    public class UserDiscussionInfo {
    
       private Boolean isBookmarked;
       private Integer lastTime;
       private Integer newCommentsCount;
    }

  • Statut de visibilité du groupe :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    /**
    * Group Visibility status.
    */
    public enum GroupVisibilityStatus {
       UNKNOWN, RESTRICTED, PUBLIC, PROTECTED, PRIVATE, DISABLED, DELETED
    }

  • Puis - GroupInfo :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    import lombok.Data;
    import lombok.ToString;
    
    /**
    * Group Info DTO class.
    */
    @Data
    @ToString
    public class GroupInfo {
    
       private Integer id;
       private String avatarUrl;
       private String createTime;
       private String description;
       private String key;
       private Integer levelToEditor;
       private MeGroupInfo meGroupInfo;
       private String pictureUrl;
       private String title;
       private GroupInfoType type;
       private Integer userCount;
       private GroupVisibilityStatus visibilityStatus;
    }

Puisque GroupInfo et GroupDiscussionInfo sont presque complètement identiques, lions-les en héritage - GroupDiscussionInfo :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

/**
* Group discussion info class.
*/
@EqualsAndHashCode(callSuper = true)
@Data
@ToString(callSuper = true)
public class GroupDiscussionInfo extends GroupInfo {

   private UserDiscussionInfo userDiscussionInfo;
   private Integer commentsCount;
}
Nous aurons également besoin d'un filtre pour la requête GroupFilter :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* Filters for group requests.
*/
public enum GroupFilter {

   UNKNOWN, MY, ALL
}
Dans une demande d'obtention par ID, il renvoie GroupDiscussionInfo, et dans une demande visant une collection de groupes, vous pouvez obtenir à la fois GroupInfo et GroupDiscussionInfo. Puisque les requêtes peuvent avoir un type, une requête, un filtre, un décalage et une limite, créons une classe GroupRequestArgs distincte et faisons-en une classe de générateur (lisez quel est le modèle de générateur) :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.*;

import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.nonNull;

/**
* Request arguments for group requests.
*/
@Builder
@Getter
public class GroupRequestArgs {

   private final String query;
   private final GroupInfoType type;
   private final GroupFilter filter;

   /**
    * specified where to start getting groups
    */
   private final Integer offset;
   /**
    * Limited number of groups.
    */
   private final Integer limit;

   public Map populateQueries() {
       Map queries = new HashMap<>();
       if(nonNull(query)) {
           queries.put("query", query);
       }
       if(nonNull(type)) {
           queries.put("type", type);
       }
       if(nonNull(filter)) {
           queries.put("filter", filter);
       }
       if(nonNull(offset)) {
           queries.put("offset", offset);
       }
       if(nonNull(limit)) {
           queries.put("limit", limit);
       }
       return queries;
   }
}
Pour rechercher le nombre de groupes, c’est légèrement différent. Il n'a qu'une requête, un type et un filtre. Et il semblerait que vous ne souhaitiez pas dupliquer le code. En même temps, si vous commencez à les combiner, cela s'avère moche lorsque vous travaillez avec des constructeurs. J'ai donc décidé de les séparer et de répéter le code. Voici à quoi ressemble GroupCountRequestArgs :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Builder;
import lombok.Getter;

import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.nonNull;

/**
* Request arguments for group count requests.
*/
@Builder
@Getter
public class GroupsCountRequestArgs {
   private final String query;
   private final GroupInfoType type;
   private final GroupFilter filter;

   public Map populateQueries() {
       Map queries = new HashMap<>();
       if (nonNull(query)) {
           queries.put("query", query);
       }
       if (nonNull(type)) {
           queries.put("type", type);
       }
       if (nonNull(filter)) {
           queries.put("filter", filter);
       }
       return queries;
   }
}
Oui, je n'ai pas mentionné que les deux dernières classes ont une méthode populateQueries, qui préparera la carte pour créer une requête (vous le verrez plus tard). Sur la base des classes décrites ci-dessus, créons une interface pour JavaRushGroupClient :
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs;

import java.util.List;

/**
 * Client for Javarush Open API corresponds to Groups.
 */
public interface JavaRushGroupClient {

    /**
     * Get all the {@link GroupInfo} filtered by provided {@link GroupRequestArgs}.
     *
     * @param requestArgs provided {@link GroupRequestArgs}.
     * @return the collection of the {@link GroupInfo} objects.
     */
    List<GroupInfo> getGroupList(GroupRequestArgs requestArgs);

    /**
     * Get all the {@link GroupDiscussionInfo} filtered by provided {@link GroupRequestArgs}.
     *
     * @param requestArgs provided {@link GroupRequestArgs}
     * @return the collection of the {@link GroupDiscussionInfo} objects.
     */
    List<GroupDiscussionInfo> getGroupDiscussionList(GroupRequestArgs requestArgs);

    /**
     * Get count of groups filtered by provided {@link GroupRequestArgs}.
     *
     * @param countRequestArgs provided {@link GroupsCountRequestArgs}.
     * @return the count of the groups.
     */
    Integer getGroupCount(GroupsCountRequestArgs countRequestArgs);

    /**
     * Get {@link GroupDiscussionInfo} by provided ID.
     *
     * @param id provided ID.
     * @return {@link GroupDiscussionInfo} object.
     */
    GroupDiscussionInfo getGroupById(Integer id);
}
Deux demandes différentes pour le cas où nous souhaitons ajouter des informations GroupInfo ou GroupDiscussionInfo. Sinon, ces requêtes sont identiques, et la seule différence sera que dans l'une, l'indicateur includeDiscussion sera vrai et dans l'autre, faux. Il y avait donc 4 méthodes et non trois. Commençons maintenant à implémenter :
package com.github.javarushcommunity.jrtb.javarushclient;

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

import java.util.List;

/**
* Implementation of the {@link JavaRushGroupClient} interface.
*/
@Component
public class JavaRushGroupClientImpl implements JavaRushGroupClient {

   private final String javarushApiGroupPath;

   public JavaRushGroupClientImpl(@Value("${javarush.api.path}") String javarushApi) {
       this.javarushApiGroupPath = javarushApi + "/groups";
   }

   @Override
   public List<GroupInfo> getGroupList(GroupRequestArgs requestArgs) {
       return Unirest.get(javarushApiGroupPath)
               .queryString(requestArgs.populateQueries())
               .asObject(new GenericType<list<GroupInfo>>() {
               })
               .getBody();
   }

   @Override
   public List<GroupDiscussionInfo> getGroupDiscussionList(GroupRequestArgs requestArgs) {
       return Unirest.get(javarushApiGroupPath)
               .queryString(requestArgs.populateQueries())
               .asObject(new GenericType<list<GroupDiscussionInfo>>() {
               })
               .getBody();
   }

   @Override
   public Integer getGroupCount(GroupsCountRequestArgs countRequestArgs) {
       return Integer.valueOf(
               Unirest.get(String.format("%s/count", javarushApiGroupPath))
                       .queryString(countRequestArgs.populateQueries())
                       .asString()
                       .getBody()
       );
   }

   @Override
   public GroupDiscussionInfo getGroupById(Integer id) {
       return Unirest.get(String.format("%s/group%s", javarushApiGroupPath, id.toString()))
               .asObject(GroupDiscussionInfo.class)
               .getBody();
   }


}
J'ajoute le chemin d'accès à l'API dans le constructeur en utilisant l'annotation Value déjà familière. Cela implique que la valeur à l'intérieur de l'annotation correspond à un champ du fichier de propriétés. Par conséquent, ajoutons une nouvelle ligne à application.properties :
javarush.api.path=https://javarush.com/api/1.0/rest
Cette valeur sera désormais au même endroit pour tous les clients API, et si le chemin de l'API change, nous le mettrons rapidement à jour. Auparavant, j'enfonçais des clous avec un microscope, recevais une réponse d'une requête http via Unirest, la traduisais en une chaîne puis analysais cette chaîne via Jackson... C'était effrayant, fastidieux et nécessitait beaucoup de choses supplémentaires. Dans cette bibliothèque, vous pouvez voir à quoi cela ressemble. Dès que j’aurai mis la main dessus, je refactoriserai tout.
Quiconque souhaite essayer de mettre à jour cette bibliothèque - ajouter des objets récepteurs uniquement en utilisant les outils de la bibliothèque unirest - écrit dans un message personnel ou comme nouveau numéro dans la bibliothèque elle-même. Ce sera une véritable expérience de travail pour vous, mais cela ne me dérange pas. Je procéderai à une révision complète du code et vous aiderai si nécessaire.
Maintenant, la question est : notre code fonctionne-t-il comme prévu ? La réponse est simple : il vous suffit de rédiger des tests pour eux. Comme je l'ai dit à plusieurs reprises, les développeurs doivent être capables d'écrire des tests. Par conséquent, en utilisant notre interface utilisateur Swagger, nous enverrons des requêtes, examinerons les réponses et les remplacerons dans les tests comme résultat attendu. Vous avez peut-être immédiatement remarqué que le nombre de groupes n’est pas statique et peut changer. Et tu as raison. La seule question est de savoir à quelle fréquence ce chiffre change-t-il ? Très rarement, donc sur plusieurs mois on peut dire que cette valeur sera statique. Et si quelque chose change, nous mettrons à jour les tests. Rencontrer - JavaRushGroupClientTest :
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs;
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.dto.GroupInfoType.TECH;

@DisplayName("Integration-level testing for JavaRushGroupClientImplTest")
class JavaRushGroupClientTest {

   private final JavaRushGroupClient groupClient = new JavaRushGroupClientImpl("https://javarush.com/api/1.0/rest");

   @Test
   public void shouldProperlyGetGroupsWithEmptyArgs() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder().build();

       //when
       List<GroupInfo> groupList = groupClient.getGroupList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertFalse(groupList.isEmpty());
   }

   @Test
   public void shouldProperlyGetWithOffSetAndLimit() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder()
               .offset(1)
               .limit(3)
               .build();

       //when
       List<GroupInfo> groupList = groupClient.getGroupList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertEquals(3, groupList.size());
   }

   @Test
   public void shouldProperlyGetGroupsDiscWithEmptyArgs() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder().build();

       //when
       List<GroupDiscussionInfo> groupList = groupClient.getGroupDiscussionList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertFalse(groupList.isEmpty());
   }

   @Test
   public void shouldProperlyGetGroupDiscWithOffSetAndLimit() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder()
               .offset(1)
               .limit(3)
               .build();

       //when
       List<GroupDiscussionInfo> groupList = groupClient.getGroupDiscussionList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertEquals(3, groupList.size());
   }

   @Test
   public void shouldProperlyGetGroupCount() {
       //given
       GroupsCountRequestArgs args = GroupsCountRequestArgs.builder().build();

       //when
       Integer groupCount = groupClient.getGroupCount(args);

       //then
       Assertions.assertEquals(30, groupCount);
   }

   @Test
   public void shouldProperlyGetGroupTECHCount() {
       //given
       GroupsCountRequestArgs args = GroupsCountRequestArgs.builder()
               .type(TECH)
               .build();

       //when
       Integer groupCount = groupClient.getGroupCount(args);

       //then
       Assertions.assertEquals(7, groupCount);
   }

   @Test
   public void shouldProperlyGetGroupById() {
       //given
       Integer androidGroupId = 16;

       //when
       GroupDiscussionInfo groupById = groupClient.getGroupById(androidGroupId);

       //then
       Assertions.assertNotNull(groupById);
       Assertions.assertEquals(16, groupById.getId());
       Assertions.assertEquals(TECH, groupById.getType());
       Assertions.assertEquals("android", groupById.getKey());
   }
}
Les tests sont rédigés dans le même style que précédemment. Il existe plusieurs tests pour chaque demande. Cela ne sert à rien de tout tester, puisque je pense que cette API a déjà été testée de la meilleure des manières.

Conclusion

Dans le cadre de cet article, nous avons ajouté un client Java pour les groupes à l'API JavaRush. Comme on dit, vivez et apprenez. Pendant que j'écrivais ce client, j'ai profité de leur documentation et j'ai facilement utilisé le travail avec les objets qu'ils fournissent. J'attire votre attention sur la tâche que j'ai proposée. Si quelqu'un est intéressé, écrivez-moi un message privé, je suis plus que sûr que ce sera une expérience très intéressante. C'était la première partie. Dans la seconde, nous implémenterons directement la commande d'ajout et (si nous l'intégrons dans un seul article) nous ajouterons l'obtention d'une liste de groupes auxquels l'utilisateur est abonné. Ensuite, toute personne ayant l'envie et le talent d'écrire des textes pour le bot, merci de m'écrire en MP. Je ne suis pas un expert en la matière et toute aide serait très utile. Formalisons tout ça en développement open source, ça va être intéressant ! Eh bien, comme d'habitude - aimez, abonnez-vous, activez la cloche , donnez une étoile à notre projet , écrivez des commentaires et notez l'article !
Liens utiles

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