JavaRush /Java блогу /Random-KY /Келгиле, бот менен иштөө үчүн буйрук үлгүсүн ишке ашыралы...
Roman Beekeeper
Деңгээл

Келгиле, бот менен иштөө үчүн буйрук үлгүсүн ишке ашыралы. (1-бөлүк) - "Java долбоору Адан Яга чейин"

Группада жарыяланган
Баарыңарга салам, кымбаттуу достор. Бүгүн биз муктаждыктарыбыз үчүн Command дизайнынын шаблонун ишке ашырабыз (шаблон - бул үлгү, биздин контекстте бул бир эле нерсе). Бул шаблонду колдонуу менен биз боттун буйруктарын иштетүү менен ыңгайлуу жана туура иштейбиз. "Java долбоору Адан Яга": Бот менен иштөө үчүн буйрук үлгүсүн ишке ашыруу.  1-1-бөлүк
Достор, сизге Javarush Telegram Bot долбоору жагабы ? Жалкоо болбоңуз: ага жылдыз бериңиз . Ошентип, анын кызыктуу экени айкын болот жана аны өнүктүрүү жагымдуураак болот!
Баштоо үчүн, бул кандай үлгү жөнүндө сөз кылуу жакшы болмок - Буйрук. Бирок мен муну кылсам, макала абдан чоң жана түйшүктүү болот. Ошондуктан, мен өз алдынча изилдөө үчүн материалдарды тандап:
  1. Бул менин 4 жыл мурунку макалам . Мен аны кичүү кезимде жазганмын, ошондуктан катуу сынга албагыла.
  2. YouTube'да абдан эмоционалдуу жана интерактивдүү шведдин видеосу. Мен аны абдан сунуштайм. Ал сонун сүйлөйт, анын англисчеси так жана түшүнүктүү. Ал эми жалпысынан алганда, анын башка дизайн үлгүлөрү жөнүндө Video бар.
  3. Менин макалама комментарийлерде кимдир бирөө Nullptr35 бул видеону сунуштады .
Бул темага кирип, мени менен бир бетте болуу үчүн жетиштүү болушу керек. Ооба, бул дизайн үлгүсү менен тааныш болгондор аман-эсен өткөрүп жиберип, уланта алышат.

Биз JRTB-3 жазабыз

Баары мурункудай эле:
  1. Биз негизги фorалды жаңыртабыз.
  2. Жаңыртылган негизги бутагынын негизинде биз жаңы 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);
}
Бул учурда, буйруктун тескери операциясын ишке ашыруунун кереги жок, андыктан бул ыкманы өткөрүп жиберебиз (аткаруудан). Аткаруу методунда Жаңыртуу an objectиси аргумент катары келет - дал ушул боттогу биздин негизги методубузга келген нерсе. Бул an object буйрукту иштетүү үчүн зарыл болгон нерселердин баарын камтыйт. Андан кийин, биз буйрук маанилерин сактай турган энум кошобуз (баштоо, токтотуу жана башкалар). Бул бизге эмне үчүн керек? Ошентип, бизде командалардын аттары үчүн бир гана чындык булагы бар. Биз аны командалык топтомубузда да түзөбүз . Аны 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 Колдонмо контекстинде ушул класстын an objectисин издейт. Ал эми ал жерде. Ал мындай иштейт: биздин тиркемеде биз ботко кирип, бир нерсе кыла алабыз. Ал эми бул кызмат билдирүүлөрдү жөнөтүү үчүн жооптуу. Ар бир жерде мындай нерселерди жазбаш үчүн:
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);
   }
}
Дизайнердин алдында комментарийлерди кунт коюп окуп чыгыңыз. Tagерек көз карандылык ( тегерек көз карандылык ) туура эмес архитектурадан улам пайда болушу мүмкүн. Биздин учурда, биз бардыгынын иштешине жана туура экендигине ынанабыз. Колдонмо контекстиндеги чыныгы an object CommandContainer ичинде буйрукту түзүүдө кошулат. StopCommand:
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);
   }
}
Жана UnknownCommand. Эмне үчүн муктажбыз? Биз үчүн бул бизге берилген буйрукту таба албасак жооп бере турган маанилүү бир буйрук. Ошондой эле бизге NoCommand жана HelpCommand керек болот.
  • NoCommand - билдирүү такыр буйрук менен башталбаган кырдаал үчүн жооптуу болот;
  • HelpCommand колдонуучу үчүн жол көрсөткүч, documentтин бир түрү болот.
Келгиле, 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);
   }
}
Андан кийин, биздин буйруктар үчүн контейнер кошобуз. Ал биздин командалык an objectтерди сактайт жана суроо-талап боюнча биз талап кылынган буйрукту кабыл алабыз. Аны 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 тибиндеги буйрук an objectи түрүндөгү мааниге ээ өзгөрүлгүс карта бар. Конструктордо биз өзгөрүлгүс картаны бир жолу толтурабыз жана ага тиркеменин бүткүл иштөөсүнө киребиз. Контейнер менен иштөөнүн негизги жана жалгыз ыкмасы - бул 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;
  • StartCommand;
  • HelpCommand;
  • NoCommand;
  • UnknownCommand.
Бул жерде эмне болду: "Java долбоору Адан Яга": Бот менен иштөө үчүн буйрук үлгүсүн ишке ашыруу.  1-4-бөлүкбот биз күткөндөй иштеди. Шилтеме аркылуу уланды .

Сериядагы бардык материалдардын тизмеси ушул макаланын башында.

Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION