JavaRush /Java Blog /Random-TW /我們正在新增訂閱一組文章的功能。(第 1 部分)-“Java 專案從頭到尾”
Roman Beekeeper
等級 35

我們正在新增訂閱一組文章的功能。(第 1 部分)-“Java 專案從頭到尾”

在 Random-TW 群組發布
你好!今天我們將在 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