JavaRush /Java Blog /Random-TL /Nagdaragdag kami ng kakayahang mag-subscribe sa isang pan...

Nagdaragdag kami ng kakayahang mag-subscribe sa isang pangkat ng mga artikulo. (Bahagi 1) - "Proyekto ng Java mula A hanggang Z"

Nai-publish sa grupo
Kamusta! Ngayon ay magdaragdag kami ng isang subscription sa isang pangkat ng mga artikulo sa JavaRush. Ito ay tumutugma sa isyu ng JRTB-5 sa GitHub. Hayaan akong ipaliwanag: Ang JavaRush ay may isang seksyon na tinatawag na Mga Artikulo , at sa loob nito ay mayroong Mga Grupo ng Mga Artikulo. Ang ideya ay upang makatanggap ng mga abiso tungkol sa isang bagong artikulo mula sa isa o higit pang mga grupo sa pamamagitan ng isang telegram bot.“Java-проект от А до Я”: Добавляем возможность подписаться на группу статей. Часть 1 - 1

Magdagdag ng JRTB-5

Sabihin nating interesado ako sa mga artikulo mula sa grupong Success Stories . Samakatuwid, gusto kong mag-subscribe sa mga update mula sa pangkat na ito at makatanggap ng isang link sa isang bagong publikasyon sa bawat oras. Bilang bahagi ng gawaing ito, kailangan nating matutunan kung paano gamitin ang bukas na API para sa pagtatrabaho sa mga grupo sa JavaRush. Sa sandaling ito ay dumating ang ganoong bagay. Narito ang isang link sa isang paglalarawan ng bukas na API .
Kaibigan! Gusto mo bang malaman kaagad kung kailan inilabas ang bagong code para sa isang proyekto o isang bagong artikulo? Sumali sa aking tg channel . Doon ko kinokolekta ang aking mga artikulo, kaisipan at open-source na pag-unlad nang magkasama.

Ano ang swagger? Alamin natin ngayon

Про сваггер мы еще не говорor. Для тех, кто не знает, объясню вкратце: это место, где можно открыто посмотреть на API Howого-то serverа и попробовать выполнить Howие-то requestы к нему. Обычно в сваггере сгруппированы возможные requestы. В нашем случае есть три группы: forum-question, group, post. В каждой группе будет один и более requestов с указанием всех необходимых данных, чтобы этот request построить (то есть, Howие дополнительные параметры можно передать, что с ними делать, Howой http метод, и так далее). Советую почитать и посмотреть больше по этой теме, потому это та часть разработки, с которой столкнется почти каждый из вас. Whatбы разобраться, узнаем, Howое количество групп есть в JavaRush. Для этого раскроем группу group-controller и выберем Get request /api/1.0/rest/groups/count. Он вернет нам количество групп в JavaRush. Смотрим:“Java-проект от А до Я”: Добавляем возможность подписаться на группу статей. Часть 1 - 2На изображении видно, что этот request поддерживает несколько параметров (query, type, filter). Whatбы попробовать этот request на вкус, нужно найти кнопку Try it out, после чего эти параметры можно будет настроить:“Java-проект от А до Я”: Добавляем возможность подписаться на группу статей. Часть 1 - 3Также там можно настроить и тип, и фильтр, и кверю (здесь вообще интересно: это будет поиск по тексту в группе). Но пока запустим без Howих-либо ограничений и посмотрим, сколько всего групп в JavaRush. Для этого нажмем на Execute. Чуть ниже будет ответ (в секции Server Response) на этот request:“Java-проект от А до Я”: Добавляем возможность подписаться на группу статей. Часть 1 - 4Мы видим, что всего групп 30, request этот выполнился за 95ms и есть набор некоторых хедеров в ответе. Далее попробуем настроить Howие-то параметры. Выберем параметр type равным значению COMPANY и посмотрим, How изменится результат:“Java-проект от А до Я”: Добавляем возможность подписаться на группу статей. Часть 1 - 5Их 4. Как это проверить? Легко: можно пойти на сайт, найти секцию статьи, выбрать все группы и там добавить соответствующий фильтр (https://javarush.com/groups/all?type=COMPANY). И да, действительно, их всего 4. Хотя реально три :D“Java-проект от А до Я”: Добавляем возможность подписаться на группу статей. Часть 1 - 6Пока что сходится. К слову, если мы проверим университеты, то их пока что нет. Ради интереса посмотрите, что будет, если поставить filter = MY в браузере, где вы залогинены в Javarush и не залогинены. Больше о сваггере — в этой статье на Хабре.

Пишем клиента к Javarush API для групп

Теперь на основе открытого API напишем Java клиента, который умеет делать requestы, получать ответы и знает, Howие именно an objectы будут приходить. Объекты также возьмем из сваггера, из секции Models (в самом низу pages). Создадим новый пакет и назовем его javarushclient рядом с service, repository. В будущем мы это вынесем в отдельную библиотеку в рамках организации Javarush Community и будем использовать исключительно How зависимость. В первую очередь нужно добавить Unitrest — библиотеку для создания http requestов к JavaRush API:
<dependency>
  <groupId>com.konghq</groupId>
  <artifactId>unirest-java</artifactId>
  <version>${unirest.version}</version>
</dependency>
И выносим версию в блок properties:
<unirest.version>3.11.01</unirest.version>
Когда у нас появится зависимость, можно начинать добавлять code. Создадим клиент для групп JavaRushGroupClient и реализацию в класс JavaRushGroupClientImpl. Но сперва нужно создать DTO-шки (data transfer object) — то есть, классы, an objectы которых будут носить все нужные для клиента данные. Все модели можно посмотреть в сваггере, В самом низу есть секция Models, в которой можно их считать. Вот How выглядит GroupDiscussionInfo в сваггере:“Java-проект от А до Я”: Добавляем возможность подписаться на группу статей. Часть 1 - 7В пакете javarushclient создадим пакет dto, в который добавим, на основе данных из сваггера, следующие классы:
  • MeGroupInfoStatus:

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

  • GroupInfoType:

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

  • UserDiscussionInfo:

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

  • GroupVisibilitysdtatus:

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

  • Потом — 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;
    }

