JavaRush /Java Blog /Random-TW /Telegram 機器人作為第一個專案及其基於個人經驗的職業成長意義
Pavel Mironov (Miroha)
等級 16
Москва

Telegram 機器人作為第一個專案及其基於個人經驗的職業成長意義

在 Random-TW 群組發布
祝福大家!向我們介紹你自己。我今年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