-
Encontra em todos os grupos que estão em nossa base de dados novos artigos publicados após a execução anterior.
Este esquema especifica um número menor de grupos – apenas aqueles com usuários ativos. Naquela época parecia lógico para mim, mas agora entendo que independentemente de haver usuários ativos inscritos em um grupo específico ou não, você ainda precisa manter atualizado o artigo mais recente que o bot processou. Pode surgir uma situação em que um novo usuário receba imediatamente todo o número de artigos publicados desde a desativação deste grupo. Este comportamento não é esperado e, para evitá-lo, precisamos manter atualizados os grupos de nosso banco de dados que atualmente não possuem usuários ativos. -
Se houver novos artigos, gere mensagens para todos os usuários que estão ativamente inscritos neste grupo. Se não houver novos artigos, simplesmente concluímos o trabalho.
EncontrarNovoArtigoServiço:
package com.github.javarushcommunity.jrtb.service;
/**
* Service for finding new articles.
*/
public interface FindNewArticleService {
/**
* Find new articles and notify subscribers about it.
*/
void findNewArticles();
}
Muito simples, certo? Esta é a sua essência, e toda a dificuldade estará na implementação:
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);
}
}
Aqui trataremos de tudo em ordem:
-
Usando groupService encontramos todos os grupos que estão no banco de dados.
-
Em seguida, dispersamos todos os grupos e para cada um chamamos o cliente criado no último artigo - javaRushPostClient.findNewPosts .
-
Em seguida, usando o método setNewArticleId , atualizamos o ID do artigo do nosso novo artigo mais recente para que nosso banco de dados saiba que já processamos novos.
-
E aproveitando o fato do GroupSub possuir um acervo de usuários, percorremos os ativos e enviamos notificações sobre novos artigos.
Criar FindNewArticleJob
Já falamos sobre o que é SpringScheduler, mas vamos repetir rapidamente: é um mecanismo do framework Spring para criar um processo em segundo plano que será executado em um horário específico que definimos. O que você precisa para isso? A primeira etapa é adicionar a anotação @EnableScheduling à nossa classe de entrada 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);
}
}
O segundo passo é criar uma classe, adicioná-la ao ApplicationContext e criar nela um método que será executado periodicamente. Criamos um pacote de tarefas no mesmo nível do repositório, serviço e assim por diante, e lá criamos a classe 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));
}
}
Para adicionar esta classe ao Contexto da Aplicação utilizei a anotação @Component . E para que o método dentro da classe saiba que precisa ser executado periodicamente, adicionei uma anotação ao método: @Scheduled(fixedRateString = "${bot.recountNewArticleFixedRate}") . Mas qual será o valor - já o definimos no arquivo application.properties:
bot.recountNewArticleFixedRate = 900000
Aqui o valor está em milissegundos. Serão 15 minutos. Nesse método tudo é simples: adicionei uma métrica super simples para mim nos logs para calcular a busca por novos artigos, para pelo menos entender aproximadamente a rapidez com que funciona.
Testando novas funcionalidades
Agora vamos testar em nosso bot de teste. Mas como? Não vou deletar artigos todas as vezes para mostrar que as notificações chegaram? Claro que não. Simplesmente editaremos os dados no banco de dados e iniciaremos o aplicativo. Vou testá-lo no meu servidor de testes. Para fazer isso, vamos nos inscrever em algum grupo. Quando a assinatura for concluída, o grupo receberá o ID atual do artigo mais recente. Vamos ao banco de dados e alteramos o valor dois artigos atrás. Como resultado, esperamos que haja tantos artigos quanto definimos lastArticleId anteriormente . Em seguida, vamos ao site, classificamos os artigos no grupo de projetos Java - os novos primeiro - e vamos para o terceiro artigo da lista: Vamos para o artigo de baixo e na barra de endereço obtemos o Id do artigo - 3313: Próximo , vá para MySQL Workbench e altere o valor lastArticleId para 3313. Vamos ver que tal grupo está no banco de dados: E para isso executaremos o comando: E pronto, agora você precisa esperar até o próximo lançamento do trabalho para procurar novos artigos. Esperamos receber duas mensagens sobre um novo artigo do grupo de projetos Java. Como se costuma dizer, o resultado não demorou a chegar: Acontece que o bot funcionou como esperávamos.Final
Como sempre, atualizamos a versão em pom.xml e adicionamos uma entrada em RELEASE_NOTES para que o histórico de trabalho seja salvo e você possa sempre voltar e entender o que mudou. Portanto, incrementamos a versão em uma unidade:<version>0.7.0-SNAPSHOT</version>
E atualize RELEASE_NOTES:
GO TO FULL VERSION