-
Wyszukuje we wszystkich grupach znajdujących się w naszej bazie nowe artykuły opublikowane po poprzedniej realizacji.
Ten schemat określa mniejszą liczbę grup - tylko te z aktywnymi użytkownikami. Wtedy wydawało mi się to logiczne, ale teraz rozumiem, że niezależnie od tego, czy w danej grupie są aktywni użytkownicy, czy nie, nadal trzeba na bieżąco aktualizować najnowszy artykuł, który bot przetworzył. Może zaistnieć sytuacja, gdy nowy użytkownik od razu otrzyma całą liczbę artykułów opublikowanych od chwili dezaktywacji tej grupy. Nie jest to oczekiwane zachowanie i aby go uniknąć, musimy pozostawić w naszej bazie danych te grupy, które obecnie nie mają aktywnych użytkowników. -
Jeśli pojawiły się nowe artykuły, wygeneruj wiadomości dla wszystkich użytkowników, którzy aktywnie subskrybują tę grupę. Jeśli nie ma nowych artykułów, po prostu kończymy pracę.
Znajdź nową usługę artykułu:
package com.github.javarushcommunity.jrtb.service;
/**
* Service for finding new articles.
*/
public interface FindNewArticleService {
/**
* Find new articles and notify subscribers about it.
*/
void findNewArticles();
}
Bardzo proste, prawda? To jest istota tego i cała trudność będzie polegać na wdrożeniu:
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);
}
}
Tutaj zajmiemy się wszystkim po kolei:
-
Za pomocą groupService znajdujemy wszystkie grupy znajdujące się w bazie danych.
-
Następnie rozchodzimy się do wszystkich grup i dla każdej wywołujemy klienta stworzonego w ostatnim artykule - javaRushPostClient.findNewPosts .
-
Następnie za pomocą metody setNewArticleId aktualizujemy identyfikator artykułu naszego najnowszego nowego artykułu, tak aby nasza baza danych wiedziała, że przetworzyliśmy już nowe.
-
A korzystając z faktu, że GroupSub posiada kolekcję użytkowników, przeglądamy tych aktywnych i wysyłamy powiadomienia o nowych artykułach.
Utwórz FindNewArticleJob
Mówiliśmy już o tym, czym jest SpringScheduler, ale powtórzmy to szybko: jest to mechanizm we frameworku Spring służący do tworzenia procesu w tle, który będzie uruchamiany o określonej przez nas godzinie. Czego potrzebujesz do tego? Pierwszym krokiem jest dodanie adnotacji @EnableScheduling do naszej wiosennej klasy wejściowej: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);
}
}
Drugim krokiem jest utworzenie klasy, dodanie jej do ApplicationContext i utworzenie w niej metody, która będzie uruchamiana okresowo. Tworzymy pakiet zadań na tym samym poziomie co repozytorium, usługa itd. i tam tworzymy klasę 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));
}
}
Aby dodać tę klasę do kontekstu aplikacji, użyłem adnotacji @Component . I żeby metoda wewnątrz klasy wiedziała, że trzeba ją uruchamiać okresowo, dodałem do metody adnotację: @Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}") . Ale ustawiliśmy to w pliku application.properties:
bot.recountNewArticleFixedRate = 900000
Tutaj wartość jest wyrażona w milisekundach. To będzie 15 minut. W tej metodzie wszystko jest proste: dodałem dla siebie super prostą metrykę w logach, aby obliczyć wyszukiwanie nowych artykułów, aby chociaż z grubsza zrozumieć, jak szybko to działa.
Testowanie nowej funkcjonalności
Teraz będziemy testować na naszym bocie testowym. Ale jak? Nie będę za każdym razem usuwać artykułów, aby pokazać, że dotarły powiadomienia? Oczywiście nie. Po prostu zmodyfikujemy dane w bazie i uruchomimy aplikację. Przetestuję to na moim serwerze testowym. Aby to zrobić, zapiszmy się do jakiejś grupy. Po zakończeniu subskrypcji grupa otrzyma aktualny identyfikator najnowszego artykułu. Przejdźmy do bazy danych i zmieńmy wartość dwa artykuły wstecz. W rezultacie spodziewamy się, że będzie tyle artykułów, ile wcześniej ustawiliśmy lastArticleId . Następnie wchodzimy na stronę, sortujemy artykuły w grupie Projekty Java - najpierw nowe - i przechodzimy do trzeciego artykułu z listy: Przejdźmy do artykułu na dole i z paska adresu otrzymamy identyfikator artykułu - 3313: Dalej , przejdź do MySQL Workbench i zmień wartość lastArticleId na 3313. Zobaczmy, czy taka grupa znajduje się w bazie danych: I dla niej wykonamy polecenie: I to wszystko, teraz musisz poczekać do następnego uruchomienia zadania, aby szukać nowych artykułów. Spodziewamy się otrzymać dwie wiadomości dotyczące nowego artykułu z grupy projektów Java. Jak mówią, na wynik nie trzeba było długo czekać: okazuje się, że bot zadziałał tak, jak się spodziewaliśmy.Kończący się
Jak zawsze aktualizujemy wersję w pom.xml i dodajemy wpis do RELEASE_NOTES, aby historia pracy została zapisana i zawsze można było wrócić i zrozumieć, co się zmieniło. Dlatego zwiększamy wersję o jedną jednostkę:<version>0.7.0-SNAPSHOT</version>
I zaktualizuj RELEASE_NOTES:
GO TO FULL VERSION