JavaRush /Java Blog /Random-TW /新增 Spring Scheduler - 《Java 專案從 A 到 Z》
Roman Beekeeper
等級 35

新增 Spring Scheduler - 《Java 專案從 A 到 Z》

在 Random-TW 群組發布
大家好,我親愛的朋友們。在上一篇文章中,我們準備了一個用於文章的 JavaRush API 的客戶端。現在我們可以為我們的作業編寫邏輯,該邏輯將每 15 分鐘執行一次。如下圖所示: 「Java 專案從頭到尾」:新增 Spring Scheduler - 1每 15 分鐘就會啟動一個作業(在我們看來,只是特定類別中的一個方法),該作業在主應用程式的後台執行,並執行以下操作:
  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註解加入我們的 spring 輸入類別:
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));
   }
}
為了將此類添加到應用程式上下文,我使用了@Component註釋。為了讓類別內的方法知道它需要定期運行,我為該方法添加了一個註解:@Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}")。但它的值是什麼 - 我們已經在 application.properties 檔案中設定了它:
bot.recountNewArticleFixedRate = 900000
這裡的值以毫秒為單位。需 15 分鐘。在這種方法中,一切都很簡單:我在日誌中為自己添加了一個超級簡單的指標來計算新文章的搜尋量,以便至少大致了解它的工作速度。

測試新功能

現在我們將在我們的測試機器人上進行測試。但如何呢?我不會每次都刪文章才顯示通知到了?當然不是。我們將簡單地編輯資料庫中的資料並啟動應用程式。我將在我的測試伺服器上測試它。為此,我們需要訂閱某個群組。訂閱完成後,群組將獲得最新文章的當前 ID。我們去資料庫把兩篇文章的數值改回來。因此,我們預計將有與我們將lastArticleId設定為更早的文章一樣多的文章。「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。下一步是什麼?看起來一切都準備好了,正如我們所說,它可以投入生產,但仍然有一些事情我想做。例如,為機器人配置管理員的工作,新增管理員並新增設定它們的功能。在完成之前檢查程式碼並看看是否有可以重構的東西也是一個好主意。我已經可以看到文章/帖子命名中的不同步。最後,我們將對我們的計劃和收到的內容進行回顧。您將來想做什麼?現在我將與您分享一個相當粗略的想法,可以並且將會看到曙光:製作一個 springboot 啟動器,該啟動器將具有使用電報機器人和搜尋文章的所有功能。這將使統一該方法並將其用於其他電報機器人成為可能。這將使這個計畫更容易為其他人所接受,並使更多的人受益。這是想法之一。另一個想法是更深入地進行通知開發。但我們稍後會討論這個。 感謝大家一如既往的關注:按讚 - 訂閱 - 響鈴為我們的專案加星,評論並評論文章! 感謝大家的閱讀。

此系列所有資料的清單位於本文開頭。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION