大家好,親愛的朋友們。今天我們將根據我們的需求實現一個命令設計的模板(模板是一種模式,在我們的上下文中它是同一件事)。使用此模板,我們將方便、正確地處理機器人的命令。
首先,我們最好談談這是什麼樣的模式──命令。但如果我這樣做的話,文章就會非常龐大而且繁瑣。因此,我選擇了自學教材:
朋友們,你們喜歡Javarush Telegram Bot 專案嗎?不要偷懶:給它一顆星。這樣就能明顯看出他是有趣的,培養他也會更愉快! |
- 這是我4年前的文章。這是我大三的時候寫的,所以不要太嚴厲地評價它。
- YouTube 上非常情緒化且互動的瑞典人的影片。我強烈推薦它。他說得很漂亮,他的英語清晰易懂。一般來說,他有一個關於其他設計模式的影片。
- 在我文章的評論中,有人Nullptr35推薦了這個影片。
我們寫JRTB-3
一切都和以前一樣:- 我們更新主分支。
- 基於更新的主分支,我們建立一個新的JRTB-3。
- 讓我們實作該模式。
- 我們建立一個新的提交來描述已完成的工作。
- 我們建立一個拉取請求,檢查它,如果一切正常,我們就合併我們的工作。
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);
}
就這樣!而且無論我們添加多少命令,這段程式碼都將保持不變。他在做什麼?第一個 if 確保訊息以命令前綴“/”開頭。如果是這種情況,那麼我們選擇第一個空格之前的行,並在 CommandContainer 中尋找相應的命令;一旦找到,我們就執行該命令。就這樣…)如果您有願望和時間,您可以實施與團隊合作,首先在一個班級中一次,有一堆條件等等,然後使用模板。您會看到差異。那將是多麼美麗啊!首先,讓我們在 bot 包旁邊建立一個包,將其稱為command。這個包中已經包含了與命令的實作相關的所有類別。我們需要一個介面來處理命令。對於這種情況,讓我們創建它:
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);
}
此時,我們不需要實作命令的反向操作,因此我們將跳過該方法(取消執行)。在執行方法中, Update物件作為參數出現- 正是我們在機器人中的 main 方法中出現的參數。該物件將包含處理命令所需的所有內容。接下來,我們將新增一個枚舉來儲存命令值(開始、停止等)。我們為什麼需要這個?這樣我們就只有一個真實的球隊名稱來源。我們還在命令包中創建它。我們稱之為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;
}
}
我們還需要一項透過機器人發送訊息的服務。為此,我們將在命令包旁邊建立一個服務包,並向其中添加所有必要的服務。 這裡值得關注的是我在這種情況下所說的「服務」一詞的意思。如果我們考慮一個應用程序,它通常分為幾個層:用於處理端點的層 - 控制器,業務邏輯層 - 服務,以及用於處理資料庫的層 - 存儲庫。因此,在我們的例子中,服務是實現某種業務邏輯的類別。 如何正確創建服務?首先,為其建立一個介面和一個實作。使用「@Service」註解將實作新增至 SpringBoot 應用程式的應用程式上下文中,並在必要時使用「@Autowired」註解將其收緊。因此,我們建立 SendBotMessageService 介面(在命名服務中,他們通常會在名稱末尾新增 Service):
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註釋,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。我們需要它們,以便我們有東西來填充我們的命令容器。目前,文本將是枯燥且缺乏資訊的;對於本任務的目的來說,這並不是很重要。所以,啟動命令:
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);
}
}
請設計師仔細閱讀評論。由於架構不太正確,可能會出現循環依賴(circular dependency )。就我們而言,我們將確保一切正常且正確。當建立 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);
}
}
無命令:
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)。有一個指令叫UnknownCommand,它負責處理我們找不到對應指令的情況。現在我們準備將容器實現到我們的機器人類中 - 在 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;
}
}
就這樣,程式碼的修改就完成了。我怎樣才能檢查這個?您需要啟動機器人並檢查一切是否正常。為此,我更新 application.properties 中的令牌,設定正確的令牌,然後在 JavarushTelegramBotApplication 類別中啟動應用程式:現在我們需要檢查命令是否按預期工作。我一步步檢查:
- 停止命令;
- 啟動命令;
- 幫助命令;
- 無命令;
- 未知的命令。
GO TO FULL VERSION