祝福大家!向我们介绍你自己。我今年24岁,去年从一所技术大学毕业,仍然没有工作经验。展望未来,我想说的是,最初,在制定的计划(2019年秋季制定的)中,我计划在2020年3月至4月去上班,但不幸的是,隔离干预,所以我把一切推迟到中旬-夏天和未来我希望写下我自己的成功故事。 我从来没有被编程所吸引。在大学里他们教了足够多的编程,但当时我对这门手艺不感兴趣。还有过程语言(C)、为期一年的 OOP(Java)、数据库、甚至汇编和 C++ 课程。但说实话,我一般对学习不感兴趣,因为所教授的大部分学科对我来说似乎没什么用,只适合报告目的(原则上是这样)。大学毕业后,我不得不做出一个决定:我没有获得一些技能,但我需要工作。我不得不考虑自学(哦,我已经因为无所事事而错过了至少整整两年的时间),选择自然落在了 Java 上,因为在大学的 OOP 课程中,其中一个人推荐了 javarush 课程,正如您所知,他专门致力于 Java 语言。我对课程的介绍很感兴趣。是的,我当时并不喜欢编程,因为遇到什么困难我就立刻放弃,而编程的困难已经够多的了。但同时,我觉得我想写代码,所以最终我决定进入编程领域。我将简要向您介绍一下我使用 javarush 的经历。我从 2019 年 8 月开始,立即购买了一个月的订阅,但到了 7 级我意识到任务很困难。我把课程放在一边,拿起希尔特。因此我同时完成了 3 个月的课程。我达到了 20 级(这是我的第二个帐户),几乎完全阅读了 Schildt,然后厌倦了这里的任务,在这些任务中我不再看到自己的实际好处。我去了 codewars、leetcode,并开始观看视频课程。顺便说一句,在 3 个月内,我从“哦不,什么是数组?如何使用它以及为什么它如此可怕”?来详细研究集合类(ArrayList、HashMap等)的源码。根据我个人的经验,我告诉初学者:这里主要是克服什么都不懂、解决不了的感觉。当它出现的时候,你只想放弃一切,似乎你在这件事上太愚蠢了。如果你能克服内心的这些时刻并在精神上得到休息,那么成功就会到来。我认为很多人无法应对这一点,所以他们很快就放弃了这样的努力。结果,2019 年 12 月我开始考虑我的项目。我决定选择 Telegram 机器人,但没有任何想法。与此同时,一位朋友需要在电报中为他的团队提供功能,他希望将其自动化。他只是知道我正在深入学习编程,并为我提供了一个项目。对我来说,是为了经验和未来的履历,对他来说,是为了团队的发展。我什至允许自己引用他的想法:“Недавно софтину хотел у программиста заказать, которая загружала бы в выбранное Облако файлы по прямым linkм. Это интересно, так How аналогов нет. И просто очень удобно. Суть: копируешь ссылку, вставляешь в окно и выбираешь нужное Облако (GDrive, Mail, Яндекс Диск и т.п), в своё время софт всё делает на стороне serverа и юзеру ничего не нужно загружать на свою машину (особенно круто, когда у тебя сборка на SSD-накопителях). Думали сделать в web-интерфейсе, чтобы можно было запускать How с телефонов, так и с десктопа... Можно в принципе через приложение реализовать, а не через web-интерфейс. Тебе такое по силам?“我开始工作,但最终,几天后,我意识到我们什么都做不了,这主要是由于缺乏知识。一位朋友需要这些相同的 Cloud.Mail 链接,但他们仍然不这样做”没有 API。曾尝试通过 GDrive 将一些东西组合在一起,但实现很蹩脚,而且这个云服务不适合“客户”。尽管最初他提供了多种云可供选择,但最终他拒绝了除了邮件之外的所有云服务.ru,没有找到解决方案。不知何故,这一切都变得昂贵,需要连接数据库,使用服务器进行存储等。顺便说一句,它仍然需要这个 Web 应用程序。因为事情没有'为了解决这个问题,我决定做一个信息机器人,它应该是从 Google Play 商店接收游戏的链接,解析该链接并将接收到的信息保存到库中,然后写入 json 文件。这样,每一次请求,图书馆都可以在用户的努力下得到扩展,以后你将无法通过Google Play以便捷的形式获取有关游戏的信息。您只需编写命令 /libraryHere_game_name 即可获得您需要的一切。但有几个困难我稍后会告诉你。起初我进展缓慢,因为我开始同时学习两门 SQL 课程。我根本无法理解机器人是如何工作的以及如何处理请求。我遇到了一位也对这个项目感兴趣的朋友。该机器人的第一个版本在大约一个月内准备就绪,但与一位朋友(就我而言)出现了分歧。我承担了机器人负责解析的部分,而他直接处理对机器人的请求及其处理。由于某种原因,他开始使机器人复杂化,引入某种授权,发明管理员,添加不必要的功能,而且我不太喜欢他的编码风格。在我看来,这在信息机器人中是没有必要的。所以我决定自己从头开始编写一个具有我需要的功能的机器人。现在我将告诉您机器人实际上做了什么(使用项目代码中的示例)。我将在文章末尾附上该项目的完整代码,不幸的是,我无法对其进行完整评论。发送到机器人的任何用户消息都是 Update 类的对象。它包含很多信息(消息 ID、聊天 ID、唯一用户 ID 等)。更新有多种类型:可以是短信、可以是电报键盘的响应(回调)、照片、音频等。为了防止用户搞乱太多,我只处理来自键盘的文本请求和回调。如果用户发送照片,机器人会通知他不打算用它做任何事情。在主机器人类的 onUpdateReceived 方法中,机器人接收更新。
@Override
public void onUpdateReceived(Update update) {
UpdatesReceiver.handleUpdates(update);
}
我将其传递给处理程序(自己的 UpdatesReceiver 类):
public static void handleUpdates(Update update) {
...
if (update.hasMessage() && update.getMessage().hasText()){
log.info("[Update (id {}) типа \"Текстовое сообщение\"]", update.getUpdateId());
new TextMessageHandler(update, replyGenerator).handleTextMessage();
}
else if (update.hasCallbackQuery()) {
//логгирование
new CallbackQueryHandler(update, replyGenerator).handleCallBackQuery();
}
else {
//логгирование
replyGenerator.sendTextMessage(update.getMessage().getChatId(), "Я могу принимать только текстовые messages!");
}
}
UpdatesReceiver 是一个中央处理程序,根据更新的类型,将控制权转移到另一个专用处理程序:TextMessageHandler 或 CallbackQueryHandler,我将更新进一步传递到其构造函数。使用机器人时,更新是最重要的事情,不能丢失,因为借助更新中存储的信息,我们可以找出应将响应发送到哪个用户和哪个聊天。为了生成对用户的响应,我编写了一个单独的类。它可以发送常规短信、带内嵌键盘的消息、带图片的消息和带回复键盘的消息。内联键盘如下所示: 它定义了按钮,通过单击这些按钮,用户可以向服务器发送回调,该回调的处理方式几乎与常规消息相同。为了“维护”它,您需要自己的处理程序。我们为每个按钮设置一个操作,然后将其写入 Update 对象。那些。对于“成本”按钮,我们为回调设置描述“/价格”,稍后我们可以从更新中获取该描述。接下来,在一个单独的类中,我已经可以处理这个回调:
public void handleCallBackQuery() {
String call_data = update.getCallbackQuery().getData();
long message_id = update.getCallbackQuery().getMessage().getMessageId();
long chat_id = update.getCallbackQuery().getMessage().getChatId();
switch (call_date){
case "/price" :
//тут что-то сделать
break;
...
回复键盘看起来像这样: 从本质上来说,它取代了用户的打字。单击“图书馆”按钮将快速向机器人发送“图书馆”消息。对于每种类型的键盘,我编写了自己的类,实现了构建器模式:内联和回复。因此,您基本上可以根据您的要求“绘制”所需的键盘。这非常方便,因为键盘可能不同,但原理是一样的。这是使用内联键盘发送消息的直观方法:
public synchronized void sendInlineKeyboardMessage(long chat_id, String gameTitle) {
SendMessage keyboard = InlineKeyboardMarkupBuilder.create(chat_id)
.setText("Вы может узнать следующую информацию об игре " + gameTitle)
.row()
.button("Стоимость " + "\uD83D\uDCB0", "/price " + gameTitle)
.button("Обновлено " + "\uD83D\uDDD3", "/updated " + gameTitle)
.button("Версия " + "\uD83D\uDEE0", "/version " + gameTitle)
.endRow()
.row()
.button("Требования " + "\uD83D\uDCF5", "/requirements " + gameTitle)
.button("Покупки " + "\uD83D\uDED2", "/iap " + gameTitle)
.button("Размер " + "\uD83D\uDD0E", "/size " + gameTitle)
.endRow()
.row()
.button("Получить всю информацию об игре" + "\uD83D\uDD79", "/all " + gameTitle)
.endRow()
.row()
.button("Скрыть клавиатуру", "close")
.endRow()
.build();
try {
execute(keyboard);
} catch (TelegramApiException e) {
log.error("[Не удалось отправить сообщение с -inline- клавиатурой]: {}", e.getMessage());
}
}
为了赋予机器人严格的功能,发明了使用斜杠字符的特殊命令:/library、/help、/game 等。否则,我们将不得不处理用户可能写入的任何垃圾。实际上,这就是 MessageHandler 的编写目的:
if (message.equals(ChatCommands.START.getDescription())) {
replyGenerator.sendTextMessage(chat_id, new StartMessageHandler().reply());
replyGenerator.sendReplyKeyboardMessage(chat_id);
}
else if (message.equals(ChatCommands.HELP.getDescription())
|| message.equalsIgnoreCase("Помощь")) {
replyGenerator.sendTextMessage(chat_id, new HelpMessageHandler().reply());
}
...
因此,根据您发送给机器人的命令,工作中将包含一个特殊的处理程序。让我们进一步看看解析器和库的工作。如果您向机器人发送 Google Play 商店中某个游戏的链接,一个特殊的处理程序将自动工作。作为响应,用户将以以下形式收到有关游戏的信息: 同时,将调用一个方法,尝试将游戏添加到机器人的库中(首先添加到本地地图,然后添加到 -> json 文件)。如果游戏已经在库中,那么将进行检查(就像在常规哈希图中一样),如果字段数据(例如版本号已更改),那么库中的游戏将被覆盖。如果未检测到任何更改,则不会进行任何输入。如果库中根本没有游戏,那么它首先写入本地地图(像tyk这样的对象),然后写入 json 文件,因为如果服务器上的应用程序意外关闭,数据将被写入丢失,但始终可以使用该文件读取。实际上,当程序启动时,库总是第一次从静态块的文件中加载:
static {
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(ConcurrentSkipListMap.class, String.class, GooglePlayGame.class);
try {
Path path = Paths.get(LIBRARY_PATH);
if (!Files.exists(path)) {
Files.createDirectories(path.getParent());
Files.createFile(path);
log.info("[Файл библиотеки создан]");
}
else {
ConcurrentMap<string, googleplaygame=""> temporary = mapper.readValue(new File(LIBRARY_PATH), mapType);
games.putAll(temporary);
log.info("[Количество игр в загруженной библиотеке] = " + games.size());
}
}
catch (IOException e) {
log.error("[Ошибка при чтении/записи file] {}", e.getMessage());
}
}
在这里,您还必须将文件中的数据读取到临时地图中,然后将其“复制”到完整地图中,以便在文件中搜索游戏时保持不区分大小写(通过编写 tITan QuEST,机器人仍然会找到游戏库中的《泰坦之旅》)。不可能找到其他解决方案,这些是使用 Jackson 反序列化的特点。因此,每次请求链接时,如果可能,游戏都会添加到库中,从而扩展库。有关特定游戏的更多信息可以使用命令 /libraryGame_Name 获得。您可以一次找到特定参数(例如当前版本)和所有参数。这是使用前面讨论过的内联键盘实现的。工作期间,我在解决问题的同时也运用了在这里学到的技能。例如,位于库中的随机游戏名称列表(可使用 /library 命令使用该选项):
private String getRandomTitles(){
if (LibraryService.getLibrary().size() < 10){
return String.join("\n", LibraryService.getLibrary().keySet());
}
List<string> keys = new ArrayList<>(LibraryService.getLibrary().keySet());
Collections.shuffle(keys);
List<string> randomKeys = keys.subList(0, 10);
return String.join("\n", randomKeys);
}
机器人如何处理链接?它检查它们是否属于 Google Play(主机、协议、端口):
private static class GooglePlayCorrectURL {
private static final String VALID_HOST = "play.google.com";
private static final String VALID_PROTOCOL = "https";
private static final int VALID_PORT = -1;
private static boolean isLinkValid(URI link) {
return (isHostExist(link) && isProtocolExist(link) && link.getPort() == VALID_PORT);
}
private static boolean isProtocolExist(URI link) {
if (link.getScheme() != null) {
return link.getScheme().equals(VALID_PROTOCOL);
}
else {
return false;
}
}
private static boolean isHostExist(URI link) {
if (link.getHost() != null) {
return link.getHost().equals(VALID_HOST);
}
else {
return false;
}
}
如果一切正常,则机器人将通过使用 Jsoup 库的链接进行连接,该库允许您获取页面的 HTML 代码,以便进行进一步的分析和解析。您无法使用不正确或有害的链接来欺骗机器人。
if (GooglePlayCorrectURL.isLinkValid(link)){
if (!link.getPath().contains("apps")){
throw new InvalidGooglePlayLinkException("К сожалению, бот работает исключительно с играми. Введите другую ссылку.");
}
URL = forceToRusLocalization(URL);
document = Jsoup.connect(URL).get();
}
else {
throw new NotGooglePlayLinkException();
}
...
这里我们必须解决区域设置的问题。该机器人从位于欧洲的服务器连接到 Google Play 商店,因此 Google Play 商店中的页面会以适当的语言打开。我必须编写一个拐杖,强制“重定向”到俄语版本的页面(毕竟,该项目是针对我们的受众的)。为此,您需要在链接末尾仔细添加参数 hl: &hl=ru到 Google Play 服务器的 GET 请求中。
private String forceToRusLocalization(String URL) {
if (URL.endsWith("&hl=ru")){
return URL;
}
else {
if (URL.contains("&hl=")){
URL = URL.replace(
URL.substring(URL.length()-"&hl=ru".length()), "&hl=ru");
}
else {
URL += "&hl=ru";
}
}
return URL;
}
成功连接后,我们会收到一个可供分析和解析的 HTML 文档,但这超出了本文的范围。解析器代码在这里。解析器本身检索必要的信息并使用游戏创建一个对象,如有必要,稍后将其添加到库中。<h2>总结</h2>机器人支持多个包含某些功能的命令。它接收来自用户的消息并将其与其命令进行匹配。如果它是链接或 /game + link 命令,它会检查该链接以查看它是否属于 Google Play。如果链接正确,它将通过 Jsoup 连接并接收 HTML 文档。本文档是根据编写的解析器进行分析的。从文档中提取有关游戏的必要信息,然后用这些数据填充游戏的对象。接下来,带有游戏的对象被放置在本地存储中(如果游戏尚不存在)并立即写入文件以避免数据丢失。库中记录的游戏(游戏名称是地图的键,游戏的对象是地图的值)可以使用命令 /library Game_name 获取。如果在机器人的库中找到指定的游戏,则会向用户返回一个内联键盘,用户可以通过该键盘获取有关游戏的信息。如果找不到游戏,您必须确保名称拼写正确(必须与 Google Play 商店中的游戏名称完全匹配,大小写除外),或者通过发送机器人将游戏添加到库中游戏链接。我在 Heroku 上部署了机器人,对于那些未来计划编写自己的机器人并在 Heroku 上免费托管的人,我将提供一些建议来解决您可能遇到的困难(因为我自己也遇到过)。不幸的是,由于 Heroku 的性质,机器人库每 24 小时不断“重置”一次。我的计划不支持在 Heroku 服务器上存储文件,因此它只是从 Github 提取我的游戏文件。有几种解决方案:使用数据库,或者寻找另一个可以存储该文件和游戏的服务器。我决定暂时不做任何事情,因为本质上这个机器人并没有那么有用。我需要它而不是获得完整的经验,这基本上就是我所取得的成就。因此,对 Heroku 的建议:
-
如果您居住在俄罗斯,您很可能必须使用 VPN 在 heroku 上注册。
-
在项目的根目录下,您需要放置一个名为 Procfile 的不带扩展名的文件。其内容应该是这样的: https: //github.com/miroha/Telegram-Bot/blob/master/Procfile
-
在 pom.xml 中,根据示例添加以下行,其中 mainClass 标记中指示包含 main 方法的类的路径:bot.BotApplication(如果 BotApplication 类位于 bot 文件夹中)。
-
不要使用 mvn package 命令等构建任何项目,heroku 将为您组装所有内容。
-
建议在项目中添加 gitignore,例如:
# Log file *.log # Compiled resources target # Tests test # IDEA files .idea *.iml
-
实际上将项目上传到github,然后将存储库连接到Heroku(或者使用其他方法,如果我没记错的话,有3种)。
-
如果下载成功(“构建成功”),请务必转到配置 Dynos:
并切换滑块,然后确保它处于打开位置(由于我没有这样做,我的机器人无法工作,我绞尽脑汁几天并做了很多不必要的动作)。
-
在 Github 上隐藏机器人令牌。为此,您需要从环境变量中获取令牌:
public class Bot extends TelegramLongPollingBot { private static final String BOT_TOKEN = System.getenv("TOKEN"); @Override public String getBotToken() { return BOT_TOKEN; } ... }
然后部署机器人后,在 Heroku 仪表板的“设置”选项卡中设置此变量(TOKEN 右侧将有一个 VALUE 字段,将机器人的令牌复制到此处):
- 收到一个用 Java 编写的完整工作项目;
- 学会使用第三方 API(Telegram Bot API);
- 在实践中,我更深入地研究了序列化,大量使用了 JSON 和 Jackson 库(最初我使用了 GSON,但它存在问题);
- 增强了我处理文件的技能,熟悉了 Java NIO;
- 学会了使用配置 .xml 文件并习惯了日志记录;
- 提高开发环境(IDEA)的熟练程度;
- 学会了使用 git 并了解了 gitignore 的价值;
- 获得网页解析技能(Jsoup 库);
- 学习并使用了多种设计模式;
- 产生了改进代码(重构)的意识和愿望;
- 我学会了在网上寻找解决方案,并且不要羞于提出我找不到答案的问题。
GO TO FULL VERSION