大家好,亲爱的朋友们。今天我们将根据我们的需求实现一个命令设计的模板(模板是一种模式,在我们的上下文中它是同一件事)。使用此模板,我们将方便、正确地处理机器人的命令。
首先,我们最好谈谈这是什么样的模式——命令。但如果我这样做的话,文章就会非常庞大而且繁琐。因此,我选择了自学材料:
朋友们,你们喜欢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