JavaRush /Java Blog /Random-IT /Implementiamo il modello di comando per lavorare con il b...
Roman Beekeeper
Livello 35

Implementiamo il modello di comando per lavorare con il bot. (Parte 1) - "Progetto Java dalla A alla Z"

Pubblicato nel gruppo Random-IT
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. "Progetto Java dalla A alla Z": implementazione di un modello di comando per lavorare con un bot.  Parte 1 - 1
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!
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:
  1. Questo è il mio articolo di 4 anni fa. L’ho scritto quando ero junior, quindi non giudicarlo troppo severamente.
  2. 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.
  3. Nei commenti al mio articolo, qualcuno Nullptr35 ha consigliato questo video .
Questo dovrebbe essere sufficiente per immergerti nell'argomento ed essere sulla mia stessa lunghezza d'onda. Bene, coloro che hanno familiarità con questo modello di progettazione possono tranquillamente saltarlo e andare avanti.

Scriviamo JRTB-3

Tutto è come prima:
  1. Aggiorniamo il ramo principale.
  2. Sulla base del ramo principale aggiornato, creiamo un nuovo JRTB-3 .
  3. Implementiamo il modello.
  4. Creiamo un nuovo commit che descrive il lavoro svolto.
  5. Creiamo una pull request, la controlliamo e se è tutto ok uniamo il nostro lavoro.
Non mostrerò i punti 1-2: li ho descritti con molta attenzione negli articoli precedenti, quindi procediamo direttamente alla realizzazione del template. Perché questo modello è adatto a noi? Sì, perché ogni volta che eseguiamo un comando, andremo al metodo onUpdateReceived(Update update) e a seconda del comando eseguiremo una logica diversa. Senza questo schema avremmo tutta una serie di istruzioni if-else if. Qualcosa come questo:
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 . "Progetto Java dalla A alla Z": implementazione di un modello di comando per lavorare con un bot.  Parte 1 - 2E 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.
Aggiungiamo HelpCommand:
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: "Progetto Java dalla A alla Z": implementazione di un modello di comando per lavorare con un bot.  Parte 1 - 3ora dobbiamo verificare che i comandi funzionino come previsto. Lo controllo passo dopo passo:
  • InterrompiComando;
  • ComandoAvvio;
  • AiutoComando;
  • Nessun comando;
  • Comando sconosciuto.
Ecco cosa è successo: "Progetto Java dalla A alla Z": implementazione di un modello di comando per lavorare con un bot.  Parte 1 - 4il bot ha funzionato esattamente come ci aspettavamo. Continua tramite collegamento .

Un elenco di tutti i materiali della serie si trova all'inizio di questo articolo.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION