JavaRush /בלוג Java /Random-HE /הוספת מתזמן אביב - "פרויקט ג'אווה מא' עד ת'"
Roman Beekeeper
רָמָה

הוספת מתזמן אביב - "פרויקט ג'אווה מא' עד ת'"

פורסם בקבוצה
שלום לכולם, חברים יקרים שלי. במאמר הקודם , הכנו לקוח לעבודה עם JavaRush API למאמרים. כעת נוכל לכתוב היגיון לעבודה שלנו, שתתבצע כל 15 דקות. בדיוק כפי שמוצג בתרשים זה: "פרויקט ג'אווה מא' עד ת': הוספת מתזמן אביב - 1כל 15 דקות תושק עבודה (לדעתנו, רק שיטה במחלקה ספציפית), אשר מבוצעת ברקע של האפליקציה הראשית ועושה את הדברים הבאים:
  1. מוצא בכל הקבוצות שנמצאות במסד הנתונים שלנו מאמרים חדשים שפורסמו לאחר הביצוע הקודם.

    סכימה זו מציינת מספר קטן יותר של קבוצות - רק אלה עם משתמשים פעילים. באותו זמן זה נראה לי הגיוני, אבל עכשיו אני מבין שללא קשר אם יש משתמשים פעילים שנרשמו לקבוצה ספציפית או לא, אתה עדיין צריך לעדכן את המאמר האחרון שהבוט עיבד. עלול להיווצר מצב שבו משתמש חדש יקבל מיד את כל הכתבות שפורסמו מאז השבתת קבוצה זו. זו אינה התנהגות צפויה, וכדי להימנע מכך, עלינו לשמור את הקבוצות הללו ממסד הנתונים שלנו שכרגע אין להם משתמשים פעילים מעודכנים.
  2. אם יש מאמרים חדשים, צור הודעות לכל המשתמשים הרשומים באופן פעיל לקבוצה זו. אם אין מאמרים חדשים, אנחנו פשוט משלימים את העבודה.

אגב, כבר ציינתי בערוץ ה-TG שלי שהבוט כבר עובד ושולח מאמרים חדשים על סמך מנויים. בואו נתחיל לכתוב FindNewArtcileService . כל עבודת החיפוש ושליחת ההודעות תתבצע שם, והתפקיד יפעיל רק את השיטה של ​​שירות זה:

FindNewArticleService:

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 , אנו מעדכנים את מזהה המאמר של המאמר החדש האחרון שלנו כך שמסד הנתונים שלנו יידע שכבר עיבדנו חדשים.

  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));
   }
}
כדי להוסיף את המחלקה הזו להקשר היישום השתמשתי בביאור @Component . וכדי שהמתודה בתוך המחלקה תדע שצריך להפעיל אותה מדי פעם, הוספתי הערה למתודה: @Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}") . אבל איזה ערך זה יהיה - הגדרנו אותו כבר בקובץ application.properties:
bot.recountNewArticleFixedRate = 900000
כאן הערך הוא באלפיות שניות. זה יהיה 15 דקות. בשיטה זו הכל פשוט: הוספתי לעצמי מדד סופר פשוט ביומנים כדי לחשב את החיפוש אחר מאמרים חדשים, כדי לפחות להבין בערך כמה מהר זה עובד.

בדיקת פונקציונליות חדשה

כעת נבדוק על בוט הבדיקה שלנו. אבל איך? אני לא אמחק מאמרים בכל פעם כדי להראות שההתראות הגיעו? ברור שלא. אנו פשוט נערוך את הנתונים במסד הנתונים ונפעיל את האפליקציה. אני אבדוק את זה בשרת הבדיקה שלי. כדי לעשות זאת, בואו נעשה מנוי לקבוצה כלשהי. עם השלמת המנוי, הקבוצה תקבל את המזהה הנוכחי של המאמר האחרון. בוא נלך למסד הנתונים ונשנה את הערך שני מאמרים בחזרה. כתוצאה מכך, אנו מצפים שיהיו כמה מאמרים כפי שהגדרנו את lastArticleId לקודם לכן . "פרויקט ג'אווה מא' עד ת': הוספת מתזמן אביב - 2לאחר מכן, נעבור לאתר, ממיין את המאמרים בקבוצת הפרויקטים של Java - ראשונים חדשים - ונעבור למאמר השלישי מהרשימה: "Java-проект от А до Я": Добавляем Spring Scheduler - 3נעבור למאמר התחתון ומשורת הכתובת נקבל מאמר Id - 3313: "Java-проект от А до Я": Добавляем Spring Scheduler - 4Next , כנסו ל-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