Ciao a tutti, cari amici. Oggi implementeremo un template (template è un pattern, nel nostro contesto è la stessa cosa) di Command design per le nostre esigenze. Utilizzando questo modello, lavoreremo comodamente e correttamente con l'elaborazione dei comandi del nostro bot.
Per cominciare, sarebbe bene parlare di che tipo di modello è questo: Comando. Ma se lo faccio, l'articolo risulterà molto grande e ingombrante. Pertanto, ho scelto i materiali per lo studio autonomo:
Amici, vi piace il progetto Javarush Telegram Bot ? Non essere pigro: dagli una stella . In questo modo sarà chiaro che è interessante e sarà più piacevole svilupparlo! |
- Questo è il mio articolo di 4 anni fa. L’ho scritto quando ero junior, quindi non giudicarlo troppo severamente.
- Video di uno svedese molto emotivo e interattivo su YouTube. Lo consiglio vivamente Parla magnificamente, il suo inglese è chiaro e comprensibile. E in generale, ha un video su altri modelli di design.
- Nei commenti al mio articolo, qualcuno Nullptr35 ha consigliato questo video .
Scriviamo JRTB-3
Tutto è come prima:- Aggiorniamo il ramo principale.
- Sulla base del ramo principale aggiornato, creiamo un nuovo JRTB-3 .
- Implementiamo il modello.
- Creiamo un nuovo commit che descrive il lavoro svolto.
- Creiamo una pull request, la controlliamo e se è tutto ok uniamo il nostro lavoro.
if (message.startsWith("/start")) {
doStartCommand();
} else if(message.startsWith("/stop")) {
doStopCommand();
} else if(message.startsWith("/addUser")) {
doAddUserCommand();
}
...
else if(message.startsWith("/makeMeHappy")) {
doMakeMeHappyCommand();
}
Inoltre, dove c'è un'ellissi, potrebbero esserci diverse dozzine di squadre in più. E come gestirlo normalmente? Come sostenere? Difficile e difficile. Ciò significa che questa opzione non è adatta a noi. Dovrebbe assomigliare a qualcosa di simile a questo:
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);
}
È tutto! E non importa quanti comandi aggiungiamo, questa sezione di codice rimarrà invariata. Cosa sta facendo? Il primo if fa in modo che il messaggio inizi con il prefisso di comando "/". Se questo è il caso, allora selezioniamo la riga fino al primo spazio e cerchiamo il comando corrispondente nel CommandContainer; non appena lo troviamo, eseguiamo il comando. E questo è tutto...) Se hai voglia e tempo, puoi implementare il lavoro in team, prima in una classe alla volta, con una serie di condizioni e tutto il resto, e poi utilizzando un modello. Vedrai la differenza. Che bellezza sarà! Per prima cosa creiamo un pacchetto accanto al pacchetto bot, che verrà chiamato command . E già in questo pacchetto ci saranno tutte le classi che riguardano l'implementazione del comando. Abbiamo bisogno di un'interfaccia per lavorare con i comandi. In questo caso, creiamolo:
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);
}
A questo punto, non è necessario implementare l'operazione inversa del comando, quindi salteremo questo metodo (unexecute). Nel metodo di esecuzione, l'oggetto Update viene fornito come argomento , esattamente quello che arriva al nostro metodo principale nel bot. Questo oggetto conterrà tutto il necessario per elaborare il comando. Successivamente, aggiungeremo un'enumerazione che memorizzerà i valori dei comandi (start, stop e così via). perché ne abbiamo bisogno? In modo che abbiamo una sola fonte di verità per i nomi delle squadre. Lo creiamo anche nel nostro pacchetto comandi . Chiamiamolo NomeComando :
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;
}
}
Abbiamo anche bisogno di un servizio che invii messaggi tramite un bot. Per fare ciò, creeremo un pacchetto di servizi accanto al comando pacchetto , al quale aggiungeremo tutti i servizi necessari. Qui vale la pena soffermarsi su cosa intendo in questo caso con la parola servizio. Se consideriamo un'applicazione, è spesso divisa in più livelli: un livello per lavorare con gli endpoint - controller, un livello di logica aziendale - servizi e un livello per lavorare con il database - un repository. Pertanto, nel nostro caso, un servizio è una classe che implementa una sorta di logica aziendale. Come creare correttamente un servizio? Innanzitutto, crea un'interfaccia e un'implementazione. Aggiungi l'implementazione utilizzando l'annotazione `@Service` al contesto dell'applicazione della nostra applicazione SpringBoot e, se necessario, rafforzala utilizzando l'annotazione `@Autowired`. Creiamo quindi l'interfaccia SendBotMessageService (nei nomi dei servizi solitamente si aggiunge Service alla fine del 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);
}
Successivamente, creiamo la sua implementazione:
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();
}
}
}
Ecco come appare l'implementazione. La magia più importante è dove viene creato il designer. Utilizzando l'annotazione @Autowired sul costruttore, SpringBoot cercherà un oggetto di questa classe nel suo contesto applicativo. Ed è già lì. Funziona così: nella nostra applicazione, ovunque possiamo accedere al bot e fare qualcosa. E questo servizio è responsabile dell'invio di messaggi. Per non scrivere ogni volta e in ogni luogo qualcosa del genere:
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();
}
Abbiamo spostato questa logica in una classe separata e la utilizzeremo se necessario. Ora dobbiamo implementare tre comandi: StartCommand, StopCommand e UnknownCommand. Ne abbiamo bisogno per avere qualcosa con cui riempire il nostro contenitore di comandi. Per ora i testi saranno aridi e poco informativi; ai fini di questo compito, questo non è molto importante. Quindi, 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);
}
}
Si prega di leggere attentamente i commenti prima del progettista. La dipendenza circolare ( dipendenza circolare ) può verificarsi a causa di un'architettura che non è del tutto corretta. Nel nostro caso, ci assicureremo che tutto funzioni e sia corretto. L'oggetto reale dal contesto dell'applicazione verrà aggiunto durante la creazione del comando già nel CommandContainer. Comando di arresto:
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 sconosciuto. Perchè ne abbiamo bisogno? Per noi questo è un comando importante a cui risponderemo se non riuscissimo a trovare il comando che ci è stato dato. Avremo bisogno anche di NoCommand e HelpCommand.
- NoCommand: sarà responsabile della situazione in cui il messaggio non inizia affatto con un comando;
- HelpCommand sarà una guida per l'utente, una sorta di documentazione.
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);
}
}
Nessun 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 per questo compito c'è ancora 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);
}
}
Successivamente, aggiungiamo un contenitore per i nostri comandi. Memorizzerà i nostri oggetti comando e su richiesta ci aspettiamo di ricevere il comando richiesto. Chiamiamolo 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);
}
}
Come puoi vedere, tutto è stato fatto semplicemente. Abbiamo una mappa immutabile con una chiave sotto forma di valore di comando e un valore sotto forma di oggetto comando di tipo Command. Nel costruttore compiliamo una volta la mappa immutabile e vi accediamo durante il funzionamento dell'applicazione. Il metodo principale e unico per lavorare con il contenitore è retrieveCommand(String commandIdentifier) . Esiste un comando chiamato UnknownCommand, che è responsabile dei casi in cui non riusciamo a trovare il comando corrispondente. Ora siamo pronti per implementare il contenitore nella nostra classe bot - in JavaRushTelegramBot: Ecco come appare ora la nostra classe bot:
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 questo è tutto, le modifiche al codice sono completate. Come posso verificarlo? Devi avviare il bot e verificare che tutto funzioni. Per fare questo aggiorno il token in application.properties, imposto quello corretto e lancio l'applicazione nella classe JavarushTelegramBotApplication: ora dobbiamo verificare che i comandi funzionino come previsto. Lo controllo passo dopo passo:
- InterrompiComando;
- ComandoAvvio;
- AiutoComando;
- Nessun comando;
- Comando sconosciuto.
GO TO FULL VERSION