JavaRush /Java блогы /Random-KK /Ботпен жұмыс істеу үшін Пәрмен үлгісін іске асырайық. (1-...
Roman Beekeeper
Деңгей

Ботпен жұмыс істеу үшін Пәрмен үлгісін іске асырайық. (1-бөлім) - «Java жобасы А-дан Я-ға дейін»

Топта жарияланған
Баршаңызға сәлем, қымбатты достар. Бүгін біз өз қажеттіліктеріміз үшін командалық дизайн үлгісін (үлгі - үлгі, біздің контекстімізде бұл бірдей нәрсе) енгіземіз. Осы үлгіні пайдалана отырып, біз бот командаларын өңдеумен ыңғайлы және дұрыс жұмыс істейміз. «А-дан Я-ға Java жобасы»: ботпен жұмыс істеуге арналған пәрмен үлгісін енгізу.  1 - 1 бөлім
Достар, Javarush Telegram Bot жобасы ұнайды ма ? Жалқау болмаңыз: оған жұлдыз беріңіз . Осылайша оның қызықты екені белгілі болады және оны дамыту әлдеқайда жағымды болады!
Бастау үшін бұл қандай үлгі екенін айту жақсы болар еді - Пәрмен. Бірақ мен мұны істесем, мақала өте үлкен және ауыр болады. Сондықтан мен өз бетімен оқуға арналған материалдарды таңдадым:
  1. Бұл менің 4 жыл бұрынғы мақалам . Мен оны жас кезімде жаздым, сондықтан оны қатты айыптамаңыз.
  2. YouTube-тегі өте эмоционалды және интерактивті шведтің бейнесі. Мен оны өте ұсынамын. Ол әдемі сөйлейді, ағылшын тілі анық және түсінікті. Жалпы, оның басқа дизайн үлгілері туралы бейнебаяны бар.
  3. Менің мақалама түсініктемелерде біреу Nullptr35 осы бейнені ұсынды .
Бұл тақырыпқа еніп, менімен бір бетте болу үшін жеткілікті болуы керек. Бұл дизайн үлгісімен таныс адамдар қауіпсіз өтіп, әрі қарай жүре алады.

Біз JRTB-3 жазамыз

Бәрі бұрынғыдай:
  1. Біз негізгі фorалды жаңартамыз.
  2. Жаңартылған негізгі фorал негізінде біз жаңа JRTB-3 жасаймыз .
  3. Үлгіні орындайық.
  4. Біз орындалған жұмысты сипаттайтын жаңа міндеттеме жасаймыз.
  5. Біз тарту сұрауын жасаймыз, оны тексереміз және бәрі дұрыс болса, жұмысымызды біріктіреміз.
Мен 1-2 тармақтарды көрсетпеймін: мен оларды алдыңғы мақалаларда өте мұқият сипаттадым, сондықтан үлгіні енгізуге көшейік. Неліктен бұл үлгі бізге қолайлы? Иә, өйткені пәрменді орындаған сайын біз onUpdateReceived(Update update) әдісіне өтеміз және пәрменге байланысты әртүрлі логиканы орындаймыз. Бұл үлгі болмаса, бізде if-else if операторларының толық жиынтығы болар еді. Сол сияқты бірнәрсе:
if (message.startsWith("/start")) {
   doStartCommand();
} else if(message.startsWith("/stop")) {
   doStopCommand();
} else if(message.startsWith("/addUser")) {
   doAddUserCommand();
}
...
else if(message.startsWith("/makeMeHappy")) {
   doMakeMeHappyCommand();
}
Оның үстіне, эллипс бар жерде тағы бірнеше ондаған команда болуы мүмкін. Ал мұны қалыпты жағдайда қалай шешуге болады? Қалай қолдау керек? Қиын және қиын. Бұл бұл опцияның бізге сәйкес келмейтінін білдіреді. Ол келесідей көрінуі керек:
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);
}
Болды! Қанша команда қоссақ та, codeтың бұл бөлімі өзгеріссіз қалады. Ол не істеп жатыр? Бірінші if хабарламаның «/» пәрмен префиксімен басталатынына көз жеткізеді. Егер солай болса, онда біз бірінші бос орынға дейінгі жолды таңдаймыз және CommandContainer ішінен сәйкес пәрменді іздейміз, оны тапқан бойда команданы іске қосамыз. Міне, осының бәрі...) Егер сізде тілек пен уақыт болса, сіз командалармен жұмыс жасай аласыз, алдымен бір сыныпта бірден, көптеген шарттармен және осының барлығымен, содан кейін үлгіні пайдалана отырып. Сіз айырмашылықты көресіз. Бұл қандай сұлулық болады! Алдымен, команда деп аталатын бот бумасының жанында бума жасайық . «А-дан Я-ға Java жобасы»: ботпен жұмыс істеуге арналған пәрмен үлгісін енгізу.  1-2 бөлімБұл пакетте команданы орындауға қатысты барлық сыныптар болады. Командалармен жұмыс істеу үшін бізге бір интерфейс қажет. Бұл жағдайда оны жасайық:
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);
}
Бұл кезде пәрменнің кері әрекетін орындаудың қажеті жоқ, сондықтан біз бұл әдісті өткізіп жібереміз (орындау). Орындау әдісінде Жаңарту нысаны аргумент ретінде келеді - дәл біздің боттағы негізгі әдісімізге келетін нәрсе. Бұл нысанда пәрменді өңдеуге қажетті барлық нәрсе болады. Әрі қарай, команда мәндерін сақтайтын нөмірді қосамыз (бастау, тоқтату және т.б.). Бұл бізге не үшін керек? Осылайша, бізде команда атаулары үшін бір ғана шындық көзі бар. Біз оны командалар бумасында да жасаймыз . Оны 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;
   }

}
Сондай-ақ бізге хабарларды бот арқылы жіберетін қызмет қажет. Ол үшін біз командалар пакетінің жанында қызмет пакетін жасаймыз , оған біз барлық қажетті қызметтерді қосамыз. Бұл жағдайда қызмет деген сөзбен нені білдіретініме назар аударған жөн. Қолданбаны қарастыратын болсақ, ол көбінесе бірнеше қабаттарға бөлінеді: соңғы нүктелермен жұмыс істеуге арналған деңгей – контроллерлер, бизнес-логика деңгейі – қызметтер және мәліметтер қорымен жұмыс істеу қабаты – репозиторий. Сондықтан, біздің жағдайда қызмет дегеніміз бизнес-логиканың қандай да бір түрін жүзеге асыратын класс. Қызметті қалай дұрыс құруға болады? Біріншіден, оның интерфейсін және іске асыруды жасаңыз. Біздің SpringBoot қолданбасының Қолданба мәтінмәніне `@Service` annotationсын пайдаланып енгізуді қосыңыз және қажет болса, оны `@Autowired` annotationсын пайдаланып қатайтыңыз. Сондықтан, біз SendBotMessageService интерфейсін жасаймыз (аттау қызметтерінде олар әдетте аттың соңына Қызметті қосады):
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);
}
Әрі қарай, біз оның орындалуын жасаймыз:
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();
       }
   }
}
Іске асыру осылай көрінеді. Ең маңызды сиқыр - дизайнердің қай жерде құрылғаны. Конструктордағы @Autowired annotationсын пайдаланып, SpringBoot қолданба контекстінде осы сыныптың нысанын іздейді. Және ол қазірдің өзінде бар. Ол келесідей жұмыс істейді: біздің қолданбада кез келген жерде біз ботқа кіріп, бірдеңе жасай аламыз. Ал бұл қызмет хабарламаларды жіберуге жауапты. Біз әр жерде мұндай нәрсені жазбау үшін:
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();
}
Біз бұл логиканы бөлек сыныпқа көшірдік және қажет болған жағдайда оны пайдаланамыз. Енді біз үш команданы орындауымыз керек: StartCommand, StopCommand және UnknownCommand. Олар бізге командалар үшін контейнерді толтыратын нәрсе болуы үшін қажет. Әзірге мәтіндер құрғақ және ақпаратсыз болады; бұл тапсырманың мақсаттары үшін бұл өте маңызды емес. Сонымен, 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);
   }
}
Дизайнердің алдында түсініктемелерді мұқият оқып шығыңыз. Дөңгелек тәуелділік ( айналмалы тәуелділік ) дұрыс емес архитектураға байланысты болуы мүмкін. Біздің жағдайда біз бәрі жұмыс істейтініне және дұрыс екеніне көз жеткіземіз. Қолданба контекстіндегі нақты нысан CommandContainer ішіндегі пәрменді жасау кезінде қосылады. Тоқтату командасы:
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);
   }
}
Және белгісіз пәрмен. Ол бізге не үшін керек? Біз үшін бұл бізге берілген пәрменді таба алмасақ жауап беретін маңызды бұйрық. Сондай-ақ бізге NoCommand және HelpCommand қажет болады.
  • NoCommand – хабарлама мүлде командадан басталмаған жағдайға жауапты болады;
  • 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);
   }
}
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);
   }
}
Бұл тапсырма үшін әлі 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);
   }
}
Әрі қарай командаларымыз үшін контейнерді қосамыз. Ол біздің командалық нысандарды сақтайды және сұрау бойынша біз қажетті пәрменді аламыз деп күтеміз. Оны 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);
   }

}
Көріп отырғаныңыздай, бәрі қарапайым болды. Бізде командалық мән түріндегі кілті және Command типті пәрмен нысаны түріндегі мәні бар өзгермейтін карта бар. Конструкторда біз өзгермейтін картаны бір рет толтырамыз және оған қолданба жұмысы барысында қол жеткіземіз. Контейнермен жұмыс істеудің негізгі және жалғыз әдісі - retrieveCommand(String commandIdentifier) . Белгісіз пәрмен деп аталатын пәрмен бар, ол сәйкес команданы таба алмаған жағдайларға жауап береді. Енді біз контейнерді JavaRushTelegramBot жүйесінде бот класына енгізуге дайынбыз: Біздің бот класы енді осылай көрінеді:
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;
   }
}
Міне, codeқа өзгертулер енгізу аяқталды. Мұны қалай тексеруге болады? Сізге ботты іске қосып, бәрі жұмыс істейтінін тексеру керек. Ол үшін application.properties ішіндегі таңбалауышты жаңартамын, дұрысын орнатамын және JavarushTelegramBotApplication сыныбында қолданбаны іске қосамын: «А-дан Я-ға Java жобасы»: ботпен жұмыс істеуге арналған пәрмен үлгісін енгізу.  1 - 3 бөлімЕнді пәрмендердің күтілгендей жұмыс істейтінін тексеру керек. Мен оны кезең-кезеңімен тексеремін:
  • StopCommand;
  • Бастау пәрмені;
  • HelpCommand;
  • NoCommand;
  • Белгісіз пәрмен.
Міне, болды: «А-дан Я-ға Java жобасы»: ботпен жұмыс істеуге арналған пәрмен үлгісін енгізу.  1-4 бөлімбот біз күткендей жұмыс істеді. Жалғасы сілтеме арқылы .

Сериядағы барлық материалдардың тізімі осы мақаланың басында.

Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION