JavaRush /Java Blog /Random-KO /Spring Scheduler 추가 - "A부터 Z까지 Java 프로젝트"
Roman Beekeeper
레벨 35

Spring Scheduler 추가 - "A부터 Z까지 Java 프로젝트"

Random-KO 그룹에 게시되었습니다
안녕하세요 여러분, 사랑하는 친구 여러분. 이전 기사 에서는 기사용 JavaRush API 작업을 위한 클라이언트를 준비했습니다. 이제 15분마다 실행될 작업에 대한 로직을 작성할 수 있습니다. 이 다이어그램에 표시된 것과 정확히 같습니다. “Java 프로젝트 A부터 Z까지”: Spring Scheduler 추가 - 115분마다 작업이 시작됩니다(우리 생각에는 특정 클래스의 메서드일 뿐임). 이 작업은 기본 애플리케이션의 백그라운드에서 실행되고 다음을 수행합니다.
  1. 이전 실행 이후에 게시된 새 기사를 데이터베이스에 있는 모든 그룹에서 찾습니다.

    이 체계는 더 적은 수의 그룹을 지정합니다. 활성 사용자가 있는 그룹만 지정합니다. 그 당시에는 그것이 논리적으로 보였지만 이제는 특정 그룹에 가입한 활성 사용자가 있는지 여부에 관계없이 봇이 처리한 최신 기사를 최신 상태로 유지해야 한다는 것을 이해합니다. 새로운 사용자가 이 그룹이 비활성화된 이후 게시된 전체 기사 수를 즉시 받는 경우 상황이 발생할 수 있습니다. 이는 예상된 동작이 아니며 이를 방지하려면 현재 활성 사용자가 없는 데이터베이스의 그룹을 최신 상태로 유지해야 합니다.
  2. 새 기사가 있으면 이 그룹을 적극적으로 구독하는 모든 사용자에 대한 메시지를 생성하십시오. 새 기사가 없으면 간단히 작업을 완료합니다.

그건 그렇고, 나는 이미 내 TG 채널에서 봇이 이미 작동하고 있으며 구독을 기반으로 새 기사를 보내고 있다고 언급했습니다 . FindNewArtcileService 작성을 시작해 보겠습니다 . 메시지를 검색하고 보내는 모든 작업이 여기에서 이루어지며 작업은 이 서비스의 메서드만 시작합니다.

새기사찾기서비스:

package com.github.javarushcommunity.jrtb.service;

/**
* Service for finding new articles.
*/
public interface FindNewArticleService {

   /**
    * Find new articles and notify subscribers about it.
    */
   void findNewArticles();
}
아주 간단하죠? 이것이 본질이며 모든 어려움은 구현에 있습니다.
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.javarushclient.JavaRushPostClient;
import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo;
import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class FindNewArticleServiceImpl implements FindNewArticleService {

   public static final String JAVARUSH_WEB_POST_FORMAT = "https://javarush.com/groups/posts/%s";

   private final GroupSubService groupSubService;
   private final JavaRushPostClient javaRushPostClient;
   private final SendBotMessageService sendMessageService;

   @Autowired
   public FindNewArticleServiceImpl(GroupSubService groupSubService,
                                    JavaRushPostClient javaRushPostClient,
                                    SendBotMessageService sendMessageService) {
       this.groupSubService = groupSubService;
       this.javaRushPostClient = javaRushPostClient;
       this.sendMessageService = sendMessageService;
   }


   @Override
   public void findNewArticles() {
       groupSubService.findAll().forEach(gSub -> {
           List<PostInfo> newPosts = javaRushPostClient.findNewPosts(gSub.getId(), gSub.getLastArticleId());

           setNewLastArticleId(gSub, newPosts);

           notifySubscribersAboutNewArticles(gSub, newPosts);
       });
   }

   private void notifySubscribersAboutNewArticles(GroupSub gSub, List<PostInfo> newPosts) {
       Collections.reverse(newPosts);
       List<String> messagesWithNewArticles = newPosts.stream()
               .map(post -> String.format("✨Вышла новая статья <b>%s</b> в группе <b>%s</b>.✨\n\n" +
                               "<b>Описание:</b> %s\n\n" +
                               "<b>Ссылка:</b> %s\n",
                       post.getTitle(), gSub.getTitle(), post.getDescription(), getPostUrl(post.getKey())))
               .collect(Collectors.toList());

       gSub.getUsers().stream()
               .filter(TelegramUser::isActive)
               .forEach(it -> sendMessageService.sendMessage(it.getChatId(), messagesWithNewArticles));
   }

   private void setNewLastArticleId(GroupSub gSub, List<PostInfo> newPosts) {
       newPosts.stream().mapToInt(PostInfo::getId).max()
               .ifPresent(id -> {
                   gSub.setLastArticleId(id);
                   groupSubService.save(gSub);
               });
   }

   private String getPostUrl(String key) {
       return String.format(JAVARUSH_WEB_POST_FORMAT, key);
   }
}
여기서는 모든 것을 순서대로 다룰 것입니다.
  1. groupService를 사용하여 데이터베이스에 있는 모든 그룹을 찾습니다.

  2. 그런 다음 모든 그룹으로 분산하고 각 그룹에 대해 마지막 기사에서 생성된 클라이언트인 javaRushPostClient.findNewPosts 를 호출합니다 .

  3. 다음으로, setNewArticleId 메소드를 사용하여 최신 새 기사의 기사 ID를 업데이트하여 데이터베이스가 이미 새 기사를 처리했음을 알 수 있도록 합니다.

  4. 그리고 GroupSub에 사용자 모음이 있다는 사실을 이용하여 활성 사용자를 살펴보고 새 기사에 대한 알림을 보냅니다.

지금은 메시지가 무엇인지 논의하지 않을 것입니다. 그것은 우리에게 그다지 중요하지 않습니다. 가장 중요한 것은 방법이 작동한다는 것입니다. 새 기사를 검색하고 알림을 보내는 로직이 준비되었으므로 작업 만들기로 넘어갈 수 있습니다.

FindNewArticleJob 만들기

우리는 이미 SpringScheduler가 무엇인지에 대해 이야기했지만 빠르게 반복해 보겠습니다. 이는 우리가 설정한 특정 시간에 실행될 백그라운드 프로세스를 생성하기 위한 Spring 프레임워크의 메커니즘입니다. 이를 위해 무엇이 필요합니까? 첫 번째 단계는 스프링 입력 클래스에 @EnableScheduling 주석을 추가하는 것입니다.
package com.github.javarushcommunity.jrtb;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling
@SpringBootApplication
public class JavarushTelegramBotApplication {

   public static void main(String[] args) {
       SpringApplication.run(JavarushTelegramBotApplication.class, args);
   }

}
두 번째 단계는 클래스를 생성하고 이를 ApplicationContext 에 추가한 후 주기적으로 실행될 메서드를 생성하는 것입니다. 저장소, 서비스 등과 동일한 수준에서 작업 패키지를 생성하고 거기에서 FindNewArticleJob 클래스를 생성합니다 .
package com.github.javarushcommunity.jrtb.job;

import com.github.javarushcommunity.jrtb.service.FindNewArticleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;

/**
* Job for finding new articles.
*/
@Slf4j
@Component
public class FindNewArticlesJob {

   private final FindNewArticleService findNewArticleService;

   @Autowired
   public FindNewArticlesJob(FindNewArticleService findNewArticleService) {
       this.findNewArticleService = findNewArticleService;
   }

   @Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}")
   public void findNewArticles() {
       LocalDateTime start = LocalDateTime.now();

       log.info("Find new article job started.");

       findNewArticleService.findNewArticles();

       LocalDateTime end = LocalDateTime.now();

       log.info("Find new articles job finished. Took seconds: {}",
               end.toEpochSecond(ZoneOffset.UTC) - start.toEpochSecond(ZoneOffset.UTC));
   }
}
이 클래스를 Application Context 에 추가하기 위해 @Component 주석을 사용했습니다 . 그리고 클래스 내부의 메서드가 주기적으로 실행되어야 함을 알 수 있도록 메서드에 @Scheduled (fixedRateString = "${bot.recountNewArticleFixedRate}") 주석을 추가했습니다 . 하지만 우리는 이를 application.properties 파일에 설정했습니다:
bot.recountNewArticleFixedRate = 900000
여기서 값은 밀리초 단위입니다. 15분 정도 소요됩니다. 이 방법에서는 모든 것이 간단합니다. 새 기사 검색을 계산하기 위해 로그에 매우 간단한 측정 항목을 추가하여 최소한 작동 속도를 대략적으로 이해했습니다.

새로운 기능 테스트

이제 테스트 봇을 테스트하겠습니다. 하지만 어떻게? 알림이 왔다는 걸 알리기 위해 매번 기사를 삭제하지는 않을 건가요? 당연히 아니지. 간단히 데이터베이스의 데이터를 편집하고 애플리케이션을 시작하겠습니다. 테스트 서버에서 테스트해보겠습니다. 이를 위해 일부 그룹을 구독해 보겠습니다. 구독이 완료되면 그룹에는 최신 기사의 현재 ID가 부여됩니다. 데이터베이스로 이동하여 두 기사의 값을 다시 변경해 보겠습니다. 결과적으로 lastArticleId를 early로 설정한 만큼의 기사가 있을 것으로 예상됩니다 . "A부터 Z까지 Java 프로젝트": Spring Scheduler 추가 - 2다음으로, 사이트로 이동하여 Java 프로젝트 그룹의 기사를 정렬하고( 새 기사부터 ) 목록에서 세 번째 기사로 이동합니다. "Java-проект от А до Я": Добавляем Spring Scheduler - 3맨 아래 기사로 이동하고 주소 표시줄에서 기사 ID - 3313을 얻습니다. "Java-проект от А до Я": Добавляем Spring Scheduler - 4다음 , MySQL Workbench로 이동하여 lastArticleId 값을 3313으로 변경합니다. 해당 그룹이 데이터베이스에 있는지 확인합니다. "Java-проект от А до Я": Добавляем Spring Scheduler - 5이에 대해 명령을 실행합니다 "Java-проект от А до Я": Добавляем Spring Scheduler - 6. 이제 다음 작업이 시작될 때까지 기다려야 합니다. 새로운 기사를 검색해 보세요. 우리는 Java 프로젝트 그룹으로부터 새 기사에 대한 두 개의 메시지를 받을 것으로 예상합니다. 그들이 말했듯이 결과는 그리 오래 걸리지 않았습니다. "Java-проект от А до Я": Добавляем Spring Scheduler - 7봇이 우리가 예상한 대로 작동한 것으로 나타났습니다.

종결

언제나 그렇듯이 작업 기록이 저장되고 언제든지 돌아가서 변경된 내용을 이해할 수 있도록 pom.xml의 버전을 업데이트하고 RELEASE_NOTES에 항목을 추가합니다. 따라서 버전을 한 단위씩 증가시킵니다.
<version>0.7.0-SNAPSHOT</version>
그리고 RELEASE_NOTES를 업데이트하세요.
## 0.7.0-SNAPSHOT * JRTB-4: 새 기사에 대한 알림을 보내는 기능 추가 * JRTB-8: 비활성 텔레그램 사용자 설정 기능 추가 * JRTB-9: 활성 사용자 설정 및/또는 사용 시작 기능 추가.
이제 풀 요청을 생성하고 새로운 변경 사항을 업로드할 수 있습니다. 다음은 두 부분으로 구성된 모든 변경 사항이 포함된 풀 요청입니다: STEP_8 . 무엇 향후 계획? 모든 것이 준비된 것처럼 보이고 우리가 말했듯이 생산에 들어갈 수 있지만 아직하고 싶은 일이 있습니다. 예를 들어 봇에 대한 관리자 작업을 구성하고 이를 추가하고 설정 기능을 추가합니다. 마무리하기 전에 코드를 살펴보고 리팩토링할 수 있는 부분이 있는지 확인하는 것도 좋은 생각입니다. 기사/게시물의 이름 지정에서 이미 비동기화를 볼 수 있습니다. 마지막에는 우리가 계획한 것과 받은 것을 회고하는 작업을 하게 됩니다. 그리고 앞으로는 어떤 일을 하고 싶나요? 이제 나는 빛을 볼 수 있고 볼 수 있는 상당히 조잡한 아이디어를 여러분과 공유하겠습니다. 즉, 텔레그램 봇으로 작업하고 기사를 검색하는 데 필요한 모든 기능을 갖춘 스프링부트 스타터를 만드는 것입니다. 이를 통해 접근 방식을 통합하고 다른 텔레그램 봇에 사용할 수 있습니다. 이렇게 하면 이 프로젝트를 다른 사람들이 더 쉽게 접근할 수 있게 되고 더 많은 사람들에게 혜택을 줄 수 있습니다. 이것은 아이디어 중 하나입니다. 또 다른 아이디어는 알림 개발에 더 깊이 들어가는 것입니다. 하지만 이에 대해서는 조금 나중에 이야기하겠습니다. 평소처럼 관심을 가져주신 모든 분들께 감사드립니다. 좋아요 - 구독 - 벨 , 우리 프로젝트에 별표 표시하고 , 기사에 댓글을 달고 평가해 주세요! 읽어주신 모든 분들께 감사드립니다.

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

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