JavaRush /Java Blog /Random-IT /Stiamo aggiungendo la possibilità di iscriversi a un grup...
Roman Beekeeper
Livello 35

Stiamo aggiungendo la possibilità di iscriversi a un gruppo di articoli. (Parte 1) - "Progetto Java dalla A alla Z"

Pubblicato nel gruppo Random-IT
Ciao! Oggi aggiungeremo un abbonamento a un gruppo di articoli in JavaRush. Ciò corrisponde al problema JRTB-5 su GitHub. Mi spiego meglio: JavaRush ha una sezione chiamata Articoli e in essa sono presenti Gruppi di articoli. L'idea è quella di ricevere notifiche su un nuovo articolo da uno o più gruppi tramite un bot di Telegram.“Progetto Java dalla A alla Z”: aggiunta della possibilità di iscriversi a un gruppo di articoli.  Parte 1 - 1

Aggiungi JRTB-5

Diciamo che sono interessato agli articoli del gruppo Storie di successo . Pertanto, desidero iscrivermi agli aggiornamenti di questo gruppo e ricevere ogni volta un collegamento a una nuova pubblicazione. Come parte di questa attività, dobbiamo imparare come utilizzare l'API aperta per lavorare con i gruppi in JavaRush. Proprio in questo momento è arrivata una cosa del genere. Di seguito è riportato un collegamento a una descrizione dell'API aperta .
Amici! Vuoi sapere subito quando viene rilasciato un nuovo codice per un progetto o un nuovo articolo? Unisciti al mio canale tg . Lì raccolgo insieme i miei articoli, pensieri e sviluppo open source.

Cos'è la spavalderia? Scopriamolo adesso

Non abbiamo ancora parlato della spavalderia. Per chi non lo sapesse, lo spiego brevemente: questo è un posto dove puoi guardare apertamente l'API di un server e provare a fargli qualche richiesta. In genere, uno spavalderia raggruppa le possibili richieste. Nel nostro caso ci sono tre gruppi: forum-domanda , gruppo , post . In ogni gruppo ci saranno una o più richieste che indicheranno tutti i dati necessari per costruire questa richiesta (cioè quali parametri aggiuntivi possono essere passati, cosa farne, quale metodo http e così via). Vi consiglio di leggere e guardare di più su questo argomento, perché questa è la parte dello sviluppo che quasi ognuno di voi incontrerà. Per capirlo, scopriamo quanti gruppi ci sono in JavaRush. A tale scopo, espandere il gruppo group-controller e selezionare Ottieni richiesta /api/1.0/rest/groups/count . Ci restituirà il numero di gruppi in JavaRush. Diamo un'occhiata: “Progetto Java dalla A alla Z”: aggiunta della possibilità di iscriversi a un gruppo di articoli.  Parte 1 - 2l'immagine mostra che questa query supporta diversi parametri (query, tipo, filtro). Per provare questa richiesta, devi trovare il pulsante Provalo , dopodiché è possibile configurare questi parametri: “Progetto Java dalla A alla Z”: aggiunta della possibilità di iscriversi a un gruppo di articoli.  Parte 1 - 3Puoi anche configurare il tipo, il filtro e la query lì (è davvero interessante qui: questa sarà una ricerca di testo in un gruppo). Ma per ora eseguiamolo senza restrizioni e vediamo quanti gruppi ci sono in JavaRush. Per fare ciò, fare clic su Esegui. Subito sotto ci sarà una risposta (nella sezione Risposta del server) a questa richiesta: “Progetto Java dalla A alla Z”: aggiunta della possibilità di iscriversi a un gruppo di articoli.  Parte 1 - 4Vediamo che ci sono 30 gruppi in totale , questa richiesta è stata completata in 95ms e c'è una serie di alcune intestazioni nella risposta. Successivamente, proviamo a configurare alcuni parametri. Selezioniamo il parametro di tipo uguale al valore COMPANY e vediamo come cambia il risultato: “Progetto Java dalla A alla Z”: aggiunta della possibilità di iscriversi a un gruppo di articoli.  Parte 1 - 5Sono 4. Come verificarlo? È semplice: puoi andare sul sito, trovare la sezione degli articoli, selezionare tutti i gruppi e aggiungere lì il filtro appropriato ( https://javarush.com/groups/all?type=COMPANY ). E sì, in effetti ce ne sono solo 4. Anche se in realtà ce ne sono tre :D “Progetto Java dalla A alla Z”: aggiunta della possibilità di iscriversi a un gruppo di articoli.  Parte 1 - 6Fin qui tutto va bene. A proposito, se controlliamo le università, non ce ne sono ancora. Solo per divertimento, guarda cosa succede se imposti filter = MY in un browser in cui hai effettuato l'accesso a Javarush e non hai effettuato l'accesso. Maggiori informazioni sulla spavalderia - in questo articolo su Habré .

Scrittura di un client per l'API Javarush per gruppi

Ora, sulla base dell'API aperta, scriveremo un client Java in grado di effettuare richieste, ricevere risposte e sapere esattamente quali oggetti arriveranno. Prenderemo anche oggetti dalla spavalderia, dalla sezione Modelli (in fondo alla pagina). Creiamo un nuovo pacchetto e chiamiamolo javarushclient accanto a service, repository. In futuro, lo sposteremo in una libreria separata all'interno dell'organizzazione della comunità Javarush e lo utilizzeremo esclusivamente come dipendenza. Prima di tutto devi aggiungere Unitrest, una libreria per creare richieste http all'API JavaRush:
<dependency>
  <groupId>com.konghq</groupId>
  <artifactId>unirest-java</artifactId>
  <version>${unirest.version}</version>
</dependency>
E inserisci la versione nel blocco delle proprietà:
<unirest.version>3.11.01</unirest.version>
Una volta che abbiamo una dipendenza, possiamo iniziare ad aggiungere codice. Creiamo un client per i gruppi JavaRushGroupClient e un'implementazione nella classe JavaRushGroupClientImpl. Ma prima devi creare DTO (oggetti di trasferimento dati), ovvero classi i cui oggetti trasporteranno tutti i dati necessari per il client. Tutti i modelli possono essere visualizzati nello swagger. In fondo c'è una sezione Modelli , in cui puoi contarli. Ecco come appare GroupDiscussionInfo nello swagger: Nel pacchetto javarushclient, creeremo un “Progetto Java dalla A alla Z”: aggiunta della possibilità di iscriversi a un gruppo di articoli.  Parte 1 - 7pacchetto dto , al quale aggiungeremo, in base ai dati dello swagger, le seguenti classi:
  • Stato informazioni gruppo me :

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

  • Informazioni sul MeGroup :

    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;
    }

  • Tipo informazioni gruppo :

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

  • Informazioni sulla discussione utente :

    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;
    }

  • Stato visibilità gruppo :

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

  • Quindi - 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;
    }

Poiché GroupInfo e GroupDiscussionInfo sono quasi completamente uguali, colleghiamoli tramite ereditarietà - GroupDiscusionInfo :
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;
}
Avremo bisogno anche di un filtro per la richiesta GroupFilter :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

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

   UNKNOWN, MY, ALL
}
In una richiesta per ottenere l'ID, restituisce GroupDiscussionInfo e in una richiesta per una raccolta di gruppi, puoi ottenere sia GroupInfo che GroupDiscussionInfo. Poiché le richieste possono avere tipo, query, filtro, offset e limite, creiamo una classe GroupRequestArgs separata e rendiamola una classe di builder (leggi qual è il pattern di builder):
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;
   }
}
Per la ricerca del numero di gruppi, è leggermente diverso. Ha solo query, tipo e filtro. E sembrerebbe che tu non voglia duplicare il codice. Allo stesso tempo, se inizi a combinarli, risulta brutto quando lavori con i costruttori. Quindi ho deciso di separarli e ripetere il codice. Ecco come appare 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;
   }
}
Sì, non ho detto che le ultime due classi hanno un metodo populateQueries, che preparerà la mappa per creare una query (lo vedrai più tardi). Sulla base delle classi sopra descritte, creiamo un'interfaccia per 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);
}
Due diverse richieste per il caso in cui vogliamo aggiungere informazioni GroupInfo o GroupDiscussionInfo. Altrimenti, queste query sono identiche e l'unica differenza sarà che in una il flag includeDiscussion sarà vero e nell'altra sarà falso. Pertanto, c'erano 4 metodi, non tre. Ora iniziamo a implementare:
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();
   }


}
Aggiungo il percorso dell'API nel costruttore utilizzando l'annotazione Value già familiare. Implica che il valore all'interno dell'annotazione corrisponda a un campo nel file delle proprietà. Pertanto, aggiungiamo una nuova riga a application.properties:
javarush.api.path=https://javarush.com/api/1.0/rest
Questo valore sarà ora in un unico posto per tutti i client API e, se il percorso API cambia, lo aggiorneremo rapidamente. In precedenza, martellavo i chiodi con un microscopio, ricevevo una risposta da una richiesta http tramite Unirest, la traducevo in una stringa e poi analizzavo questa stringa tramite Jackson... Era spaventoso, noioso e richiedeva molte cose aggiuntive. In questa libreria puoi vedere come appare. Appena lo avrò tra le mani, rifattorizzerò tutto.
Chiunque volesse provare ad aggiornare questa libreria - aggiungere oggetti riceventi solo utilizzando gli strumenti della libreria unirest - scriva in un messaggio personale o come nuova uscita nella libreria stessa. Questa sarà una vera esperienza lavorativa per te, ma non mi dispiace. Condurrò una revisione completa del codice e aiuterò se necessario.
Ora la domanda è: il nostro codice funziona come ci aspettiamo? La risposta è semplice: devi solo scrivere dei test per loro. Come ho detto più di una volta, gli sviluppatori devono essere in grado di scrivere test. Pertanto, utilizzando la nostra interfaccia utente Swagger, invieremo richieste, esamineremo le risposte e le sostituiremo nei test come risultato previsto. Potresti aver notato subito che il numero di gruppi non è statico e può cambiare. E hai ragione. L'unica domanda è: quanto spesso cambia questo numero? Molto raramente, quindi nel corso di diversi mesi possiamo dire che questo valore sarà statico. E se qualcosa cambia, aggiorneremo i test. Incontra - 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());
   }
}
I test sono scritti nello stesso stile di prima. Ci sono diversi test per ogni richiesta. È inutile testare tutto, poiché penso che questa API sia già stata testata nel migliore dei modi.

Conclusione

Come parte di questo articolo, abbiamo aggiunto un client Java per gruppi all'API JavaRush. Come si suol dire, vivi e impara. Mentre scrivevo questo client, ho approfittato della loro documentazione e ho utilizzato convenientemente il lavoro con gli oggetti forniti. Attiro la vostra attenzione sul compito che ho proposto. Se qualcuno è interessato mi scriva un messaggio privato, sono più che sicuro che sarà un'esperienza molto interessante. Questa era la prima parte. Nella seconda implementeremo direttamente il comando di aggiunta e (se lo inseriamo in un articolo) lo aggiungeremo ottenendo un elenco di gruppi a cui è iscritto l'utente. Quindi, chiunque abbia il desiderio e il talento per scrivere testi per il bot, mi scriva in privato. Non sono un esperto in materia e qualsiasi aiuto sarebbe molto utile. Formalizziamo tutto questo come sviluppo open source, sarà interessante! Bene, come al solito: metti mi piace, iscriviti, suona la campana , dai una stella al nostro progetto , scrivi commenti e valuta l'articolo!
link utili

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