JavaRush /Java Blog /Random-KO /기사에 클라이언트 추가 - "A부터 Z까지의 Java 프로젝트"
Roman Beekeeper
레벨 35

기사에 클라이언트 추가 - "A부터 Z까지의 Java 프로젝트"

Random-KO 그룹에 게시되었습니다
안녕하세요 여러분, 사랑하는 친구 여러분. 우리는 프로젝트의 MVP가 되는 JavaRush Telegram Bot이라는 목표에 단계적으로 가까워지고 있습니다. 지난 글에서 말했듯이 이제 과제가 5개밖에 안 남았네요. 오늘은 그 중 두 가지를 다루겠습니다. "A부터 Z까지의 Java 프로젝트": 기사에 클라이언트 추가 - 1이번 프로젝트는 여기서 끝나지 않을 것이라는 점을 거듭 말씀드리고 싶습니다. 나는 이 프로젝트가 어떻게 발전해야 하는지, 어떤 새로운 것을 추가할 수 있는지, 무엇을 더 잘할 수 있는지에 대한 많은 아이디어와 비전을 여전히 가지고 있습니다. MVP 이전에 리팩토링 주제, 즉 기능을 변경하지 않고 코드 품질을 향상시키는 방법에 대한 별도의 기사를 작성할 것입니다. 그때쯤이면 전체 프로젝트가 가시화되고 무엇을, 어디에서 개선할 수 있는지가 분명해질 것입니다. 우리의 경우에는 많은 테스트가 작성되었기 때문에 기능이 손상되지 않도록 최대한 보호할 것입니다. 우리는 또한 우리가 원했던 것과 결국 얻은 것에 대해 회고를 쓸 것입니다. 이것은 매우 유용한 것입니다. 6개월 전에 모든 것이 얼마나 정확하게 확인되었는지 살펴보겠습니다. 적어도 이것은 나에게 매우 흥미롭습니다. 수동 테스터로 시험해보고 싶은 사람이 있다면 우리에게 편지를 보내주시면 협력해 드리겠습니다. 이 프로젝트를 함께 더 좋게 만들어 봅시다! 여기에는 6개월 전에 설명된 두 가지 작업인 JRTB-8JRTB-9가 있습니다 . 저는 이러한 작업을 위해 구현해야 할 것이 무엇인지 살펴보기 시작했고, 명령 실행 측면에서 모든 것이 이미 준비되어 있다는 것을 깨달았습니다. 이런 일이 발생합니다...) 여기에서 실행 메소드 인 StartCommand 를 볼 수 있습니다 .
@Override
public void execute(Update update) {
   String chatId = update.getMessage().getChatId().toString();

   telegramUserService.findByChatId(chatId).ifPresentOrElse(
           user -> {
               user.setActive(true);
               telegramUserService.save(user);
           },
           () -> {
               TelegramUser telegramUser = new TelegramUser();
               telegramUser.setActive(true);
               telegramUser.setChatId(chatId);
               telegramUserService.save(telegramUser);
           });

   sendBotMessageService.sendMessage(chatId, START_MESSAGE);
}
여기서 논리는 작동합니다. 데이터베이스에 이미 chatId를 사용하는 사용자가 있는 경우 해당 사용자에 대해 active = true 필드를 설정하기만 하면 됩니다. 그런 사용자가 없으면 새 사용자를 만듭니다. StopCommand/stop 명령 과 동일합니다 .
@Override
public void execute(Update update) {
   telegramUserService.findByChatId(update.getMessage().getChatId().toString())
           .ifPresent(it -> {
               it.setActive(false);
               telegramUserService.save(it);
           });
   sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), STOP_MESSAGE);
}
이 명령을 호출하면 사용자에 대해 active = false 필드만 설정되는 것을 볼 수 있습니다. 그게 전부입니다. 사용자가 다시 봇과의 채팅을 활성화하기로 결정하면 구독이 활성화되고 대기하게 됩니다. 그리고 작업이 이미 완료되어 종료될 수 있는 것 같습니다. 그러나 그것은 거기에 없었습니다. 가장 중요한 작업은 구독의 새 기사에 대한 알림을 만드는 것입니다. 여기에서 이러한 작업이 완전히 업데이트되고 완료됩니다. 즉, 새 기사 알림을 구현하기 전까지는 닫을 수 없습니다. 따라서 JRTB-4 작업을 처리해 보겠습니다. 20분마다 확인을 생성하고 새 기사에 대한 알림을 생성합니다. 친구! 프로젝트의 새 코드가 공개되면 즉시 알고 싶으십니까? 새 기사는 언제 나오나요? 내 tg 채널에 가입하세요 . 그곳에서 나는 내 기사, 생각, 오픈 소스 개발을 함께 수집합니다.

JRTB-4를 구현합니다

이 작업의 일부로 수행해야 할 작업은 다음과 같습니다.
  1. 데이터베이스에 구독이 있는 모든 그룹을 주기적으로 방문하고, 기사를 게시 날짜별로 정렬하고, 마지막 게시의 ID가 GroupSub의 값과 일치하는지 확인하는 작업을 만듭니다. 일치하지 않는다면 지난번 이후 얼마나 많은 기사가 게재되었는지 정확히 파악해야 합니다. GroupSub7의 last_article_id를 현재 상태로 업데이트합니다.

  2. 게시된 기사 목록을 찾으면 해당 그룹의 모든 활성 사용자를 찾아 새 기사에 대한 알림을 보냅니다.

이를 위해 Spring Scheduler와 같은 것을 사용합니다. 이는 Spring Framework의 메커니즘으로, 이를 통해 특정 시간에 실행될 작업을 생성할 수 있습니다. 15-20-40분마다 또는 매주 목요일 15:30 또는 기타 옵션 중 하나를 선택할 수 있습니다. 영어 - joba에서 트레이싱 페이퍼라고도합니다. 이 작업을 수행하는 동안 새 기사 검색에서 의도적으로 한 가지 결함을 남겨 두겠습니다. 이는 매우 드물며 이 작업의 작동을 수동으로 테스트한 상황에서만 나타납니다. 이렇게 하려면 기사 검색을 위한 클라이언트를 작성해야 합니다. 이를 위해 이미 우리에게 친숙한 Swagger API를 사용하겠습니다 . 포스트 컨트롤러가 있습니다. 우리는 특정 필터를 사용하여 기사 모음을 검색하는 데에만 관심이 있습니다.
/api/1.0/rest/posts 필터로 게시물 가져오기
우리는 이 요청을 처리할 것입니다. 그 안에 무엇이 필요합니까? 특정 그룹에 속하는 기사 목록을 가져오며 발행 날짜별로 정렬해야 합니다. 이런 방식으로 우리는 지난 15개의 기사를 가져와 데이터베이스의 lastArticleId를 기반으로 새 출판물이 게시되었는지 확인할 수 있습니다 . 해당 사항이 있는 경우 해당 정보를 처리하여 사용자에게 전송합니다. 따라서 JavaRushPostClient 를 작성해야 합니다 .

우리는 JavaRushPostClient를 작성합니다.

여기서는 API로 전송된 모든 요청을 다루려고 하지 않고 필요한 요청만 생성하겠습니다. 이를 통해 우리는 한 번에 두 가지 목표를 달성합니다.
  1. 우리는 신청서 작성 과정을 가속화합니다.

  2. 우리는 이 작업을 커뮤니티를 돕고 싶어하고 개발자로서 스스로 노력하기로 결정한 사람들에게 맡깁니다. 이에 대해서는 MVP 이후에 완료할 수 있는 작업을 만들겠습니다.

그럼 해보자. Swagger UI 에서 모델 섹션을 쿼리하기 위해 다음 DTO를 만듭니다."A부터 Z까지의 Java 프로젝트": 기사에 클라이언트 추가 - 2

기본사용자정보:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Data;

/**
* DTO, which represents base user information.
*/
@Data
public class BaseUserInfo {
   private String city;
   private String country;
   private String displayName;
   private Integer id;
   private String job;
   private String key;
   private Integer level;
   private String pictureUrl;
   private String position;
   private UserPublicStatus publicStatus;
   private String publicStatusMessage;
   private Integer rating;
   private Integer userId;
}

언어:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* DTO, which represents languages.
*/
public enum Language {
   UNKNOWN,
   ENGLISH,
   GERMAN,
   SPANISH,
   HINDI,
   FRENCH,
   PORTUGUESE,
   POLISH,
   BENGALI,
   PUNJABI,
   CHINESE,
   ITALIAN,
   INDONESIAN,
   MARATHI,
   TAMIL,
   TELUGU,
   JAPANESE,
   KOREAN,
   URDU,
   TAIWANESE,
   NETHERLANDS,
   RUSSIAN,
   UKRAINIAN
}

좋아요정보:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* DTO, which represents like's information.
*/
public class LikesInfo {

   private Integer count;
   private LikeStatus status;
}

좋아요상태:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* DTO, which represents like's status.
*/
public enum LikeStatus {

   UNKNOWN,
   LIKE,
   HOT,
   FOLLOW,
   FAVORITE,
   SOLUTION,
   HELPFUL,
   ARTICLE,
   OSCAR,
   DISLIKE,
   WRONG,
   SPAM,
   ABUSE,
   FOUL,
   TROLLING,
   OFFTOPIC,
   DUPLICATE,
   DIRTY,
   OUTDATED,
   BORING,
   UNCLEAR,
   HARD,
   EASY,
   FAKE,
   SHAM,
   AWFUL
}

우편 유형:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* DTO, which represents post types.
*/
public enum PostType {
   UNKNOWN, USUAL, INNER_LINK, OUTER_LINK
}

사용자공개상태:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* DTO, which represents user public status.
*/
public enum UserPublicStatus {
   UNKNOWN,
   BEGINNER,
   ACTIVE,
   STRONG,
   GRADUATED,
   INTERNSHIP_IN_PROGRESS,
   INTERNSHIP_COMPLETED,
   RESUME_COMPLETED,
   LOOKING_FOR_JOB,
   HAVE_JOB;
}

VisibilityStatus:
package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* DTO, which represents visibility status.
*/
public enum VisibilityStatus {
   UNKNOWN,
   RESTRICTED,
   PUBLIC,
   PROTECTED,
   PRIVATE,
   DISABLED,
   DELETED
}
이러한 모든 DTO를 기반으로 기사를 수신할 기본 클래스를 작성해 보겠습니다.

게시물정보:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Data;

/**
* DTO, which represents post information.
*/
@Data
public class PostInfo {

   private BaseUserInfo authorInfo;
   private Integer commentsCount;
   private String content;
   private Long createdTime;
   private String description;
   private GroupInfo groupInfo;
   private Integer id;
   private String key;
   private Language language;
   private LikesInfo likesInfo;
   private GroupInfo originalGroupInfo;
   private String pictureUrl;
   private Double rating;
   private Integer ratingCount;
   private String title;
   private PostType type;
   private Long updatedTime;
   private UserDiscussionInfo userDiscussionInfo;
   private Integer views;
   private VisibilityStatus visibilityStatus;

}
이제 작업할 인터페이스와 그 구현을 만들어 보겠습니다. 기사 작업에는 한 가지 방법만 필요합니다.

JavaRushPost클라이언트:

package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo;

import java.util.List;

/**
* Client for Javarush Open API corresponds to Posts.
*/
public interface JavaRushPostClient {

   /**
    * Find new posts since lastPostId in provided group.
    *
    * @param groupId provided group ID.
    * @param lastPostId provided last post ID.
    * @return the collection of the new {@link PostInfo}.
    */
   List<PostInfo> findNewPosts(Integer groupId, Integer lastPostId);
}
findNewPosts는 그룹 ID와 봇이 이미 게시한 기사의 마지막 ID라는 두 가지 인수를 사용합니다. 따라서 lastPostId 가 있는 기사보다 나중에 게시된 모든 기사가 전송됩니다 . 구현은 다음과 같습니다.
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo;
import kong.unirest.GenericType;
import kong.unirest.Unirest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class JavaRushPostClientImpl implements JavaRushPostClient {

   private final String javarushApiPostPath;

   public JavaRushPostClientImpl(@Value("${javarush.api.path}") String javarushApi) {
       this.javarushApiPostPath = javarushApi + "/posts";
   }

   @Override
   public List<PostInfo> findNewPosts(Integer groupId, Integer lastPostId) {
       List<PostInfo> lastPostsByGroup = Unirest.get(javarushApiPostPath)
               .queryString("order", "NEW")
               .queryString("groupKid", groupId)
               .queryString("limit", 15)
               .asObject(new GenericType<List<PostInfo>>() {
               }).getBody();
       List<PostInfo> newPosts = new ArrayList<>();
       for (PostInfo post : lastPostsByGroup) {
           if (lastPostId.equals(post.getId())) {
               return newPosts;
           }
           newPosts.add(post);
       }
       return newPosts;
   }
}
요청에 여러 필터를 추가합니다.
  • order = NEW - 목록에 새 항목이 먼저 포함됩니다.
  • groupKid = groupId - 특정 그룹만 검색합니다.
  • 한도 = 15 - 요청당 기사 수를 제한합니다. 우리의 빈도는 15-20분이며 이 시간 동안 15(!) 이하가 기록될 것으로 예상합니다.
다음으로 기사를 찾으면 목록을 살펴보고 새로운 기사를 찾습니다. 알고리즘은 간단하고 직관적입니다. 개선하고 싶다면 작성하세요). 이 클라이언트에 대한 간단한 테스트를 작성해 보겠습니다.
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo;
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.JavaRushGroupClientTest.JAVARUSH_API_PATH;

@DisplayName("Integration-level testing for JavaRushPostClient")
class JavaRushPostClientTest {

   private final JavaRushPostClient postClient = new JavaRushPostClientImpl(JAVARUSH_API_PATH);

   @Test
   public void shouldProperlyGetNew15Posts() {
       //when
       List<PostInfo> newPosts = postClient.findNewPosts(30, 2935);

       //then
       Assertions.assertEquals(15, newPosts.size());
   }
}
클라이언트와의 통신이 전혀 이루어지고 있지 않은지 확인하는 매우 간단한 테스트입니다. 그는 Java 프로젝트 그룹에서 15개의 새 기사를 찾았습니다. 왜냐하면 내가 그에게 이 그룹의 첫 번째 기사 ID를 제공했고 그 중 이미 15개 이상이 있기 때문입니다... 이미 22개가 있습니다! 나는 그들이 그렇게 많을 것이라고 생각조차하지 않았습니다. 어떻게 빨리 알았지? 그가 숫자를 세러 갔다고 생각하시나요? 아니) 스웨거를 이용해 특정 그룹의 기사 수를 살펴봤습니다. 그건 그렇고, 다른 사람들에게서도 이렇게 보일 수 있습니다... 그리고 RANDOM 그룹에는 몇 개의 기사가 있습니까?... 지금 말씀드리겠습니다: 그 중 1062개가 있습니다! 심각한 금액입니다.

첫 번째 부분 끝

여기에 기사별로 고객과의 작업을 추가했습니다. 우리는 이미 모든 것을 해냈고, 이번에는 모든 것이 간단하고 빨라야 한다고 생각합니다. 다음 글 에서는 Spring Scheduler를 추가하고 FindNewArticleService를 작성하겠습니다 . 음, 평소와 같이 좋아요 - 구독 - 벨을 울리고 , 프로젝트에 별점을 주고 , 댓글을 쓰고 기사를 평가해 보세요! 읽어주셔서 감사합니다. 곧 뵙겠습니다!

시리즈의 모든 자료 목록은 이 기사의 시작 부분에 있습니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION