JavaRush /Blog Java /Random-VI /Hãy triển khai Mẫu lệnh để làm việc với bot. (Phần 1) - "...
Roman Beekeeper
Mức độ

Hãy triển khai Mẫu lệnh để làm việc với bot. (Phần 1) - "Dự án Java từ A đến Z"

Xuất bản trong nhóm
Xin chào tất cả mọi người, các bạn thân mến. Hôm nay chúng ta sẽ triển khai một mẫu (mẫu là một mẫu, trong ngữ cảnh của chúng ta nó cũng giống như vậy) của thiết kế Lệnh cho nhu cầu của chúng ta. Sử dụng mẫu này, chúng tôi sẽ làm việc thuận tiện và chính xác với việc xử lý các lệnh của bot. "Dự án Java từ A đến Z": Triển khai Mẫu lệnh để làm việc với bot.  Phần 1 - 1
Các bạn ơi, bạn có thích dự án Javarush Telegram Bot không ? Đừng lười biếng: hãy cho nó một ngôi sao . Bằng cách này, rõ ràng là anh ấy rất thú vị và sẽ dễ chịu hơn khi phát triển anh ấy!
Để bắt đầu, sẽ rất tốt nếu nói về loại mẫu này - Lệnh. Nhưng nếu làm như vậy thì bài viết sẽ rất lớn và cồng kềnh. Vì vậy tôi chọn tài liệu để tự học:
  1. Đây là bài viết của tôi từ 4 năm trước. Tôi viết nó khi còn là học sinh cấp ba nên đừng phán xét nó quá khắt khe.
  2. Video về một người Thụy Điển rất giàu cảm xúc và tương tác trên YouTube. Tôi khuyên bạn nên nó. Anh ấy nói rất hay, tiếng Anh của anh ấy rõ ràng và dễ hiểu. Và nói chung, anh ấy có một video về các mẫu thiết kế khác.
  3. Trong phần bình luận cho bài viết của tôi, ai đó Nullptr35 đã đề xuất video này .
Điều này là đủ để bạn đắm mình vào chủ đề và ở cùng quan điểm với tôi. Chà, những người đã quen thuộc với mẫu thiết kế này có thể bỏ qua và tiếp tục một cách an toàn.

Chúng tôi viết JRTB-3

Mọi thứ vẫn như cũ:
  1. Chúng tôi cập nhật chi nhánh chính.
  2. Dựa trên nhánh chính được cập nhật, chúng tôi tạo JRTB-3 mới .
  3. Hãy thực hiện mô hình.
  4. Chúng tôi tạo một cam kết mới mô tả công việc được thực hiện.
  5. Chúng tôi tạo một yêu cầu kéo, kiểm tra nó và nếu mọi thứ đều ổn, chúng tôi sẽ hợp nhất công việc của mình.
