JavaRush /Java 博客 /Random-ZH /Telegram 机器人作为第一个项目及其基于个人经验的职业成长意义
Pavel Mironov (Miroha)
第 16 级
Москва

Telegram 机器人作为第一个项目及其基于个人经验的职业成长意义

已在 Random-ZH 群组中发布
祝福大家!向我们介绍你自己。我今年24岁,去年从一所技术大学毕业,仍然没有工作经验。展望未来,我想说的是,最初,在制定的计划(2019年秋季制定的)中,我计划在2020年3月至4月去上班,但不幸的是,隔离干预,所以我把一切推迟到中旬-夏天和未来我希望写下我自己的成功故事。 Telegram 机器人作为第一个项目及其对基于个人经验的职业成长的意义 - 1我从来没有被编程所吸引。在大学里他们教了足够多的编程,但当时我对这门手艺不感兴趣。还有过程语言(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,我将更新进一步传递到其构造函数。使用机器人时,更新是最重要的事情,不能丢失,因为借助更新中存储的信息,我们可以找出应将响应发送到哪个用户和哪个聊天。为了生成对用户的响应,我编写了一个单独的类。它可以发送常规短信、带内嵌键盘的消息、带图片的消息和带回复键盘的消息。内联键盘如下所示: Telegram 机器人作为第一个项目及其对基于个人经验的职业成长的意义 - 1它定义了按钮,通过单击这些按钮,用户可以向服务器发送回调,该回调的处理方式几乎与常规消息相同。为了“维护”它,您需要自己的处理程序。我们为每个按钮设置一个操作,然后将其写入 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;
...
回复键盘看起来像这样: Telegram 机器人作为第一个项目及其对基于个人经验的职业成长的意义 - 2从本质上来说,它取代了用户的打字。单击“图书馆”按钮将快速向机器人发送“图书馆”消息。对于每种类型的键盘,我编写了自己的类,实现了构建器模式:内联回复。因此,您基本上可以根据您的要求“绘制”所需的键盘。这非常方便,因为键盘可能不同,但原理是一样的。这是使用内联键盘发送消息的直观方法:
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 商店中某个游戏的链接,一个特殊的处理程序将自动工作。作为响应,用户将以以下形式收到有关游戏的信息: Telegram 机器人作为第一个项目及其对基于个人经验的职业成长的意义 - 3同时,将调用一个方法,尝试将游戏添加到机器人的库中(首先添加到本地地图,然后添加到 -> 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 的建议:
  1. 如果您居住在俄罗斯,您很可能必须使用 VPN 在 heroku 上注册。

  2. 在项目的根目录下,您需要放置一个名为 Procfile 的不带扩展名的文件。其内容应该是这样的: https: //github.com/miroha/Telegram-Bot/blob/master/Procfile

  3. 在 pom.xml 中,根据示例添加以下行,其中 mainClass 标记中指示包含 main 方法的类的路径:bot.BotApplication(如果 BotApplication 类位于 bot 文件夹中)。

  4. 不要使用 mvn package 命令等构建任何项目,heroku 将为您组装所有内容。

  5. 建议在项目中添加 gitignore,例如:

    # Log file
    *.log
    
    # Compiled resources
    target
    
    # Tests
    test
    
    # IDEA files
    .idea
    *.iml
  6. 实际上将项目上传到github,然后将存储库连接到Heroku(或者使用其他方法,如果我没记错的话,有3种)。

  7. 如果下载成功(“构建成功”),请务必转到配置 Dynos:

    Telegram 机器人作为第一个项目及其对基于个人经验的职业成长的意义 - 4

    并切换滑块,然后确保它处于打开位置(由于我没有这样做,我的机器人无法工作,我绞尽脑汁几天并做了很多不必要的动作)。

  8. 在 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 字段,将机器人的令牌复制到此处):

    Telegram 机器人作为第一个项目及其对基于个人经验的职业成长的意义 - 5
总的来说,在我自己项目的两个月工作中,我:
  • 收到一个用 Java 编写的完整工作项目;
  • 学会使用第三方 API(Telegram Bot API);
  • 在实践中,我更深入地研究了序列化,大量使用了 JSON 和 Jackson 库(最初我使用了 GSON,但它存在问题);
  • 增强了我处理文件的技能,熟悉了 Java NIO;
  • 学会了使用配置 .xml 文件并习惯了日志记录;
  • 提高开发环境(IDEA)的熟练程度;
  • 学会了使用 git 并了解了 gitignore 的价值;
  • 获得网页解析技能(Jsoup 库);
  • 学习并使用了多种设计模式;
  • 产生了改进代码(重构)的意识和愿望;
  • 我学会了在网上寻找解决方案,并且不要羞于提出我找不到答案的问题。
Telegram 机器人作为第一个项目及其对基于个人经验的职业成长的意义 - 7我不知道这个机器人到底有多有用或多无用,也不知道代码有多漂亮/丑陋,但我获得的经验绝对是值得的。我对我的项目有一种责任感。我时不时地想改进它,添加一些新的东西。当我能够运行它并看到一切都按照我想要的方式运行时,我感到非常兴奋。这不是最主要的吗?享受你所做的事情,享受每一行工作代码,就像最后一块巧克力一样。因此,如果你正在掌握编程,那么我给你的建议是:不要留在这里直到40级,而是尽早开始你自己的项目。如果有人感兴趣,该项目的源代码在这里(为 Spring 重写): https: //github.com/miroha/GooglePlayGames-TelegramBot 在过去的两个月里,我几乎没有学习新材料,因为在我看来我已经走到了死胡同。没有工作,我就不再知道在哪里进行开发,除了教授 Spring 框架,这是我计划在下个月做的事情。然后我将尝试使用这个框架“重写”机器人。准备好回答任何问题。:) 大家好运! 2020年7月7日更新 纯Java中的机器人存储库丢失了(我删除了它,副本保留在另一台本地计算机上),但我下载了用于Spring Boot的重写机器人:https ://github.com/miroha /GooglePlayGames-TelegramBot
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION