JavaRush /Blog Java /Random-PL /Dodajemy możliwość subskrypcji grupy artykułów. (Część 1)...
Roman Beekeeper
Poziom 35

Dodajemy możliwość subskrypcji grupy artykułów. (Część 1) - „Projekt Java od A do Z”

Opublikowano w grupie Random-PL
Cześć! Dziś dodamy subskrypcję grupy artykułów w JavaRush. Odpowiada to problemowi JRTB-5 w GitHub. Pozwólcie, że wyjaśnię: JavaRush ma sekcję o nazwie Artykuły , w której znajdują się Grupy Artykułów. Pomysł polega na tym, aby otrzymywać powiadomienia o nowym artykule z jednej lub większej liczby grup za pośrednictwem bota telegramu.„Projekt Java od A do Z”: Dodanie możliwości subskrypcji grupy artykułów.  Część 1 - 1

Dodaj JRTB-5

Załóżmy, że interesują mnie artykuły z grupy Historie Sukcesu . Dlatego chcę zapisać się na aktualizacje z tej grupy i za każdym razem otrzymywać link do nowej publikacji. W ramach tego zadania musimy nauczyć się wykorzystywać otwarte API do pracy z grupami w JavaRush. Właśnie w tym momencie coś takiego przyszło. Poniżej znajduje się link do opisu otwartego API .
Przyjaciele! Czy chcesz wiedzieć od razu, kiedy zostanie wydany nowy kod projektu lub nowy artykuł? Dołącz do mojego kanału tg . Gromadzę tam swoje artykuły, przemyślenia i rozwój oprogramowania open source.

Co to jest swagger? Rozwiążmy to teraz

Nie rozmawialiśmy jeszcze o szaleństwie. Tym, którzy nie wiedzą, krótko wyjaśnię: jest to miejsce, w którym można otwarcie przyjrzeć się API serwera i spróbować skierować do niego jakieś żądania. Zazwyczaj swagger grupuje możliwe żądania. W naszym przypadku są trzy grupy: forum-pytanie , grupa , post . W każdej grupie będzie jedno lub więcej żądań wskazujących wszystkie dane niezbędne do zbudowania tego żądania (czyli jakie dodatkowe parametry można przekazać, co z nimi zrobić, jaką metodę http i tak dalej). Radzę przeczytać i obejrzeć więcej na ten temat, ponieważ jest to część rozwoju, z którą spotka się prawie każdy z Was. Aby to rozgryźć, dowiedzmy się, ile grup jest w JavaRush. W tym celu rozwiń grupę group-controller i wybierz Get request /api/1.0/rest/groups/count . Zwróci nam liczbę grup w JavaRush. Spójrzmy: „Projekt Java od A do Z”: Dodanie możliwości subskrypcji grupy artykułów.  Część 1 - 2obraz pokazuje, że to zapytanie obsługuje kilka parametrów (zapytanie, typ, filtr). Aby wypróbować to żądanie, musisz znaleźć przycisk Wypróbuj , po czym można skonfigurować następujące parametry: „Projekt Java od A do Z”: Dodanie możliwości subskrypcji grupy artykułów.  Część 1 - 3Możesz tam także skonfigurować typ, filtr i zapytanie (tutaj jest to naprawdę interesujące: będzie to wyszukiwanie tekstowe w Grupa). Ale na razie uruchommy go bez żadnych ograniczeń i zobaczmy, ile grup jest w JavaRush. Aby to zrobić, kliknij Wykonaj. Tuż poniżej znajduje się odpowiedź (w sekcji Odpowiedź serwera) na to żądanie: „Projekt Java od A do Z”: Dodanie możliwości subskrypcji grupy artykułów.  Część 1 - 4Widzimy, że jest w sumie 30 grup , to żądanie zostało ukończone w ciągu 95 ms, a w odpowiedzi znajduje się zestaw kilku nagłówków. Następnie spróbujmy skonfigurować niektóre parametry. Wybierzmy parametr type równy wartości COMPANY i zobaczmy jak zmieni się wynik: „Projekt Java od A do Z”: Dodanie możliwości subskrypcji grupy artykułów.  Część 1 - 5Jest ich 4. Jak to sprawdzić? To proste: możesz wejść na stronę, znaleźć sekcję z artykułami, zaznaczyć wszystkie grupy i dodać tam odpowiedni filtr ( https://javarush.com/groups/all?type=COMPANY ). I tak, rzeczywiście jest ich tylko 4. Choć właściwie jest ich trzech :D „Projekt Java od A do Z”: Dodanie możliwości subskrypcji grupy artykułów.  Część 1 - 6Na razie się zgadza. Swoją drogą, jeśli sprawdzimy uniwersytety, to jeszcze ich nie ma. Dla zabawy zobacz, co się stanie, jeśli ustawisz filter = MY w przeglądarce, w której jesteś zalogowany do Javarush, ale nie jesteś zalogowany. Więcej o dumie - w artykule na temat Habré .

Napisanie klienta dla Javarush API dla grup

Teraz w oparciu o otwarte API napiszemy klienta Java, który może wysyłać żądania, otrzymywać odpowiedzi i dokładnie wie, jakie obiekty przyjdą. Pobierzemy także obiekty ze swaggera, z sekcji Modele (na samym dole strony). Stwórzmy nowy pakiet i nazwijmy go javarushclient obok usługi, repozytorium. W przyszłości przeniesiemy to do osobnej biblioteki w organizacji społeczności Javarush i będziemy używać jej wyłącznie jako zależności. Przede wszystkim musisz dodać Unitrest, bibliotekę do tworzenia żądań http do API JavaRush:
<dependency>
  <groupId>com.konghq</groupId>
  <artifactId>unirest-java</artifactId>
  <version>${unirest.version}</version>
</dependency>
I umieść wersję w bloku właściwości:
<unirest.version>3.11.01</unirest.version>
Gdy mamy już zależność, możemy zacząć dodawać kod. Stwórzmy klienta dla grup JavaRushGroupClient oraz implementację w klasie JavaRushGroupClientImpl. Najpierw jednak trzeba stworzyć DTO (obiekty transferu danych) – czyli klasy, których obiekty będą przenosiły wszystkie dane potrzebne klientowi. Wszystkie modele możesz obejrzeć w swaggerze.Na samym dole znajduje się sekcja Modele , w której możesz je policzyć. Tak wygląda GroupDiscussionInfo w swaggerze: W pakiecie javarushclient utworzymy „Projekt Java od A do Z”: Dodanie możliwości subskrypcji grupy artykułów.  Część 1 - 7pakiet dto , do którego dodamy na podstawie danych ze swaggera następujące klasy:
  • Stan informacji o grupie Me :

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

  • Informacje o mojej grupie :

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

  • Typ informacji o grupie :

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

  • Informacje o dyskusji użytkownika :

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

  • Stan widoczności grupy :

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

  • Następnie - 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;
    }

Ponieważ GroupInfo i GroupDiscussionInfo są prawie takie same, połączmy je w dziedziczeniu - 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;
}
Będziemy również potrzebować filtra dla żądania GroupFilter :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

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

   UNKNOWN, MY, ALL
}
W żądaniu uzyskania identyfikatora zwraca GroupDiscussionInfo, a w żądaniu zbioru grup można uzyskać zarówno GroupInfo, jak i GroupDiscussionInfo. Ponieważ żądania mogą mieć typ, zapytanie, filtr, offset i limit, utwórzmy osobną klasę GroupRequestArgs i uczyńmy ją klasą budującą (przeczytaj, jaki jest wzorzec konstruktora):
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;
   }
}
Nieco inaczej jest w przypadku wyszukiwania liczby grup. Ma tylko zapytanie, typ i filtr. I wygląda na to, że nie chcesz powielać kodu. Jednocześnie, jeśli zaczniesz je łączyć, podczas pracy z budowniczymi okaże się brzydkie. Postanowiłem więc je rozdzielić i powtórzyć kod. Tak wygląda 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;
   }
}
Tak, nie wspomniałem, że dwie ostatnie klasy posiadają metodę populateQueries, która przygotuje mapę do utworzenia zapytania (zobaczysz to później). W oparciu o klasy opisane powyżej utwórzmy interfejs dla 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);
}
Dwa różne żądania w przypadku, gdy chcemy uzyskać informacje GroupInfo lub GroupDiscussionInfo dodane. W przeciwnym razie żądania te są identyczne, a jedyną różnicą będzie to, że w jednym flaga includeDiscussion będzie miała wartość true, a w drugim będzie fałszywa. Zatem były 4 metody, a nie trzy. Teraz zacznijmy wdrażać:
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();
   }


}
Ścieżkę do API dodaję w konstruktorze korzystając ze znanej już adnotacji Value. Oznacza to, że wartość wewnątrz adnotacji odpowiada polu w pliku właściwości. Dlatego dodajmy nową linię do application.properties:
javarush.api.path=https://javarush.com/api/1.0/rest
Wartość ta będzie teraz w jednym miejscu dla wszystkich klientów API, a w przypadku zmiany ścieżki API szybko ją zaktualizujemy. Wcześniej wbijałem gwoździe pod mikroskopem, otrzymałem odpowiedź na żądanie http za pośrednictwem Unirest, przetłumaczyłem ją na ciąg znaków, a następnie przeanalizowałem ten ciąg przez Jacksona… To było przerażające, żmudne i wymagało wielu dodatkowych rzeczy. W tej bibliotece możesz zobaczyć jak to wygląda. Jak tylko wpadnie mi w ręce, wszystko przeanalizuję.
Każdy, kto chciałby spróbować zaktualizować tę bibliotekę - dodać obiekty odbierające wyłącznie przy pomocy narzędzi biblioteki unirest - pisać w wiadomości prywatnej lub jako nowy numer w samej bibliotece. To będzie dla ciebie prawdziwe doświadczenie zawodowe, ale nie mam nic przeciwko. Przeprowadzę pełny przegląd kodu i w razie potrzeby pomogę.
Teraz pytanie brzmi: czy nasz kod działa tak, jak tego oczekujemy? Odpowiedź jest prosta: wystarczy napisać dla nich testy. Jak już nie raz mówiłem, programiści muszą umieć pisać testy. Dlatego za pomocą naszego interfejsu użytkownika Swagger będziemy wysyłać żądania, przeglądać odpowiedzi i podstawiać je do testów jako oczekiwany wynik. Być może od razu zauważyłeś, że liczba grup nie jest stała i może się zmieniać. I masz rację. Pytanie tylko, jak często ta liczba się zmienia? Bardzo rzadko, bo na przestrzeni kilku miesięcy można powiedzieć, że wartość ta będzie stała. A jeśli coś się zmieni, zaktualizujemy testy. Poznaj - 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());
   }
}
Testy są napisane w tym samym stylu co poprzednio. Dla każdego żądania istnieje kilka testów. Nie ma sensu testować wszystkiego, bo uważam, że to API zostało już najlepiej przetestowane.

Wniosek

W ramach tego artykułu dodaliśmy klienta Java dla grup do API JavaRush. Jak to mówią, żyj i ucz się. Pisząc tego klienta korzystałem z jego dokumentacji i wygodnie korzystałem z pracy z obiektami, które udostępnia. Zwracam uwagę na zadanie, które zaproponowałem. Jeśli ktoś jest zainteresowany proszę napisać do mnie wiadomość prywatną, jestem więcej niż pewien, że będzie to bardzo ciekawe doświadczenie. To była pierwsza część. W drugim bezpośrednio zaimplementujemy polecenie dodawania i (jeśli zmieścimy to w jednym artykule) dodamy uzyskanie listy grup, do których użytkownik jest zapisany. Następnie każdego, kto ma chęć i talent do pisania tekstów na bota, proszę pisać do mnie na PW. Nie jestem ekspertem w tej kwestii i każda pomoc będzie bardzo pomocna. Sformalizujmy to wszystko jako rozwój open source, będzie interesująco! No cóż, jak zwykle - polub, subskrybuj, zadzwoń , daj gwiazdkę naszemu projektowi , napisz komentarz i oceń artykuł!
Przydatne linki

Lista wszystkich materiałów wchodzących w skład serii znajduje się na początku artykułu.

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