Tôi sẽ không hiển thị các điểm 1-2: Tôi đã mô tả chúng rất cẩn thận trong các bài viết trước, vì vậy hãy tiến thẳng đến việc triển khai mẫu. Tại sao mẫu này phù hợp với chúng tôi? Có, bởi vì mỗi khi chúng ta thực thi một lệnh, chúng ta sẽ chuyển sang phương thức onUpdateReceived(Update update) và tùy theo lệnh mà chúng ta sẽ thực thi logic khác nhau. Nếu không có mẫu này, chúng ta sẽ có cả đống câu lệnh if-else if. Một cái gì đó như thế này:
if (message.startsWith("/start")) {
   doStartCommand();
} else if(message.startsWith("/stop")) {
   doStopCommand();
} else if(message.startsWith("/addUser")) {
   doAddUserCommand();
}
...
else if(message.startsWith("/makeMeHappy")) {
   doMakeMeHappyCommand();
}
Hơn nữa, ở đâu có dấu ba chấm thì có thể có thêm vài chục đội nữa. Và xử lý việc này thế nào cho bình thường? Làm thế nào để hỗ trợ? Khó khăn và khó khăn. Điều này có nghĩa là tùy chọn này không phù hợp với chúng tôi. Nó sẽ trông giống như thế này:
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);
}
Đó là tất cả! Và cho dù chúng ta có thêm bao nhiêu lệnh đi chăng nữa thì phần mã này vẫn không thay đổi. Anh ta đang làm gì vậy? Hàm if đầu tiên đảm bảo rằng thông báo bắt đầu bằng tiền tố lệnh "/". Nếu đúng như vậy thì chúng ta chọn dòng đến khoảng trống đầu tiên và tìm lệnh tương ứng trong CommandContainer; ngay khi tìm thấy, chúng ta chạy lệnh. Và chỉ vậy thôi...) Nếu có mong muốn và thời gian, bạn có thể triển khai làm việc với các nhóm, đầu tiên là trong một lớp cùng một lúc, với một loạt điều kiện và tất cả những thứ đó, sau đó sử dụng một mẫu. Bạn sẽ thấy sự khác biệt. Nó sẽ đẹp biết bao! Đầu tiên, hãy tạo một gói bên cạnh gói bot, gói này sẽ được gọi là command . "Dự án Java từ A đến Z": Triển khai Mẫu lệnh để làm việc với bot.  Phần 1 - 2Và trong gói này sẽ có tất cả các lớp liên quan đến việc thực hiện lệnh. Chúng ta cần một giao diện để làm việc với các lệnh. Đối với trường hợp này, hãy tạo nó:
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);
}
Tại thời điểm này, chúng ta không cần thực hiện thao tác đảo ngược của lệnh nên sẽ bỏ qua phương thức này (không thực thi). Trong phương thức thực thi, đối tượng Cập nhật xuất hiện dưới dạng đối số - chính xác là đối số xuất hiện trong phương thức chính của chúng ta trong bot. Đối tượng này sẽ chứa mọi thứ cần thiết để xử lý lệnh. Tiếp theo, chúng ta sẽ thêm một enum sẽ lưu trữ các giá trị lệnh (bắt đầu, dừng, v.v.). Tại sao chúng ta cần điều này? Vì vậy, chúng tôi chỉ có một nguồn sự thật về tên các đội. Chúng tôi cũng tạo nó trong gói lệnh của mình . Hãy gọi nó là 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;
   }

}
Chúng tôi cũng cần một dịch vụ sẽ gửi tin nhắn thông qua bot. Để thực hiện việc này, chúng tôi sẽ tạo một gói dịch vụ bên cạnh gói lệnh , chúng tôi sẽ thêm tất cả các dịch vụ cần thiết vào đó. Ở đây cần tập trung vào ý nghĩa của từ dịch vụ trong trường hợp này. Nếu chúng ta xem xét một ứng dụng, nó thường được chia thành nhiều lớp: một lớp để làm việc với điểm cuối - bộ điều khiển, một lớp logic nghiệp vụ - dịch vụ và một lớp để làm việc với cơ sở dữ liệu - kho lưu trữ. Do đó, trong trường hợp của chúng tôi, dịch vụ là một lớp thực hiện một số loại logic nghiệp vụ. Làm thế nào để tạo một dịch vụ chính xác? Đầu tiên, tạo giao diện cho nó và cách triển khai. Thêm phần triển khai bằng chú thích `@Service` vào Ngữ cảnh ứng dụng của ứng dụng SpringBoot của chúng tôi và nếu cần, hãy thắt chặt nó bằng chú thích `@Autowired`. Vì vậy, chúng ta tạo giao diện SendBotMessageService (trong việc đặt tên dịch vụ họ thường thêm Service vào cuối tên):
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);
}
Tiếp theo, chúng tôi tạo ra phần triển khai của nó:
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();
       }
   }
}
Việc triển khai trông như thế này. Phép thuật quan trọng nhất là nơi nhà thiết kế được tạo ra. Sử dụng chú thích @Autowired trên hàm tạo, SpringBoot sẽ tìm kiếm một đối tượng của lớp này trong Ngữ cảnh ứng dụng của nó. Và anh ấy đã ở đó rồi. Nó hoạt động như thế này: trong ứng dụng của chúng tôi, ở bất cứ đâu chúng tôi có thể truy cập bot và làm điều gì đó. Và dịch vụ này có nhiệm vụ gửi tin nhắn. Để chúng ta không viết những điều như thế này mọi lúc mọi nơi:
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();
}
Chúng tôi đã chuyển logic này vào một lớp riêng biệt và sẽ sử dụng nó nếu cần thiết. Bây giờ chúng ta cần thực hiện ba lệnh: StartCommand, StopCommand và UnknownCommand. Chúng ta cần chúng để có thứ gì đó lấp đầy vùng chứa lệnh của mình. Hiện tại, các văn bản sẽ khô khan và thiếu thông tin; đối với mục đích của nhiệm vụ này, điều này không quan trọng lắm. Vì vậy, 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);
   }
}
Vui lòng đọc kỹ ý kiến ​​trước khi thiết kế. Sự phụ thuộc vòng tròn ( sự phụ thuộc vòng tròn ) có thể xảy ra do kiến ​​trúc không hoàn toàn đúng. Trong trường hợp của chúng tôi, chúng tôi sẽ đảm bảo rằng mọi thứ đều hoạt động và chính xác. Đối tượng thực từ Ngữ cảnh ứng dụng sẽ được thêm vào khi tạo lệnh đã có trong CommandContainer. Lệnh dừng:
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à Lệnh không xác định. Tại sao chúng ta cần nó? Đối với chúng tôi, đây là một lệnh quan trọng sẽ phản hồi nếu chúng tôi không thể tìm thấy lệnh đã được trao cho chúng tôi. Chúng ta cũng sẽ cần NoCommand và HelpCommand.
  • NoCommand - sẽ chịu trách nhiệm về tình huống khi tin nhắn hoàn toàn không bắt đầu bằng một lệnh;
  • HelpCommand sẽ là hướng dẫn cho người dùng, một loại tài liệu.