Так How GroupInfo и GroupDiscussionInfo почти fully совпадают, свяжем их в наследовании — 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;
}
Также нам понадобится фильтр для requestа GroupFilter:
package com.github.javarushcommunity.jrtb.javarushclient.dto;

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

   UNKNOWN, MY, ALL
}
В requestе на получение по ID-шнику выдает GroupDiscussionInfo, а в requestе на коллекцию групп можно получить How GroupInfo, так и GroupDiscussionInfo. Так How у requestов могут быть type, query, filter, offset и limit, создадим отдельный класс GroupRequestArgs и сделаем его классом-билдером (почитайте, что такое паттерн 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;
   }
}
Для поиска количества групп он несколько отличается. В нем есть только query, type и filter. И казалось бы, не хочется дублировать code. Вместе с тем, если начать их объединять, получается некрасиво в работе с билдерами. Поэтому я решил их разделить и сделать повторение codeа. Вот How выглядит 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;
   }
}
Да, я не упомянул, что в последних двух классах есть метод populateQueries, который подготовит мапу для создания requestа (увидите его далее). Основываясь на описанных выше классах, создадим интерфейс для 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);
}
Два разных requestа на случай, когда мы хотим получить информацию GroupInfo or GroupDiscussionInfo добавил. В остальном эти requestы тождественны, и разница будет лишь в том, что в одном флаг includeDiscussion будет true, а в другом — false. Поэтому вышло 4 метода, а не три. Теперь приступим к реализации:
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();
   }


}
Путь к АПИшке я добавляю в конструкторе, используя уже знакомую нам аннотацию Value. Она подразумевает, что meaning внутри аннотации соответствует полю в файле с пропертями. Поэтому добавим в application.properties новую строку:
javarush.api.path=https://javarush.com/api/1.0/rest
Теперь это meaning будет находиться в одном месте для всех клиентов АПИ, и если way to АПИ изменится, мы его быстро обновим. Раньше я забивал гвозди микроскопом получал ответ из http-requestа через Unirest, переводил его в строку и потом эту строку парсил через Jackson… Это было страшно, муторно и требовало многих дополнительных вещей. В этой библиотеке можно посмотреть, How это выглядит. Как дойдут руки — отрефакторю все.
У кого будет желание попробовать обновить эту библиотеку — добавить получение an objectов только при помощи инструментов библиотеки unirest — пишите в личку or How новое issue в самой библиотеке. Для вас это будет реальный опыт работы, а мне не жалко. Проведу полноценный code review и помогу, если нужно будет.
Теперь вопрос: а работает ли наш code так, How мы ожидаем? Ответить несложно: нужно просто написать тесты для них. Как я уже не раз говорил, разработчики должны уметь писать тесты. Поэтому, пользуясь нашим Swagger UI, будем отправлять requestы, смотреть ответы и подставлять их в тесты How ожидаемый результат. Вы могли сразу заметить, что количество групп не статическое и может меняться. И вы правы. Вопрос лишь в том, насколько часто это количество меняется? Очень редко, так что в разрезе нескольких месяцев можно утверждать, что это meaning будет статично. А если что-то изменится, обновим тесты. Встречайте — 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());
   }
}
Тесты написаны в таком же стиле, How и до этого. Есть по нескольку тестов на каждый request. Тестировать все не имеет смысла, так How я думаю, что этот АПИ и так уже оттестирован лучшим образом.

Вывод

В рамках этой статьи мы добавor Java клиента для групп к JavaRush API. Как говорится, век живи — век учись. Пока писал этого клиента, воспользовался их documentацией и с удобством использовал работу с an objectми, которую они предоставляют. Обращаю внимание на задачу, которую я предложил. Кому интересно — пишите личным сообщением, я более чем уверен, что это будет очень интересный опыт. Это была первая часть. Во второй мы реализуем непосредственно команду по добавлению и (если уложимся в одну статью) добавим получение списка групп, на которых пользователь подписан. Далее, у кого есть желание и талант к написанию текстов для бота, прошу написать мне в ЛС. Я не специалист в этом деле и любая помощь мне будет очень кстати. Оформим это все How разработку в open source, будет интересно! Ну и How обычно — лайк, подписка, колокольчик, ставь звезду нашему проекту, пиши комментарии и оценивай статью!
Полезные ссылки

Список всех материалов серии в начале этой статьи.

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