你好!今天我们将在 JavaRush 中添加一组文章的订阅。这对应于GitHub 上的问题 JRTB-5。让我解释一下:JavaRush 有一个名为 Articles 的部分,其中有文章组。这个想法是通过电报机器人从一个或多个组接收有关新文章的通知。
首先,您需要添加 Unitrest,一个用于向 JavaRush API 创建 http 请求的库:
用显微镜敲钉子,通过 Unirest 接收 http 请求的响应,将其翻译成字符串,然后通过 Jackson 解析这个字符串......这很可怕,很乏味,并且需要很多额外的东西。在这个库中你可以看到它的样子。一旦我掌握了它,我就会重构一切。
现在的问题是:我们的代码是否按照我们的预期工作?答案很简单:您只需要为它们编写测试即可。正如我不止一次说过的,开发人员必须能够编写测试。因此,使用我们的 Swagger UI,我们将发送请求,查看响应并将它们作为预期结果替换到测试中。您可能立即注意到组的数量不是静态的并且可以改变。你是对的。唯一的问题是这个数字多久变化一次?很少见,因此在几个月的过程中,我们可以说该值将是静态的。如果发生变化,我们将更新测试。认识 - JavaRushGroupClientTest:
就像,订阅,响铃,给我们的项目一颗星,写评论并评价文章!
添加JRTB-5
假设我对成功故事组的文章感兴趣。因此,我想订阅该组的更新,并每次都会收到新出版物的链接。作为此任务的一部分,我们需要学习如何使用开放 API 与 JavaRush 中的组一起工作。就在这时,这样的事情来了。这里是开放 API 描述的链接。朋友们!您想立即知道项目的新代码或新文章何时发布吗?加入我的tg频道。我在那里收集我的文章、想法和开源开发。 |
什么是招摇?现在让我们弄清楚
我们还没有讨论过大摇大摆的事情。对于那些不知道的人,我将简要解释一下:这是一个您可以公开查看服务器 API 并尝试向其发出一些请求的地方。通常,招摇者会对可能的请求进行分组。在我们的例子中,共有三个组:forum-question、group、post。在每一组中都会有一个或多个请求,指示构建此请求的所有必要数据(即可以传递哪些附加参数、如何处理它们、什么 http 方法等等)。我建议您更多地阅读和观看有关该主题的内容,因为这是几乎每个人都会遇到的开发部分。为了弄清楚这一点,我们来看看 JavaRush 中有多少个组。为此,请展开组控制器组并选择获取请求/api/1.0/rest/groups/count。它将返回 JavaRush 中的组数。让我们看一下:该图显示该查询支持多个参数(查询、类型、过滤器)。要尝试此请求,您需要找到“尝试一下”按钮,然后可以配置这些参数:您还可以在那里配置类型、过滤器和查询(这里实际上很有趣:这将是一个文本搜索团体)。但现在,让我们不加任何限制地运行它,看看 JavaRush 中有多少个组。为此,请单击“执行”。下面将有对此请求的响应(在“服务器响应”部分):我们看到总共有30 个组,该请求在 95 毫秒内完成,并且响应中有一组一些标头。接下来,我们尝试配置一些参数。我们选择与 COMPANY 值相等的类型参数,看看结果如何变化:共有 4 个,如何检查?这很简单:您可以访问该网站,找到文章部分,选择所有组并在其中添加适当的过滤器(https://javarush.com/groups/all?type=COMPANY)。是的,确实只有 4 个。虽然实际上有 3 个 :D到目前为止还算合适。顺便说一句,如果我们检查一下大学,还没有。只是为了好玩,看看如果您在登录 Javarush 和未登录的浏览器中设置 filter = MY 会发生什么。有关招摇的更多信息,请参见这篇关于哈布雷的文章。为组的 Javarush API 编写客户端
现在,基于开放 API,我们将编写一个 Java 客户端,它可以发出请求、接收响应并确切地知道哪些对象将到达。我们还将从“模型”部分(位于页面最底部)的swagger 中获取对象。让我们创建一个新包,并在服务、存储库旁边将其命名为 javarushclient。将来,我们会将其移至 Javarush 社区组织内的一个单独的库中,并将其专门用作依赖项。我已经在“为 Skyscanner API 创建客户端并将其发布到 jCenter 和 Maven Central 中的指南”一文中写了有关创建 Java 客户端的文章。 |
<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包,我们将根据 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; }
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 库的工具添加接收对象 - 写在个人消息中或作为库本身的新问题。这对你来说将是真正的工作经验,但我不介意。我将进行完整的代码审查并在必要时提供帮助。 |
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 客户端。正如他们所说,生活和学习。当我编写这个客户端时,我利用了他们的文档并方便地使用了他们提供的对象。我提请您注意我提出的任务。如果有人有兴趣,请给我留言,我非常确定这将是一次非常有趣的经历。这是第一部分。在第二个中,我们将直接实现添加命令,并且(如果我们将其放入一篇文章中)我们将添加获取用户订阅的组列表。接下来,任何有愿望和才华为机器人编写文本的人,请在私信中给我写信。我不是这方面的专家,任何帮助都会非常有帮助。让我们将这一切正式化为开源开发,这会很有趣!好吧,像往常一样 -有用的链接 |
---|
|
GO TO FULL VERSION