Olá a todos, queridos amigos. Hoje vamos implementar um template (template é um padrão, no nosso contexto é a mesma coisa) de design de Comando para as nossas necessidades. Usando este modelo, trabalharemos de maneira conveniente e correta com o processamento dos comandos do nosso bot.
Para começar, seria bom falar sobre que tipo de padrão é esse - Comando. Mas se eu fizer isso, o artigo ficará muito grande e complicado. Portanto, escolhi materiais para autoestudo:
Amigos, vocês gostam do projeto Javarush Telegram Bot ? Não tenha preguiça: dê uma estrela . Assim ficará claro que ele é interessante e será mais prazeroso desenvolvê-lo! |
- Este é o meu artigo de 4 anos atrás. Eu escrevi quando era júnior, então não julgue com muita severidade.
- Vídeo de um sueco muito emocionante e interativo no YouTube. Eu recomendo. Ele fala lindamente, seu inglês é claro e compreensível. E, em geral, ele tem um vídeo sobre outros padrões de design.
- Nos comentários do meu artigo, alguém Nullptr35 recomendou este vídeo .
Nós escrevemos JRTB-3
Tudo está como antes:- Atualizamos o branch principal.
- Com base no branch principal atualizado, criamos um novo JRTB-3 .
- Vamos implementar o padrão.
- Criamos um novo commit descrevendo o trabalho realizado.
- Criamos um pull request, verificamos e se estiver tudo bem, mesclamos nosso trabalho.
if (message.startsWith("/start")) {
doStartCommand();
} else if(message.startsWith("/stop")) {
doStopCommand();
} else if(message.startsWith("/addUser")) {
doAddUserCommand();
}
...
else if(message.startsWith("/makeMeHappy")) {
doMakeMeHappyCommand();
}
Além disso, onde há reticências, pode haver várias dezenas de equipes a mais. E como lidar com isso normalmente? Como apoiar? Difícil e difícil. Isso significa que esta opção não nos convém. Deve ser algo assim:
if (message.startsWith(COMMAND_PREFIX)) {
String commandIdentifier = message.split(" ")[0].toLowerCase();
commandContainer.getCommand(commandIdentifier, userName).execute(update);
} else {
commandContainer.getCommand(NO.getCommand(), userName).execute(update);
}
Isso é tudo! E não importa quantos comandos adicionemos, esta seção do código permanecerá inalterada. O que ele está fazendo? O primeiro if garante que a mensagem comece com o prefixo de comando "/". Se for esse o caso, então selecionamos a linha até o primeiro espaço e procuramos o comando correspondente no CommandContainer; assim que o encontrarmos, executamos o comando. E isso é tudo...) Se você tiver vontade e tempo, você pode implementar o trabalho em equipe, primeiro em uma aula de uma vez, com um monte de condições e tudo mais, e depois usando um template. Você verá a diferença. Que beleza será! Primeiro, vamos criar um pacote próximo ao pacote do bot, que será chamado de command . E já neste pacote estarão todas as classes relacionadas à implementação do comando. Precisamos de uma interface para trabalhar com comandos. Para este caso, vamos criá-lo:
package com.github.javarushcommunity.jrtb.command;
import org.telegram.telegrambots.meta.api.objects.Update;
/**
* Command interface for handling telegram-bot commands.
*/
public interface Command {
/**
* Main method, which is executing command logic.
*
* @param update provided {@link Update} object with all the needed data for command.
*/
void execute(Update update);
}
Neste ponto, não precisamos implementar a operação reversa do comando, então pularemos este método (desexecutar). No método execute, o objeto Update vem como argumento - exatamente aquele que vem para o nosso método principal no bot. Este objeto conterá tudo o que é necessário para processar o comando. A seguir, adicionaremos um enum que armazenará os valores do comando (iniciar, parar e assim por diante). Por que nós precisamos disso? Para que tenhamos apenas uma fonte de verdade para os nomes dos times. Também o criamos em nosso pacote de comandos . Vamos chamá-lo de CommandName :
package com.github.javarushcommunity.jrtb.command;
/**
* Enumeration for {@link Command}'s.
*/
public enum CommandName {
START("/start"),
STOP("/stop");
private final String commandName;
CommandName(String commandName) {
this.commandName = commandName;
}
public String getCommandName() {
return commandName;
}
}
Também precisamos de um serviço que envie mensagens por meio de um bot. Para fazer isso, criaremos um pacote de serviços próximo ao comando package , ao qual adicionaremos todos os serviços necessários. Aqui vale a pena focar no que quero dizer com a palavra serviço neste caso. Se considerarmos um aplicativo, ele geralmente é dividido em várias camadas: uma camada para trabalhar com endpoints - controladores, uma camada de lógica de negócios - serviços e uma camada para trabalhar com o banco de dados - um repositório. Portanto, no nosso caso, um serviço é uma classe que implementa algum tipo de lógica de negócio. Como criar um serviço corretamente? Primeiro, crie uma interface para isso e uma implementação. Adicione a implementação usando a anotação `@Service` ao contexto do aplicativo de nosso aplicativo SpringBoot e, se necessário, aperte-a usando a anotação `@Autowired`. Portanto, criamos a interface SendBotMessageService (na nomenclatura de serviços geralmente adicionam Service no final do nome):
package com.github.javarushcommunity.jrtb.service;
/**
* Service for sending messages via telegram-bot.
*/
public interface SendBotMessageService {
/**
* Send message via telegram bot.
*
* @param chatId provided chatId in which messages would be sent.
* @param message provided message to be sent.
*/
void sendMessage(String chatId, String message);
}
A seguir, criamos sua implementação:
package com.github.javarushcommunity.jrtb.service;
import com.github.javarushcommunity.jrtb.bot.JavarushTelegramBot;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
/**
* Implementation of {@link SendBotMessageService} interface.
*/
@Service
public class SendBotMessageServiceImpl implements SendBotMessageService {
private final JavarushTelegramBot javarushBot;
@Autowired
public SendBotMessageServiceImpl(JavarushTelegramBot javarushBot) {
this.javarushBot = javarushBot;
}
@Override
public void sendMessage(String chatId, String message) {
SendMessage sendMessage = new SendMessage();
sendMessage.setChatId(chatId);
sendMessage.enableHtml(true);
sendMessage.setText(message);
try {
javarushBot.execute(sendMessage);
} catch (TelegramApiException e) {
//todo add logging to the project.
e.printStackTrace();
}
}
}
É assim que se parece a implementação. A magia mais importante é onde o designer é criado. Usando a anotação @Autowired no construtor, o SpringBoot procurará um objeto desta classe em seu Contexto de Aplicação. E ele já está lá. Funciona assim: na nossa aplicação, em qualquer lugar podemos acessar o bot e fazer alguma coisa. E este serviço é responsável pelo envio de mensagens. Para que não escrevamos algo assim sempre e em todos os lugares:
SendMessage sendMessage = new SendMessage();
sendMessage.setChatId(chatId);
sendMessage.setText(message);
try {
javarushBot.execute(sendMessage);
} catch (TelegramApiException e) {
//todo add logging to the project.
e.printStackTrace();
}
Movemos essa lógica para uma classe separada e a usaremos se necessário. Agora precisamos implementar três comandos: StartCommand, StopCommand e UnknownCommand. Precisamos deles para termos algo para preencher nosso contêiner de comandos. Por enquanto, os textos serão áridos e pouco informativos; para os propósitos desta tarefa, isso não é muito importante. Então, StartCommand:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;
/**
* Start {@link Command}.
*/
public class StartCommand implements Command {
private final SendBotMessageService sendBotMessageService;
public final static String START_MESSAGE = "Привет. Я Javarush Telegram Bot. Я помогу тебе быть в курсе последних " +
"статей тех авторов, котрые тебе интересны. Я еще маленький и только учусь.";
// Здесь не добавляем сервис через получение из Application Context.
// Потому что если это сделать так, то будет циклическая зависимость, которая
// ломает работу applications.
public StartCommand(SendBotMessageService sendBotMessageService) {
this.sendBotMessageService = sendBotMessageService;
}
@Override
public void execute(Update update) {
sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), START_MESSAGE);
}
}
Por favor, leia os comentários com atenção antes do designer. A dependência circular ( circular dependency ) pode ocorrer devido a uma arquitetura que não está muito correta. No nosso caso, garantiremos que tudo funcione e esteja correto. O objeto real do Contexto da Aplicação será adicionado ao criar o comando já no CommandContainer. Comando Parar:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;
/**
* Stop {@link Command}.
*/
public class StopCommand implements Command {
private final SendBotMessageService sendBotMessageService;
public static final String STOP_MESSAGE = "Деактивировал все ваши подписки \uD83D\uDE1F.";
public StopCommand(SendBotMessageService sendBotMessageService) {
this.sendBotMessageService = sendBotMessageService;
}
@Override
public void execute(Update update) {
sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), STOP_MESSAGE);
}
}
E comando desconhecido. Por que precisamos disso? Para nós, este é um comando importante que responderá caso não consigamos encontrar o comando que nos foi dado. Também precisaremos de NoCommand e HelpCommand.
- NoCommand - será responsável pela situação em que a mensagem não comece com nenhum comando;
- HelpCommand será um guia para o usuário, uma espécie de documentação.
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;
import static com.github.javarushcommunity.jrtb.command.CommandName.*;
/**
* Help {@link Command}.
*/
public class HelpCommand implements Command {
private final SendBotMessageService sendBotMessageService;
public static final String HELP_MESSAGE = String.format("✨<b>Дотупные команды</b>✨\n\n"
+ "<b>Начать\\закончить работу с ботом</b>\n"
+ "%s - начать работу со мной\n"
+ "%s - приостановить работу со мной\n\n"
+ "%s - получить помощь в работе со мной\n",
START.getCommandName(), STOP.getCommandName(), HELP.getCommandName());
public HelpCommand(SendBotMessageService sendBotMessageService) {
this.sendBotMessageService = sendBotMessageService;
}
@Override
public void execute(Update update) {
sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), HELP_MESSAGE);
}
}
Nenhum comando:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;
/**
* No {@link Command}.
*/
public class NoCommand implements Command {
private final SendBotMessageService sendBotMessageService;
public static final String NO_MESSAGE = "Я поддерживаю команды, начинающиеся со слеша(/).\n"
+ "Whatбы посмотреть список команд введите /help";
public NoCommand(SendBotMessageService sendBotMessageService) {
this.sendBotMessageService = sendBotMessageService;
}
@Override
public void execute(Update update) {
sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), NO_MESSAGE);
}
}
E para esta tarefa ainda existe o UnknownCommand:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;
/**
* Unknown {@link Command}.
*/
public class UnknownCommand implements Command {
public static final String UNKNOWN_MESSAGE = "Не понимаю вас \uD83D\uDE1F, напишите /help чтобы узнать что я понимаю.";
private final SendBotMessageService sendBotMessageService;
public UnknownCommand(SendBotMessageService sendBotMessageService) {
this.sendBotMessageService = sendBotMessageService;
}
@Override
public void execute(Update update) {
sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), UNKNOWN_MESSAGE);
}
}
A seguir, vamos adicionar um contêiner para nossos comandos. Ele armazenará nossos objetos de comando e, mediante solicitação, esperamos receber o comando necessário. Vamos chamá-lo de CommandContainer :
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.google.common.collect.ImmutableMap;
import static com.github.javarushcommunity.jrtb.command.CommandName.*;
/**
* Container of the {@link Command}s, which are using for handling telegram commands.
*/
public class CommandContainer {
private final ImmutableMap<String, Command> commandMap;
private final Command unknownCommand;
public CommandContainer(SendBotMessageService sendBotMessageService) {
commandMap = ImmutableMap.<string, command="">builder()
.put(START.getCommandName(), new StartCommand(sendBotMessageService))
.put(STOP.getCommandName(), new StopCommand(sendBotMessageService))
.put(HELP.getCommandName(), new HelpCommand(sendBotMessageService))
.put(NO.getCommandName(), new NoCommand(sendBotMessageService))
.build();
unknownCommand = new UnknownCommand(sendBotMessageService);
}
public Command retrieveCommand(String commandIdentifier) {
return commandMap.getOrDefault(commandIdentifier, unknownCommand);
}
}
Como você pode ver, tudo foi feito de forma simples. Temos um mapa imutável com uma chave na forma de um valor de comando e um valor na forma de um objeto de comando do tipo Command. No construtor, preenchemos o mapa imutável uma vez e o acessamos durante toda a operação da aplicação. O principal e único método para trabalhar com o contêiner é retrieveCommand(String commandIdentifier) . Existe um comando chamado UnknownCommand, que é responsável pelos casos em que não conseguimos encontrar o comando correspondente. Agora estamos prontos para implementar o contêiner em nossa classe de bot - em JavaRushTelegramBot: Esta é a aparência de nossa classe de bot agora:
package com.github.javarushcommunity.jrtb.bot;
import com.github.javarushcommunity.jrtb.command.CommandContainer;
import com.github.javarushcommunity.jrtb.service.SendBotMessageServiceImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.objects.Update;
import static com.github.javarushcommunity.jrtb.command.CommandName.NO;
/**
* Telegram bot for Javarush Community from Javarush community.
*/
@Component
public class JavarushTelegramBot extends TelegramLongPollingBot {
public static String COMMAND_PREFIX = "/";
@Value("${bot.username}")
private String username;
@Value("${bot.token}")
private String token;
private final CommandContainer commandContainer;
public JavarushTelegramBot() {
this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this));
}
@Override
public void onUpdateReceived(Update update) {
if (update.hasMessage() && update.getMessage().hasText()) {
String message = update.getMessage().getText().trim();
if (message.startsWith(COMMAND_PREFIX)) {
String commandIdentifier = message.split(" ")[0].toLowerCase();
commandContainer.retrieveCommand(commandIdentifier).execute(update);
} else {
commandContainer.retrieveCommand(NO.getCommandName()).execute(update);
}
}
}
@Override
public String getBotUsername() {
return username;
}
@Override
public String getBotToken() {
return token;
}
}
E pronto, as alterações no código estão concluídas. Como posso verificar isso? Você precisa iniciar o bot e verificar se tudo funciona. Para fazer isso, atualizo o token em application.properties, defino o correto e inicio o aplicativo na classe JavarushTelegramBotApplication: Agora precisamos verificar se os comandos funcionam conforme o esperado. Eu verifico passo a passo:
- PararComando;
- IniciarComando;
- Comando de ajuda;
- Nenhum comando;
- Comando desconhecido.
GO TO FULL VERSION