Hãy thêm 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);
   }
}
Không có lệnh:
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à đối với nhiệm vụ này vẫn còn 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);
   }
}
Tiếp theo, hãy thêm vùng chứa cho các lệnh của chúng ta. Nó sẽ lưu trữ các đối tượng lệnh của chúng tôi và theo yêu cầu, chúng tôi mong đợi nhận được lệnh được yêu cầu. Hãy gọi nó là 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);
   }

}
Như bạn có thể thấy, mọi thứ đã được thực hiện đơn giản. Chúng ta có một bản đồ bất biến với một khóa ở dạng giá trị lệnh và một giá trị ở dạng đối tượng lệnh thuộc loại Lệnh. Trong hàm tạo, chúng ta điền vào bản đồ bất biến một lần và truy cập nó trong suốt quá trình hoạt động của ứng dụng. Phương thức chính và duy nhất để làm việc với vùng chứa là RetrieveCommand(String commandIdentifier) ​​​​. Có một lệnh tên là UnknownCommand, nó chịu trách nhiệm cho những trường hợp chúng ta không thể tìm thấy lệnh tương ứng. Bây giờ chúng ta đã sẵn sàng triển khai vùng chứa vào lớp bot của mình - trong JavaRushTelegramBot: Lớp bot của chúng ta bây giờ trông như thế này:
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à thế là xong, việc thay đổi code đã hoàn tất. Làm thế nào tôi có thể kiểm tra điều này? Bạn cần khởi chạy bot và kiểm tra xem mọi thứ có hoạt động không. Để thực hiện việc này, tôi cập nhật mã thông báo trong application.properties, đặt mã thông báo chính xác và khởi chạy ứng dụng trong lớp JavarushTelegramBotApplication: "Dự án Java từ A đến Z": Triển khai Mẫu lệnh để làm việc với bot.  Phần 1 - 3Bây giờ chúng ta cần kiểm tra xem các lệnh có hoạt động như mong đợi hay không. Tôi kiểm tra từng bước:
  • Lệnh dừng;
  • Lệnh Bắt đầu;
  • Trợ giúpLệnh;
  • Không có lệnh;
  • Lệnh không xác định.
Đây là những gì đã xảy ra: "Dự án Java từ A đến Z": Triển khai Mẫu lệnh để làm việc với bot.  Phần 1 - 4Bot hoạt động chính xác như chúng tôi mong đợi. Tiếp tục qua liên kết .

Danh sách tất cả các tài liệu trong loạt bài này nằm ở đầu bài viết này.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION