JavaRush /Java Blogu /Random-AZ /Gəlin botla işləmək üçün Əmr Modelini həyata keçirək. (1-...
Roman Beekeeper
Səviyyə

Gəlin botla işləmək üçün Əmr Modelini həyata keçirək. (1-ci hissə) - "A-dan Z-yə Java layihəsi"

Qrupda dərc edilmişdir
Hər kəsə salam əziz dostlar. Bu gün ehtiyaclarımız üçün Komanda dizaynının şablonunu (şablon nümunədir, bizim kontekstimizdə eyni şeydir) tətbiq edəcəyik. Bu şablondan istifadə edərək, biz botun əmrlərinin işlənməsi ilə rahat və düzgün işləyəcəyik. "A-dan Z-yə Java layihəsi": Botla işləmək üçün Əmr Modelinin həyata keçirilməsi.  1-1 hissə
Dostlar, Javarush Telegram Bot layihəsini bəyənirsinizmi ? Tənbəl olmayın: ona bir ulduz verin . Bu yolla onun maraqlı olduğu aydın olacaq və onu inkişaf etdirmək daha xoş olacaq!
Başlamaq üçün bunun necə bir nümunə olduğu barədə danışmaq yaxşı olardı - Əmr. Amma bunu etsəm, məqalə çox böyük və çətin olacaq. Buna görə də öz-özünə iş üçün material seçdim:
  1. Bu mənim 4 il əvvəlki məqaləmdir. Mən bunu kiçik olanda yazmışdım, ona görə də çox sərt mühakimə etməyin.
  2. YouTube-da çox emosional və interaktiv İsveçlinin videosu. Mən bunu çox tövsiyə edirəm. Gözəl danışır, ingilis dili aydın və başa düşüləndir. Və ümumiyyətlə, onun digər dizayn nümunələri haqqında videosu var.
  3. Məqaləmə şərhlərdə kimsə Nullptr35 bu videonu tövsiyə etdi .
Bu, özünüzü mövzuya qərq etmək və mənimlə eyni səhifədə olmaq üçün kifayət olmalıdır. Yaxşı, bu dizayn nümunəsi ilə tanış olanlar təhlükəsiz şəkildə atlaya və davam edə bilərlər.

JRTB-3 yazırıq

Hər şey əvvəlki kimidir:
  1. Əsas filialı yeniləyirik.
  2. Yenilənmiş əsas filial əsasında biz yeni JRTB-3 yaradırıq .
  3. Nümunəni həyata keçirək.
  4. Görülən işləri təsvir edən yeni bir öhdəlik yaradırıq.
  5. Biz çəkmə sorğusu yaradırıq, yoxlayırıq və hər şey qaydasındadırsa, işimizi birləşdiririk.
1-2-ci nöqtələri göstərməyəcəyəm: onları əvvəlki məqalələrdə çox diqqətlə təsvir etdim, ona görə də şablonun tətbiqinə keçək. Bu şablon niyə bizim üçün uyğundur? Bəli, çünki biz hər dəfə əmr icra etdikdə onUpdateReceived(Update update) metoduna keçəcəyik və əmrdən asılı olaraq fərqli məntiq icra edəcəyik. Bu nümunə olmasaydı, bizdə if-else if ifadələri olardı. Bu kimi bir şey:
if (message.startsWith("/start")) {
   doStartCommand();
} else if(message.startsWith("/stop")) {
   doStopCommand();
} else if(message.startsWith("/addUser")) {
   doAddUserCommand();
}
...
else if(message.startsWith("/makeMeHappy")) {
   doMakeMeHappyCommand();
}
Üstəlik, ellips olan yerdə daha bir neçə onlarla komanda ola bilər. Və bunu normal şəkildə necə idarə etmək olar? Necə dəstəkləmək olar? Çətin və çətin. Bu o deməkdir ki, bu variant bizə uyğun deyil. Bu kimi bir şey görünməlidir:
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);
}
Hamısı budur! Və nə qədər əmr əlavə etsək də, kodun bu bölməsi dəyişməz qalacaq. O nə edir? Birinci if, mesajın "/" əmr prefiksi ilə başladığına əmin olur. Əgər belədirsə, onda biz birinci boşluğa qədər olan xətti seçirik və CommandContainer-də müvafiq əmri axtarırıq, onu tapan kimi əmri icra edirik. Və hamısı budur...) Əgər arzunuz və vaxtınız varsa, əvvəlcə bir sinifdə birdən-birə, bir çox şərtlər və bütün bunlarla, sonra isə şablondan istifadə etməklə komandalarla işi həyata keçirə bilərsiniz. Siz fərqi görəcəksiniz. Nə gözəllik olacaq! Əvvəlcə bot paketinin yanında komanda adlanacaq bir paket yaradaq . "A-dan Z-yə Java layihəsi": Botla işləmək üçün Əmr Modelinin həyata keçirilməsi.  1-2 hissəVə artıq bu paketdə əmrin icrası ilə əlaqəli bütün siniflər olacaq. Komandalarla işləmək üçün bizə bir interfeys lazımdır. Bu halda, onu yaradaq:
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);
}
Bu nöqtədə əmrin tərs əməliyyatını həyata keçirməyə ehtiyacımız yoxdur, ona görə də bu metodu atlayacağıq (icra etmə). İcra metodunda Update obyekti arqument kimi gəlir - məhz botdakı əsas metodumuza gələn obyekt. Bu obyekt əmri emal etmək üçün lazım olan hər şeyi ehtiva edəcəkdir. Sonra, əmr dəyərlərini saxlayacaq bir nömrə əlavə edəcəyik (başlamaq, dayandırmaq və s.). Bu bizə niyə lazımdır? Beləliklə, komanda adları üçün yalnız bir həqiqət mənbəyimiz var. Biz də onu komanda paketimizdə yaradırıq . Gəlin onu CommandName adlandıraq :
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;
   }

}
Bizə bir bot vasitəsilə mesaj göndərəcək bir xidmət də lazımdır. Bunu etmək üçün əmr paketinin yanında bir xidmət paketi yaradacağıq , bütün lazımi xidmətləri əlavə edəcəyik. Burada bu halda xidmət sözü ilə nəyi nəzərdə tutduğuma diqqət yetirməyə dəyər. Tətbiqi nəzərdən keçirsək, o, çox vaxt bir neçə təbəqəyə bölünür: son nöqtələrlə işləmək üçün təbəqə - nəzarətçilər, biznes məntiqi təbəqəsi - xidmətlər və verilənlər bazası ilə işləmək üçün bir təbəqə - depo. Buna görə də, bizim vəziyyətimizdə xidmət bir növ biznes məntiqini həyata keçirən bir sinifdir. Xidməti necə düzgün yaratmaq olar? Birincisi, bunun üçün bir interfeys və bir tətbiq yaradın. SpringBoot tətbiqimizin Tətbiq Kontekstinə `@Service` annotasiyasından istifadə edərək tətbiqi əlavə edin və lazım gələrsə, `@Autowired` annotasiyasından istifadə edərək onu sıxın. Buna görə də, biz SendBotMessageService interfeysini yaradırıq (adlandırma xidmətlərində adətən adın sonuna Xidmət əlavə edirlər):
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);
}
Sonra onun icrasını yaradırıq:
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();
       }
   }
}
Tətbiq belə görünür. Ən vacib sehr dizaynerin yaradıldığı yerdir. Konstruktorda @Autowired annotasiyasından istifadə edərək, SpringBoot Tətbiq Kontekstində bu sinfin obyektini axtaracaq. Və o, artıq oradadır. Bu belə işləyir: tətbiqimizdə istənilən yerdə biz bota daxil olub nəsə edə bilərik. Və bu xidmət mesajların göndərilməsinə cavabdehdir. Hər yerdə hər dəfə belə bir şey yazmamaq üçün:
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();
}
Bu məntiqi ayrıca bir sinifə köçürdük və lazım gələrsə ondan istifadə edəcəyik. İndi üç əmri yerinə yetirməliyik: StartCommand, StopCommand və UnknownCommand. Onlara ehtiyacımız var ki, əmrlər üçün konteynerimizi dolduracaq bir şeyimiz olsun. Hələlik mətnlər quru və məlumatsız olacaq, bu tapşırığın məqsədləri üçün bu o qədər də vacib deyil. Beləliklə, 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);
   }
}
Dizaynerdən əvvəl şərhləri diqqətlə oxuyun. Dairəvi asılılıq ( dairəvi asılılıq ) tamamilə düzgün olmayan bir arxitekturaya görə baş verə bilər. Bizim vəziyyətimizdə hər şeyin işlədiyinə və düzgün olduğuna əmin olacağıq. Artıq CommandContainer-də əmr yaradılarkən Tətbiq Kontekstindən real obyekt əlavə olunacaq. Dayandır əmri:
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);
   }
}
Və Naməlum Əmr. Niyə bizə lazımdır? Bizim üçün bu, bizə verilən əmri tapa bilməsək cavab verəcək vacib bir əmrdir. Bizə NoCommand və HelpCommand da lazımdır.
  • NoCommand - mesajın ümumiyyətlə əmrlə başlamadığı vəziyyətə görə məsuliyyət daşıyacaq;
  • HelpCommand istifadəçi üçün bir bələdçi, bir növ sənəd olacaq.
HelpCommand əlavə edək:
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);
   }
}
NoCommand:
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);
   }
}
Və bu tapşırıq üçün hələ də UnknownCommand var:
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);
   }
}
Sonra əmrlərimiz üçün konteyner əlavə edək. O, əmr obyektlərimizi saxlayacaq və sorğu ilə biz tələb olunan əmri alacağımızı gözləyirik. Gəlin onu CommandContainer adlandıraq :
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);
   }

}
Gördüyünüz kimi, hər şey sadə şəkildə edildi. Əmr dəyəri şəklində açarı və Command tipli əmr obyekti şəklində dəyəri olan dəyişməz xəritəmiz var. Konstruktorda biz dəyişməz xəritəni bir dəfə doldururuq və tətbiqin bütün fəaliyyəti ərzində ona daxil oluruq. Konteynerlə işləmək üçün əsas və yeganə üsul retrieveCommand(String commandIdentifier) -dır . Müvafiq əmri tapa bilmədiyimiz hallara cavabdeh olan UnknownCommand adlı bir əmr var. İndi konteyneri bot sinifimizə tətbiq etməyə hazırıq - JavaRushTelegramBot-da: İndi bot sinifimiz belə görünür:
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;
   }
}
Və budur, kodda dəyişikliklər tamamlandı. Bunu necə yoxlaya bilərəm? Siz botu işə salmalı və hər şeyin işlədiyini yoxlamalısınız. Bunun üçün application.properties-də tokeni yeniləyirəm, düzgün olanı təyin edirəm və JavarushTelegramBotApplication sinfində tətbiqi işə salıram: "A-dan Z-yə Java layihəsi": Botla işləmək üçün Əmr Modelinin həyata keçirilməsi.  1-3 hissəİndi əmrlərin gözlənildiyi kimi işlədiyini yoxlamaq lazımdır. Addım-addım yoxlayıram:
  • StopCommand;
  • Başlanğıc əmri;
  • HelpCommand;
  • NoCommand;
  • Naməlum Əmr.
Nə baş verdi: "A-dan Z-yə Java layihəsi": Botla işləmək üçün Əmr Modelinin həyata keçirilməsi.  1-4 hissəBot tam gözlədiyimiz kimi işlədi. Link vasitəsilə davam edir .

Serialdakı bütün materialların siyahısı bu məqalənin əvvəlindədir.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION