Olá! Hoje adicionaremos uma assinatura a um grupo de artigos no JavaRush. Isso corresponde ao problema JRTB-5 no GitHub. Deixa eu explicar: JavaRush tem uma seção chamada Artigos , e nela existem Grupos de Artigos. A ideia é receber notificações sobre um novo artigo de um ou mais grupos por meio de um bot de telegram.
Primeiro de tudo, você precisa adicionar Unitrest, uma biblioteca para criar solicitações http para a API JavaRush:
martelava pregos com um microscópio, recebia uma resposta de uma solicitação http via Unirest, traduzia em uma string e depois analisava essa string por meio de Jackson... Era assustador, tedioso e exigia muitas coisas adicionais. Nesta biblioteca você pode ver como é. Assim que eu colocar as mãos nele, refatorarei tudo.
Agora a questão é: nosso código funciona como esperamos? A resposta é fácil: você só precisa escrever testes para eles. Como já disse mais de uma vez, os desenvolvedores devem ser capazes de escrever testes. Portanto, utilizando nossa UI Swagger, enviaremos solicitações, analisaremos as respostas e as substituiremos nos testes conforme o resultado esperado. Você deve ter notado imediatamente que o número de grupos não é estático e pode mudar. E você está certo. A única questão é com que frequência esse número muda? Muito raramente, pelo que ao longo de vários meses podemos dizer que este valor será estático. E se algo mudar, atualizaremos os testes. Conheça - JavaRushGroupClientTest:
curta, inscreva-se, ligue , dê uma estrela ao nosso projeto , escreva comentários e avalie o artigo!
Adicionar JRTB-5
Digamos que estou interessado em artigos do grupo Histórias de Sucesso . Portanto, quero me inscrever nas atualizações deste grupo e sempre receber um link para uma nova publicação. Como parte desta tarefa, precisamos aprender como usar a API aberta para trabalhar com grupos no JavaRush. Justo neste momento tal coisa chegou. Aqui está um link para uma descrição da API aberta .Amigos! Quer saber imediatamente quando um novo código para um projeto ou um novo artigo for lançado? Entre no meu canal tg . Lá eu reúno meus artigos, pensamentos e desenvolvimento de código aberto. |
O que é arrogância? Vamos descobrir agora
Ainda não falamos sobre a arrogância. Para quem não sabe, vou explicar brevemente: este é um lugar onde você pode olhar abertamente a API de um servidor e tentar fazer algumas requisições a ele. Normalmente, um swagger agrupa possíveis solicitações. No nosso caso, existem três grupos: forum-question , group , post . Em cada grupo haverá uma ou mais solicitações indicando todos os dados necessários para construir esta solicitação (ou seja, quais parâmetros adicionais podem ser passados, o que fazer com eles, qual método http e assim por diante). Aconselho vocês a lerem e observarem mais sobre esse assunto, pois essa é a parte do desenvolvimento que quase todos vocês encontrarão. Para descobrir, vamos descobrir quantos grupos existem no JavaRush. Para fazer isso, expanda o grupo group-controller e selecione Get request /api/1.0/rest/groups/count . Ele nos retornará o número de grupos no JavaRush. Vejamos: A imagem mostra que esta consulta suporta vários parâmetros (consulta, tipo, filtro). Para testar esta solicitação, você precisa encontrar o botão Experimente , após o qual estes parâmetros podem ser configurados: Você também pode configurar o tipo, o filtro e a consulta (na verdade é interessante aqui: esta será uma pesquisa de texto em um grupo). Mas, por enquanto, vamos executá-lo sem quaisquer restrições e ver quantos grupos existem no JavaRush. Para fazer isso, clique em Executar. Logo abaixo haverá uma resposta (na seção Resposta do Servidor) para esta solicitação: Vemos que existem 30 grupos no total , esta solicitação foi concluída em 95ms e há um conjunto de alguns cabeçalhos na resposta. A seguir, vamos tentar configurar alguns parâmetros. Vamos selecionar o parâmetro de tipo igual ao valor COMPANY e ver como o resultado muda: São 4. Como verificar isso? É fácil: você pode acessar o site, encontrar a seção de artigos, selecionar todos os grupos e adicionar o filtro apropriado lá ( https://javarush.com/groups/all?type=COMPANY ). E sim, de fato, existem apenas 4. Embora na verdade existam três :D Até agora cabe. Aliás, se verificarmos as universidades, ainda não há nenhuma. Apenas por diversão, veja o que acontece se você definir filter = MY em um navegador onde você está logado no Javarush e não logado. Mais sobre a arrogância - neste artigo sobre Habré .Escrevendo um cliente para a API Javarush para grupos
Agora, com base na API aberta, escreveremos um cliente Java que pode fazer solicitações, receber respostas e saber exatamente quais objetos chegarão. Também pegaremos objetos do swagger, da seção Modelos (no final da página). Vamos criar um novo pacote e chamá-lo de javarushclient próximo ao serviço, repositório. No futuro, moveremos isso para uma biblioteca separada dentro da organização da comunidade Javarush e a usaremos exclusivamente como uma dependência.Já escrevi sobre a criação de clientes Java no artigo “Guia para criar um cliente para a API Skyscanner e publicá-lo no jCenter e Maven Central” . |
<dependency>
<groupId>com.konghq</groupId>
<artifactId>unirest-java</artifactId>
<version>${unirest.version}</version>
</dependency>
E coloque a versão no bloco de propriedades:
<unirest.version>3.11.01</unirest.version>
Assim que tivermos uma dependência, podemos começar a adicionar código. Vamos criar um cliente para grupos JavaRushGroupClient e uma implementação na classe JavaRushGroupClientImpl. Mas primeiro você precisa criar DTOs (objetos de transferência de dados) - ou seja, classes cujos objetos transportarão todos os dados necessários para o cliente. Todos os modelos podem ser visualizados no swagger.Na parte inferior há uma seção Modelos , na qual você pode contá-los. Esta é a aparência de GroupDiscussionInfo no swagger: No pacote javarushclient, criaremos um pacote dto , ao qual adicionaremos, com base nos dados do swagger, as seguintes classes:
-
MeGroupInfoStatus :
package com.github.javarushcommunity.jrtb.javarushclient.dto; /** * Member group status. */ public enum MeGroupInfoStatus { UNKNOWN, CANDIDATE, INVITEE, MEMBER, EDITOR, MODERATOR, ADMINISTRATOR, BANNED }
-
Informações do meu grupo :
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 }
-
Informações sobre discussão do usuário :
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; }
-
Status de visibilidade do grupo :
package com.github.javarushcommunity.jrtb.javarushclient.dto; /** * Group Visibility status. */ public enum GroupVisibilityStatus { UNKNOWN, RESTRICTED, PUBLIC, PROTECTED, PRIVATE, DISABLED, DELETED }
-
Então - 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; }
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;
}
Também precisaremos de um filtro para a solicitação GroupFilter :
package com.github.javarushcommunity.jrtb.javarushclient.dto;
/**
* Filters for group requests.
*/
public enum GroupFilter {
UNKNOWN, MY, ALL
}
Em uma solicitação para obter por ID, ele retorna GroupDiscussionInfo, e em uma solicitação para uma coleção de grupos, você pode obter GroupInfo e GroupDiscussionInfo. Como as solicitações podem ter tipo, consulta, filtro, deslocamento e limite, vamos criar uma classe GroupRequestArgs separada e torná-la uma classe construtora (leia qual é o padrão do construtor):
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;
}
}
Para pesquisar o número de grupos, é um pouco diferente. Possui apenas consulta, tipo e filtro. E parece que você não deseja duplicar o código. Ao mesmo tempo, se você começar a combiná-los, ficará feio ao trabalhar com construtores. Então resolvi separá-los e repetir o código. Esta é a aparência de 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;
}
}
Sim, não mencionei que as duas últimas classes possuem um método populateQueries, que preparará o mapa para a criação de uma consulta (você verá mais tarde). Com base nas classes descritas acima, vamos criar uma interface para 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);
}
Duas solicitações diferentes para o caso em que desejamos adicionar informações de GroupInfo ou GroupDiscussionInfo. Caso contrário, essas consultas serão idênticas, e a única diferença será que em uma o sinalizador includeDiscussion será verdadeiro e na outra será falso. Portanto, havia 4 métodos, e não três. Agora vamos começar a implementar:
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();
}
}
Eu adiciono o caminho para a API no construtor usando a já familiar anotação Value. Implica que o valor dentro da anotação corresponde a um campo no arquivo de propriedades. Portanto, vamos adicionar uma nova linha em application.properties:
javarush.api.path=https://javarush.com/api/1.0/rest
Este valor agora estará em um só lugar para todos os clientes da API e, se o caminho da API mudar, iremos atualizá-lo rapidamente. Anteriormente, eu Quem quiser tentar atualizar esta biblioteca - adicionar objetos receptores apenas utilizando as ferramentas da biblioteca unirest - escreva em mensagem pessoal ou como novo fascículo na própria biblioteca. Esta será uma experiência de trabalho real para você, mas não me importo. Conduzirei uma revisão completa do código e ajudarei se necessário. |
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());
}
}
Os testes são escritos no mesmo estilo de antes. Existem vários testes para cada solicitação. Não adianta testar tudo, pois acho que essa API já foi testada da melhor forma.
Conclusão
Como parte deste artigo, adicionamos um cliente Java para grupos à API JavaRush. Como se costuma dizer, viva e aprenda. Enquanto escrevia este cliente, aproveitei sua documentação e usei convenientemente o trabalho com os objetos que eles fornecem. Chamo sua atenção para a tarefa que propus. Se alguém estiver interessado, escreva-me uma mensagem privada, tenho a certeza que será uma experiência muito interessante. Esta foi a primeira parte. No segundo, implementaremos diretamente o comando de adição e (se cabermos em um artigo) adicionaremos obtendo uma lista de grupos nos quais o usuário está inscrito. A seguir, quem tiver vontade e talento para escrever textos para o bot, por favor me escreva por PM. Não sou especialista no assunto e qualquer ajuda seria muito útil. Vamos formalizar tudo isso como desenvolvimento open source, vai ser interessante! Bem, como sempre -Links Úteis |
---|
|
GO TO FULL VERSION