JavaRush /Blog Java /Random-VI /Thêm Spring Scheduler - "Dự án Java từ A đến Z"
Roman Beekeeper
Mức độ

Thêm Spring Scheduler - "Dự án Java từ A đến Z"

Xuất bản trong nhóm
Xin chào tất cả mọi người, những người bạn thân yêu của tôi. Trong bài viết trước , chúng tôi đã chuẩn bị một ứng dụng khách để làm việc với API JavaRush cho các bài viết. Bây giờ chúng ta có thể viết logic cho công việc của mình, công việc này sẽ được thực thi 15 phút một lần. Chính xác như được hiển thị trong sơ đồ này: “Dự án Java từ A đến Z”: Thêm Spring Scheduler - 1Cứ sau 15 phút, một công việc sẽ được khởi chạy (theo ý kiến ​​của chúng tôi, chỉ là một phương thức trong một lớp cụ thể), được thực thi trong nền của ứng dụng chính và thực hiện như sau:
  1. Tìm trong tất cả các nhóm có trong cơ sở dữ liệu của chúng tôi các bài viết mới được xuất bản sau lần thực hiện trước đó.

    Lược đồ này chỉ định số lượng nhóm nhỏ hơn - chỉ những nhóm có người dùng đang hoạt động. Vào thời điểm đó, điều đó đối với tôi có vẻ hợp lý, nhưng bây giờ tôi hiểu rằng bất kể có người dùng đang hoạt động nào đăng ký vào một nhóm cụ thể hay không, bạn vẫn cần cập nhật bài viết mới nhất mà bot xử lý. Một tình huống có thể phát sinh khi người dùng mới nhận được ngay toàn bộ số bài viết đã xuất bản kể từ khi nhóm này ngừng hoạt động. Đây không phải là hành vi được mong đợi và để tránh nó, chúng tôi cần loại bỏ những nhóm hiện không có người dùng hoạt động khỏi cơ sở dữ liệu của chúng tôi.
  2. Nếu có bài viết mới, hãy tạo tin nhắn cho tất cả người dùng đã đăng ký tích cực vào nhóm này. Nếu không có bài viết mới, chúng tôi chỉ đơn giản là hoàn thành công việc.

Nhân tiện, tôi đã đề cập trong kênh TG của mình rằng bot đã hoạt động và gửi các bài viết mới dựa trên đăng ký. Hãy bắt đầu viết FindNewArtcileService . Tất cả công việc tìm kiếm và gửi tin nhắn sẽ diễn ra ở đó và công việc sẽ chỉ khởi chạy phương thức của dịch vụ này:

Dịch vụ FindNewArticle:

package com.github.javarushcommunity.jrtb.service;

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

   /**
    * Find new articles and notify subscribers about it.
    */
   void findNewArticles();
}
Rất đơn giản phải không? Đây là bản chất của nó và tất cả khó khăn sẽ nằm ở việc thực hiện:
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);
   }
}
Ở đây chúng tôi sẽ giải quyết mọi thứ theo thứ tự:
  1. Sử dụng groupService chúng tôi tìm thấy tất cả các nhóm có trong cơ sở dữ liệu.

  2. Sau đó, chúng tôi phân tán đến tất cả các nhóm và với mỗi nhóm, chúng tôi gọi ứng dụng khách được tạo trong bài viết trước - javaRushPostClient.findNewPosts .

  3. Tiếp theo, bằng cách sử dụng phương thức setNewArticleId , chúng ta cập nhật ID bài viết của bài viết mới nhất để cơ sở dữ liệu biết rằng chúng tôi đã xử lý bài viết mới.

  4. Và dựa trên thực tế là GroupSub có một tập hợp người dùng, chúng tôi xem xét những người dùng đang hoạt động và gửi thông báo về các bài viết mới.

Bây giờ chúng ta sẽ không thảo luận về thông điệp đó là gì, nó không quan trọng lắm đối với chúng ta. Điều chính là phương pháp này hoạt động. Logic tìm kiếm bài viết mới và gửi thông báo đã sẵn sàng, vì vậy bạn có thể chuyển sang tạo công việc.

Tạo FindNewArticleJob

Chúng ta đã nói về SpringScheduler là gì, nhưng hãy nhắc lại nhanh: đó là một cơ chế trong Spring framework để tạo một quy trình nền sẽ chạy vào một thời điểm cụ thể mà chúng ta đã đặt. Bạn cần gì cho việc này? Bước đầu tiên là thêm chú thích @EnableScheduling vào lớp đầu vào mùa xuân của chúng ta:
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);
   }

}
Bước thứ hai là tạo một lớp, thêm nó vào ApplicationContext và tạo một phương thức trong đó sẽ được chạy định kỳ. Chúng tôi tạo một gói công việc ở cùng cấp độ với kho lưu trữ, dịch vụ, v.v. và ở đó chúng tôi tạo lớp 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));
   }
}
Để thêm lớp này vào Bối cảnh ứng dụng , tôi đã sử dụng chú thích @Component . Và để phương thức bên trong lớp biết rằng nó cần được chạy định kỳ, tôi đã thêm chú thích vào phương thức: @Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}") . Nhưng chúng tôi đặt nó trong tệp application.properties:
bot.recountNewArticleFixedRate = 900000
Ở đây giá trị tính bằng mili giây. Sẽ là 15 phút. Trong phương pháp này, mọi thứ đều đơn giản: Tôi đã thêm một số liệu siêu đơn giản cho chính mình vào nhật ký để tính toán tìm kiếm các bài viết mới, nhằm ít nhất là hiểu đại khái tốc độ hoạt động của nó.

Thử nghiệm chức năng mới

Bây giờ chúng ta sẽ thử nghiệm trên bot thử nghiệm của mình. Nhưng bằng cách nào? Tôi sẽ không xóa bài viết mỗi lần để hiển thị rằng thông báo đã đến? Dĩ nhiên là không. Chúng tôi chỉ cần chỉnh sửa dữ liệu trong cơ sở dữ liệu và khởi chạy ứng dụng. Tôi sẽ thử nghiệm nó trên máy chủ thử nghiệm của tôi. Để làm điều này, hãy đăng ký vào một số nhóm. Khi đăng ký hoàn tất, nhóm sẽ được cấp ID hiện tại của bài viết mới nhất. Hãy vào cơ sở dữ liệu và thay đổi giá trị của hai bài viết. Do đó, chúng tôi mong đợi rằng sẽ có nhiều bài viết như chúng tôi đã đặt LastArticleId thành trước đó . "Dự án Java từ A đến Z": Thêm Spring Scheduler - 2Tiếp theo, chúng ta vào trang web, sắp xếp các bài viết trong nhóm dự án Java - những bài mới trước - và đi đến bài viết thứ ba từ danh sách: "Java-проект от А до Я": Добавляем Spring Scheduler - 3Chúng ta hãy đi đến bài viết dưới cùng và từ thanh địa chỉ, chúng ta nhận được Id bài viết - 3313: "Java-проект от А до Я": Добавляем Spring Scheduler - 4Next , hãy truy cập MySQL Workbench và thay đổi giá trị LastArticleId thành 3313. Hãy xem một nhóm như vậy có trong cơ sở dữ liệu hay không: "Java-проект от А до Я": Добавляем Spring Scheduler - 5Và đối với nó, chúng ta sẽ thực thi lệnh: "Java-проект от А до Я": Добавляем Spring Scheduler - 6Và thế là xong, bây giờ bạn cần đợi cho đến lần khởi chạy công việc tiếp theo để tìm kiếm bài viết mới. Chúng tôi mong đợi nhận được hai thông báo về một bài viết mới từ nhóm dự án Java. Như họ nói, không lâu nữa sẽ có kết quả: "Java-проект от А до Я": Добавляем Spring Scheduler - 7Hóa ra bot đã hoạt động như chúng tôi mong đợi.

Kết thúc

Như mọi khi, chúng tôi cập nhật phiên bản trong pom.xml và thêm mục nhập vào RELEASE_NOTES để lịch sử công việc được lưu và bạn luôn có thể quay lại và hiểu những gì đã thay đổi. Do đó, chúng tôi tăng phiên bản lên một đơn vị:
<version>0.7.0-SNAPSHOT</version>
Và cập nhật RELEASE_NOTES:
## 0.7.0-SNAPSHOT * JRTB-4: đã thêm khả năng gửi thông báo về các bài viết mới * JRTB-8: đã thêm khả năng thiết lập người dùng telegram không hoạt động * JRTB-9: đã thêm khả năng thiết lập người dùng đang hoạt động và/hoặc bắt đầu sử dụng nó.
Bây giờ bạn có thể tạo yêu cầu kéo và tải lên các thay đổi mới. Đây là yêu cầu kéo với tất cả các thay đổi trong hai phần: STEP_8 . Cái gì tiếp theo? Có vẻ như mọi thứ đã sẵn sàng và như chúng tôi nói, nó có thể đi vào sản xuất, nhưng vẫn còn một số việc tôi muốn làm. Ví dụ: định cấu hình công việc của quản trị viên cho bot, thêm chúng và thêm khả năng thiết lập chúng. Bạn cũng nên xem qua mã trước khi hoàn tất và xem liệu có những thứ nào có thể được cấu trúc lại hay không. Tôi đã có thể thấy sự đồng bộ hóa trong cách đặt tên bài viết/bài đăng. Cuối cùng, chúng tôi sẽ hồi tưởng lại những gì chúng tôi đã lên kế hoạch và những gì chúng tôi đã nhận được. Và bạn muốn làm gì trong tương lai? Bây giờ tôi sẽ chia sẻ với bạn một ý tưởng khá thô sơ có thể và sẽ được đưa ra ánh sáng: tạo ra một bộ khởi động springboot có tất cả chức năng để làm việc với bot telegram và tìm kiếm các bài báo. Điều này sẽ giúp thống nhất cách tiếp cận và sử dụng nó cho các bot điện tín khác. Điều này sẽ làm cho dự án này dễ tiếp cận hơn với những người khác và có thể mang lại lợi ích cho nhiều người hơn. Đây là một trong những ý tưởng. Một ý tưởng khác là đi sâu hơn vào việc phát triển thông báo. Nhưng chúng ta sẽ nói về điều này một lát sau. Cảm ơn tất cả các bạn đã quan tâm, như thường lệ: thích - đăng ký - chuông , gắn sao cho dự án của chúng tôi , bình luận và đánh giá bài viết! Cảm ơn mọi người đã đọc.

Danh sách tất cả các tài liệu trong loạt bài này nằm ở đầu bài viết này.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION