JavaRush /Java 博客 /Random-ZH /我们正在添加订阅一组文章的功能。(第 1 部分)-“Java 项目从头到尾”
Roman Beekeeper
第 35 级

我们正在添加订阅一组文章的功能。(第 1 部分)-“Java 项目从头到尾”

已在 Random-ZH 群组中发布
你好!今天我们将在 JavaRush 中添加一组文章的订阅。这对应于GitHub 上的问题 JRTB-5。让我解释一下:JavaRush 有一个名为 Articles 的部分,其中有文章组。这个想法是通过电报机器人从一个或多个组接收有关新文章的通知。“Java 项目从 A 到 Z”:添加订阅一组文章的功能。 第 1 - 1 部分

添加JRTB-5

假设我对成功故事组的文章感兴趣。因此,我想订阅该组的更新,并每次都会收到新出版物的链接。作为此任务的一部分,我们需要学习如何使用开放 API 与 JavaRush 中的组一起工作。就在这时,这样的事情来了。这里是开放 API 描述的链接
朋友们!您想立即知道项目的新代码或新文章何时发布吗?加入我的tg频道。我在那里收集我的文章、想法和开源开发。

什么是招摇?现在让我们弄清楚

我们还没有讨论过大摇大摆的事情。对于那些不知道的人,我将简要解释一下:这是一个您可以公开查看服务器 API 并尝试向其发出一些请求的地方。通常,招摇者会对可能的请求进行分组。在我们的例子中,共有三个组:forum-questiongrouppost。在每一组中都会有一个或多个请求,指示构建此请求的所有必要数据(即可以传递哪些附加参数、如何处理它们、什么 http 方法等等)。我建议您更多地阅读和观看有关该主题的内容,因为这是几乎每个人都会遇到的开发部分。为了弄清楚这一点,我们来看看 JavaRush 中有多少个组。为此,请展开组控制器组并选择获取请求/api/1.0/rest/groups/count。它将返回 JavaRush 中的组数。让我们看一下:“Java 项目从 A 到 Z”:添加订阅一组文章的功能。 第 1 - 2 部分该图显示该查询支持多个参数(查询、类型、过滤器)。要尝试此请求,您需要找到“尝试一下”按钮,然后可以配置这些参数:“Java 项目从 A 到 Z”:添加订阅一组文章的功能。 第 1 - 3 部分您还可以在那里配置类型、过滤器和查询(这里实际上很有趣:这将是一个文本搜索团体)。但现在,让我们不加任何限制地运行它,看看 JavaRush 中有多少个组。为此,请单击“执行”。下面将有对此请求的响应(在“服务器响应”部分):“Java 项目从 A 到 Z”:添加订阅一组文章的功能。 第 1 - 4 部分我们看到总共有30 个组,该请求在 95 毫秒内完成,并且响应中有一组一些标头。接下来,我们尝试配置一些参数。我们选择与 COMPANY 值相等的类型参数,看看结果如何变化:“Java 项目从 A 到 Z”:添加订阅一组文章的功能。 第 1 - 5 部分共有 4 个,如何检查?这很简单:您可以访问该网站,找到文章部分,选择所有组并在其中添加适当的过滤器(https://javarush.com/groups/all?type=COMPANY)。是的,确实只有 4 个。虽然实际上有 3 个 :D“Java 项目从 A 到 Z”:添加订阅一组文章的功能。 第 1 - 6 部分到目前为止还算合适。顺便说一句,如果我们检查一下大学,还没有。只是为了好玩,看看如果您在登录 Javarush 和未登录的浏览器中设置 filter = MY 会发生什么。有关招摇的更多信息,请参见这篇关于哈布雷的文章

为组的 Javarush API 编写客户端

现在,基于开放 API,我们将编写一个 Java 客户端,它可以发出请求、接收响应并确切地知道哪些对象将到达。我们还将从“模型”部分(位于页面最底部)的swagger 中获取对象。让我们创建一个新包,并在服务、存储库旁边将其命名为 javarushclient。将来,我们会将其移至 Javarush 社区组织内的一个单独的库中,并将其专门用作依赖项。 首先,您需要添加 Unitrest,一个用于向 JavaRush API 创建 http 请求的库:
<dependency>
  <groupId>com.konghq</groupId>
  <artifactId>unirest-java</artifactId>
  <version>${unirest.version}</version>
</dependency>
并将版本放入属性块中:
<unirest.version>3.11.01</unirest.version>
一旦我们有了依赖关系,我们就可以开始添加代码。让我们为 JavaRushGroupClient 组创建一个客户端并在 JavaRushGroupClientImpl 类中创建一个实现。但首先您需要创建 DTO(数据传输对象),即其对象将携带客户端所需的所有数据的类。所有模型都可以在swagger中查看,最底部有一个模型部分,您可以在其中统计模型。这就是GroupDiscussionInfo在 swagger 中的样子:在 javarushclient 包中,我们将创建一个dto“Java 项目从 A 到 Z”:添加订阅一组文章的功能。 第 1 - 7 部分包,我们将根据 swagger 中的数据向其中添加以下类:
  • 我组信息状态

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

  • 我的组信息

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

  • 组信息类型

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

  • 用户讨论信息

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

  • 组可见性状态

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

由于GroupInfoGroupDiscussionInfo几乎完全相同,因此让我们通过继承将它们链接起来 - 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;
}
我们还需要一个用于GroupFilter请求的过滤器:
package com.github.javarushcommunity.jrtb.javarushclient.dto;

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

   UNKNOWN, MY, ALL
}
在通过ID获取的请求中,它返回GroupDiscussionInfo,在获取群组集合的请求中,您可以同时获取GroupInfo和GroupDiscussionInfo。由于请求可以具有类型、查询、过滤器、偏移量和限制,因此让我们创建一个单独的GroupRequestArgs类并将其设为构建器类(了解构建器模式是什么):
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;
   }
}
对于搜索组的数量,情况略有不同。它只有查询、类型和过滤器。而且您似乎不想重复代码。同时,如果你开始将它们组合起来,那么与构建者合作时就会变得很丑陋。所以我决定将它们分开并重复代码。这就是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 方法,它将准备用于创建查询的地图(稍后您会看到)。基于上面描述的类,让我们为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);
}
当我们想要获取添加的 GroupInfo 或 GroupDiscussionInfo 信息时,有两种不同的请求。否则,这些查询是相同的,唯一的区别是在一个查询中 includeDiscussion 标志为 true,而在另一个查询中则为 false。因此,有4种方法,而不是3种。现在让我们开始实施:
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 注释在构造函数中添加 API 的路径。这意味着注释内的值对应于属性文件中的字段。因此,让我们在 application.properties 中添加新行:
javarush.api.path=https://javarush.com/api/1.0/rest
现在,所有 API 客户端的该值都位于一个位置,如果 API 路径发生变化,我们将快速更新它。以前,我用显微镜敲钉子,通过 Unirest 接收 http 请求的响应,将其翻译成字符串,然后通过 Jackson 解析这个字符串......这很可怕,很乏味,并且需要很多额外的东西。在这个中你可以看到它的样子。一旦我掌握了它,我就会重构一切。
任何想要尝试更新此库的人 - 仅使用 unirest 库的工具添加接收对象 - 写在个人消息中或作为库本身的新问题。这对你来说将是真正的工作经验,但我不介意。我将进行完整的代码审查并在必要时提供帮助。
现在的问题是:我们的代码是否按照我们的预期工作?答案很简单:您只需要为它们编写测试即可。正如我不止一次说过的,开发人员必须能够编写测试。因此,使用我们的 Swagger UI,我们将发送请求,查看响应并将它们作为预期结果替换到测试中。您可能立即注意到组的数量不是静态的并且可以改变。你是对的。唯一的问题是这个数字多久变化一次?很少见,因此在几个月的过程中,我们可以说该值将是静态的。如果发生变化,我们将更新测试。认识 - 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());
   }
}
测试的编写风格与以前相同。每个请求都有多个测试。测试所有的东西是没有意义的,因为我认为这个 API 已经以最好的方式进行了测试。

结论

作为本文的一部分,我们向 JavaRush API 添加了一个用于组的 Java 客户端。正如他们所说,生活和学习。当我编写这个客户端时,我利用了他们的文档并方便地使用了他们提供的对象。我提请您注意我提出的任务。如果有人有兴趣,请给我留言,我非常确定这将是一次非常有趣的经历。这是第一部分。在第二个中,我们将直接实现添加命令,并且(如果我们将其放入一篇文章中)我们将添加获取用户订阅的组列表。接下来,任何有愿望和才华为机器人编写文本的人,请在私信中给我写信。我不是这方面的专家,任何帮助都会非常有帮助。让我们将这一切正式化为开源开发,这会很有趣!好吧,像往常一样 -就像,订阅,响铃,给我们的项目一颗星,写评论并评价文章!
有用的链接

该系列所有材料的列表位于本文开头。

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