JavaRush /مدونة جافا /Random-AR /إنشاء روبوت برقية باستخدام Spring Boot Pt.2: Quiz Bot
Whiskels
مستوى
Москва

إنشاء روبوت برقية باستخدام Spring Boot Pt.2: Quiz Bot

نشرت في المجموعة
الجزء 1 في المقالة السابقة، قمنا بإنشاء روبوت بسيط يرحب بنا في أي حدث. لقد كتبنا بالفعل آلاف الأسطر من التعليمات البرمجية، وحان الوقت لإضافة وظائف أكثر تعقيدًا إلى الروبوت الخاص بنا. سنحاول اليوم كتابة روبوت بسيط حتى نتمكن في وقت فراغنا من صقل معرفتنا بـ Java Core قبل إجراء المقابلات (من المثير للدهشة أنني لم أجد روبوتًا عاملاً واحدًا من هذا النوع). وللقيام بذلك سنقوم بما يلي:
  • ربط قاعدة بيانات Postgres خارجية بـ Heroku؛
  • لنكتب نصوصنا الأولى لتهيئة قاعدة البيانات وملؤها؛
  • لنقم بتوصيل Spring Boot Data JPA للعمل مع قاعدة البيانات؛
  • نحن ننفذ سيناريوهات مختلفة لسلوك الروبوت.
إذا كان هذا مثيرًا للاهتمام بالنسبة لك، وبعد قراءة المقال، لا تريد رمي الطماطم الفاسدة، فضع نجمة في مستودعي ، سأكون سعيدًا! إذا كان لديك أي أسئلة، سنناقشها في التعليقات. هذا ما سنحصل عليه في النهاية: إنشاء روبوت برقية باستخدام Spring Boot Pt.2: Quiz Bot - 1إنشاء روبوت برقية باستخدام Spring Boot Pt.2: Quiz Bot - 2إنشاء روبوت برقية باستخدام Spring Boot Pt.2: Quiz Bot - 3<h2>لذا، فلنبدأ!</h2><h3>إنشاء قاعدة بيانات على Heroku</h3>لنبدأ بإنشاء أول قاعدة بيانات خارجية لنا. بالنسبة للعمل المحلي، أوصي بمراجعة pgAdmin . لكنني أريدك أن تسترشد بحقيقة أنك ستنشر الروبوت في Heroku في المستقبل بحيث لا يعتمد على جهازك المحلي، ولهذا دعونا نتعرف على هذه الخدمة. إجراء:
  • سجل في هيروكو ;
  • انتقل إلى لوحة التحكم لدينا -> جديد -> إنشاء تطبيق جديد وإنشاء تطبيق جديد؛
  • نذهب إلى التطبيق الذي تم إنشاؤه حديثًا، وتخيفنا الأزرار العديدة، لكننا نركز على لوحة "الوظائف الإضافية المثبتة" - بجانبه يوجد زر تكوين الوظائف الإضافية، ونضغط عليه؛
  • أدخل "Heroku Postgres" في البحث، وحدد خطة "Hobby Dev - Free" -> إرسال نموذج الطلب؛
  • افتح قاعدة البيانات التي تم الحصول عليها حديثًا -> الإعدادات -> عرض بيانات الاعتماد. ستحتوي علامة التبويب هذه على مفاتيحنا للوصول إلى قاعدة البيانات. نحن نتذكر موقعهم - سنحتاجهم لتوصيل قاعدة البيانات، مثل DataSource في IDEA.
<h3>أضف التبعيات إلى pom.xml</h3>كجزء من العمل على الروبوت الخاص بنا، سنضيف التبعيات التالية إلى pom الخاص بنا: Lombok، وSpring Boot Data JPA، وPostgreSQL. قف! ما كل هذا ولماذا نضيفه؟
  • Lombok هي مكتبة بفضلها سنقلل بشكل كبير من كمية الأكواد المختلفة. باستخدامه، يمكننا إنشاء المُنشئين والمُحددين والحاصلين تلقائيًا وغير ذلك الكثير.
  • يعد Spring Data JPA إطارًا للعمل مع قواعد البيانات (على الرغم من أنه يبدو بسيطًا جدًا). سيكون وصف إمكانات Spring Data JPA عبارة عن سلسلة من المقالات، والتبعية التي حددناها تستلزم أيضًا وضع السبات وأكثر من ذلك بكثير، لذلك دعونا نتخطى التفاصيل ونحاول فقط كتابة شيء ما باستخدام Spring JPA اليوم.
  • PostgreSQL - نقوم بسحب المكتبة للحصول على برنامج تشغيل يعمل مع قاعدة بياناتنا.
يبدأ ملف pom.xml الخاص بنا في الظهور بالشكل التالي: الخصائص: إنشاء روبوت برقية باستخدام Spring Boot Pt.2: Quiz Bot - 1التبعيات: إنشاء روبوت برقية باستخدام Spring Boot Pt.2: Quiz Bot - 2إذا لم تكن قد قرأت المقالة السابقة، فيرجى ملاحظة أننا نضيف تبعيات جديدة هنا، لذا فهذه ليست بنية pom.xml الكاملة. لا تنس تحميل هذه التبعيات في مشروعنا (على سبيل المثال، بالانتقال إلى نافذة Maven -> إعادة استيراد جميع مشاريع Maven).<h3>توصيل قاعدة البيانات في IDEA</h3>إذا كنت تستخدم IDEA Community Edition، فيمكنك ذلك قم بتمكين علامة التبويب DataSource كما يلي . بعد إضافة البرنامج المساعد، نحتاج إلى تكوين DataSource. للقيام بذلك، قم أولاً بتمكين عرض البرنامج الإضافي: عرض -> أداة Windows -> متصفح DB. في النافذة التي تفتح، انقر فوق علامة الجمع الخضراء (اتصال جديد) -> PostgreSQL. سنحتاج هنا إلى بيانات الاعتماد التي رأيناها بالفعل في Heroku. املأ النافذة: إنشاء روبوت برقية باستخدام Spring Boot Pt.2: Quiz Bot - 3وانقر على "اختبار الاتصال". إذا تم كل شيء بشكل صحيح، ستظهر نافذة منبثقة تشير إلى نجاح الاتصال بقاعدة البيانات. احفظ مصدر بياناتنا.<h3>إنشاء جداول في قاعدة البيانات</h3>الآن لنقم بإنشاء الجداول التي سنعمل معها. أولاً، لنقم بتثبيت PostgreSQL . بعد التثبيت، قم بإنشاء ملف initDB.sql في المجلد src/main/resources:
DROP TABLE IF EXISTS java_quiz;
DROP TABLE IF EXISTS users;
CREATE SEQUENCE global_seq START WITH 100000;

CREATE TABLE users
(
    id         INTEGER PRIMARY KEY DEFAULT nextval('global_seq'),
    chat_id    INTEGER UNIQUE                NOT NULL,
    name       VARCHAR                       NOT NULL,
    score      INTEGER             DEFAULT 0 NOT NULL,
    high_score INTEGER             DEFAULT 0 NOT NULL,
    bot_state  VARCHAR                       NOT NULL
);

CREATE TABLE java_quiz
(
    id             INTEGER PRIMARY KEY DEFAULT nextval('global_seq'),
    question       VARCHAR NOT NULL,
    answer_correct VARCHAR NOT NULL,
    option1        VARCHAR NOT NULL,
    option2        VARCHAR NOT NULL,
    option3        VARCHAR NOT NULL
);
ماذا يفعل السيناريو لدينا؟ يقوم أول سطرين بمسح الجداول، إذا كانت موجودة، لإعادة إنشائها. يقوم السطر الثالث بإنشاء تسلسل سيتم استخدامه لإنشاء إدخالات معرف فريدة في قاعدة البيانات الخاصة بنا. بعد ذلك، نقوم بإنشاء جدولين: للمستخدمين وللأسئلة. سيكون لدى المستخدم معرف فريد ومعرف دردشة برقية واسم وعدد النقاط (الحالية والحد الأقصى)، بالإضافة إلى الحالة الحالية للبوت. سيكون للأسئلة أيضًا معرف فريد، بالإضافة إلى الحقول المسؤولة عن خيارات السؤال والإجابة الخاصة بها. يمكننا تنفيذ البرنامج النصي الناتج عن طريق النقر بزر الماوس الأيمن عليه وتحديد "تنفيذ برنامج SQL". يجب إيلاء اهتمام خاص لعنصر "واجهة Cmd-Line" - هنا سنحتاج إلى PostgreSQL المثبت حديثًا. عند تكوين هذا الحقل، حدد "واجهة Cmd-Line الجديدة" وحدد المسار إلى psql.exe. ونتيجة لذلك، يجب أن تبدو الإعدادات كما يلي: إنشاء روبوت برقية باستخدام Spring Boot Pt.2: Quiz Bot - 4نقوم بتنفيذ البرنامج النصي وإذا لم نرتكب أي خطأ في أي مكان، فستكون نتيجة عملنا كما يلي: إنشاء روبوت برقية باستخدام Spring Boot Pt.2: Quiz Bot - 8<h3>إنشاء نموذج</h3>حان الوقت الآن للعودة إلى كتابة كود جافا. لاختصار المقال، سأحذف وصف التعليقات التوضيحية المستخدمة لكتابة الفصول الدراسية حتى تتمكن من التعرف عليها. لنقم بإنشاء حزمة نموذجية سيكون لدينا فيها ثلاث فئات:
  • AbstractBaseEntity هي فئة تصف أي كائن يمكن أن يكون له معرف (هذه الفئة هي تبسيط قوي لما قد تراه في التدريب):
    package com.whiskels.telegram.model;
    
    import lombok.Getter;
    import lombok.Setter;
    
    import javax.persistence.*;
    // Аннотация, которая говорит нам, что это суперкласс для всех Entity
    // https://vladmihalcea.com/how-to-inherit-properties-from-a-base-class-entity-using-mappedsuperclass-with-jpa-and-hibernate/
    @MappedSuperclass
    // http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access
    @Access(AccessType.FIELD)
    
    // Аннотации Lombok для автогенерации сеттеров и геттеров на все поля
    @Getter
    @Setter
    public abstract class AbstractBaseEntity {
    
    // Аннотации, описывающие механизм генерации id - разберитесь в documentации каждой!
        @Id
        @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ)
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq")
    //  See https://hibernate.atlassian.net/browse/HHH-3718 and https://hibernate.atlassian.net/browse/HHH-12034
    //  Proxy initialization when accessing its identifier managed now by JPA_PROXY_COMPLIANCE setting
        protected Integer id;
    
        protected AbstractBaseEntity() {
        }
    }
  • مستخدم :
    package com.whiskels.telegram.model;
    
    import com.whiskels.telegram.bot.State;
    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    import org.hibernate.annotations.BatchSize;
    
    import javax.persistence.*;
    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.NotNull;
    import java.util.Set;
    
    import static javax.persistence.FetchType.EAGER;
    
    @Entity
    @Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "chat_id", name = "users_unique_chatid_idx")})
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class User extends AbstractBaseEntity {
        @Column(name = "chat_id", unique = true, nullable = false)
        @NotNull
        private Integer chatId;
    
        @Column(name = "name", unique = true, nullable = false)
        @NotBlank
        private String name;
    
        @Column(name = "score", nullable = false)
        @NotNull
        private Integer score;
    
        @Column(name = "high_score", nullable = false)
        @NotNull
        private Integer highScore;
    
        @Column(name = "bot_state", nullable = false)
        @NotBlank
        private State botState;
    
    // Конструктор нужен для создания нового пользователя (а может и нет? :))
        public User(int chatId) {
            this.chatId = chatId;
            this.name = String.valueOf(chatId);
            this.score = 0;
            this.highScore = 0;
            this.botState = State.START;
        }
    }
  • فئة السؤال :
    package com.whiskels.telegram.model;
    
    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.Table;
    import javax.validation.constraints.NotBlank;
    
    @Entity
    @Table(name = "java_quiz")
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Question extends AbstractBaseEntity {
        @Column(name = "question", nullable = false)
        @NotBlank
        private String question;
    
        @Column(name = "answer_correct", nullable = false)
        @NotBlank
        private String correctAnswer;
    
        @Column(name = "option2", nullable = false)
        @NotBlank
        private String optionOne;
    
        @Column(name = "option1", nullable = false)
        @NotBlank
        private String optionTwo;
    
        @Column(name = "option3", nullable = false)
        @NotBlank
        private String optionThree;
    
        @Override
        public String toString() {
            return "Question{" +
                    "question='" + question + '\'' +
                    ", correctAnswer='" + correctAnswer + '\'' +
                    ", optionOne='" + optionOne + '\'' +
                    ", optionTwo='" + optionTwo + '\'' +
                    ", optionThree='" + optionThree + '\'' +
                    '}';
        }
    }
<h3>إنشاء مستودعات</h3>الآن لنكتب مستودعات Spring Data Jpa. نقوم بإنشاء حزمة مستودع تحتوي على واجهتين : JpaUserRepository، JpaQuestionRepository. سوف يرثون من JpaRepository، وهي واجهة Spring Data التي تسمح لنا بإنشاء السحر عمليًا. لفهم عملهم، أوصي بمشاهدة فيديو يفغيني بوريسوف . ستكون الفصول صغيرة جدًا:
  • مستودع JpaUser:
    package com.whiskels.telegram.repository;
    
    import com.whiskels.telegram.model.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.Optional;
    
    @Repository
    @Transactional(readOnly = true)
    public interface JpaUserRepository extends JpaRepository<user, integer=""> {
    // По названию метода Spring сам поймет, что мы хотим получить пользователя по переданному chatId
        Optional<user> getByChatId(int chatId);
    }
    </user></user,>
  • مستودع JpaQuestion:
    package com.whiskels.telegram.repository;
    
    import com.whiskels.telegram.model.Question;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;
    
    @Repository
    @Transactional(readOnly = true)
    public interface JpaQuestionRepository extends JpaRepository<question, integer=""> {
    // А здесь мы написали SQL Query, которая будет выбирать 1 случайный вопрос из таблицы вопросов
        @Query(nativeQuery = true, value = "SELECT *  FROM java_quiz ORDER BY random() LIMIT 1")
        Question getRandomQuestion();
    }
    </question,>
<h3>إضافة وظيفة إلى الروبوت</h3>في فئة المستخدم لدينا حقل لفئة الحالة التي لم يتم إنشاؤها بعد ، والتي ستخبرنا في أي مرحلة من العمل مع الروبوت الذي وصل إليه المستخدم حاليًا. لنقم بإنشائه في الحزمة /bot:
package com.whiskels.telegram.bot;

public enum State {
    NONE,
    START,
    ENTER_NAME,
    PLAYING_QUIZ,
}
بعد ذلك، سنقوم بإنشاء حزمة بوت/معالج حيث سنعلن عن واجهة المعالج:
package com.whiskels.telegram.bot.handler;

import com.whiskels.telegram.bot.State;
import com.whiskels.telegram.model.User;
import org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod;

import java.io.Serializable;
import java.util.List;

public interface Handler {

// основной метод, который будет обрабатывать действия пользователя
    List<partialbotapimethod<? extends="" serializable="">> handle(User user, String message);
// метод, который позволяет узнать, можем ли мы обработать текущий State у пользователя
    State operatedBotState();
// метод, который позволяет узнать, Howие команды CallBackQuery мы можем обработать в этом классе
    List<string> operatedCallBackQuery();
}

</string></partialbotapimethod<?>
سنقوم بإنشاء المعالجات بعد قليل، ولكن الآن دعونا نفوض معالجة الأحداث إلى فئة UpdateReceiver الجديدة ، والتي سنقوم بإنشائها في جذر حزمة الروبوت: تنبيه! هنا وأكثر ستكون هناك طرق يتم عرضها كـ List> Handle(args); في الواقع تبدو هكذا، لكن منسق الكود كسرها:إنشاء روبوت برقية باستخدام Spring Boot Pt.2: Quiz Bot - 6
package com.whiskels.telegram.bot;

import com.whiskels.telegram.bot.handler.Handler;
import com.whiskels.telegram.model.User;
import com.whiskels.telegram.repository.JpaUserRepository;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod;
import org.telegram.telegrambots.meta.api.objects.CallbackQuery;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

@Component
public class UpdateReceiver {
    // Храним доступные хендлеры в списке (подсмотрел у Miroha)
    private final List<handler> handlers;
    // Имеем доступ в базу пользователей
    private final JpaUserRepository userRepository;

    public UpdateReceiver(List<handler> handlers, JpaUserRepository userRepository) {
        this.handlers = handlers;
        this.userRepository = userRepository;
    }

    // Обрабатываем полученный Update
    public List<partialbotapimethod<? extends="" serializable="">> handle(Update update) {
        // try-catch, чтобы при несуществующей команде просто возвращать пустой список
        try {
            // Проверяем, если Update - сообщение с текстом
            if (isMessageWithText(update)) {
                // Получаем Message из Update
                final Message message = update.getMessage();
                // Получаем айди чата с пользователем
                final int chatId = message.getFrom().getId();

                // Просим у репозитория пользователя. Если такого пользователя нет - создаем нового и возвращаем его.
                // Как раз на случай нового пользователя мы и сделали конструктор с одним параметром в классе User
                final User user = userRepository.getByChatId(chatId)
                        .orElseGet(() -> userRepository.save(new User(chatId)));
                // Ищем нужный обработчик и возвращаем результат его работы
                return getHandlerByState(user.getBotState()).handle(user, message.getText());

            } else if (update.hasCallbackQuery()) {
                final CallbackQuery callbackQuery = update.getCallbackQuery();
                final int chatId = callbackQuery.getFrom().getId();
                final User user = userRepository.getByChatId(chatId)
                        .orElseGet(() -> userRepository.save(new User(chatId)));

                return getHandlerByCallBackQuery(callbackQuery.getData()).handle(user, callbackQuery.getData());
            }

            throw new UnsupportedOperationException();
        } catch (UnsupportedOperationException e) {
            return Collections.emptyList();
        }
    }

    private Handler getHandlerByState(State state) {
        return handlers.stream()
                .filter(h -> h.operatedBotState() != null)
                .filter(h -> h.operatedBotState().equals(state))
                .findAny()
                .orElseThrow(UnsupportedOperationException::new);
    }

    private Handler getHandlerByCallBackQuery(String query) {
        return handlers.stream()
                .filter(h -> h.operatedCallBackQuery().stream()
                        .anyMatch(query::startsWith))
                .findAny()
                .orElseThrow(UnsupportedOperationException::new);
    }

    private boolean isMessageWithText(Update update) {
        return !update.hasCallbackQuery() && update.hasMessage() && update.getMessage().hasText();
    }
}

</partialbotapimethod<?></handler></handler>
ونقوم بتفويض المعالجة لها في فئة Bot:
package com.whiskels.telegram.bot;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;

import java.io.Serializable;
import java.util.List;

@Slf4j
@Component
public class Bot extends TelegramLongPollingBot {
    @Value("${bot.name}")
    @Getter
    private String botUsername;

    @Value("${bot.token}")
    @Getter
    private String botToken;

    private final UpdateReceiver updateReceiver;

    public Bot(UpdateReceiver updateReceiver) {
        this.updateReceiver = updateReceiver;
    }

    @Override
    public void onUpdateReceived(Update update) {
        List<partialbotapimethod<? extends="" serializable="">> messagesToSend = updateReceiver.handle(update);

        if (messagesToSend != null && !messagesToSend.isEmpty()) {
            messagesToSend.forEach(response -> {
                if (response instanceof SendMessage) {
                    executeWithExceptionCheck((SendMessage) response);
                }
            });
        }
    }

    public void executeWithExceptionCheck(SendMessage sendMessage) {
        try {
            execute(sendMessage);
        } catch (TelegramApiException e) {
            log.error("oops");
        }
    }
}

</partialbotapimethod<?>
الآن يقوم الروبوت الخاص بنا بتفويض معالجة الأحداث إلى فئة UpdateReceiver ، ولكن ليس لدينا أي معالجات حتى الآن. دعونا خلق لهم! تنصل! لقد أردت حقًا مشاركة إمكانيات كتابة مثل هذا الروبوت، لذلك يمكن إعادة هيكلة التعليمات البرمجية الإضافية (كما هو الحال من حيث المبدأ كود UpdateReceiver) بشكل جيد باستخدام أنماط مختلفة. لكننا نتعلم وهدفنا هو الحصول على روبوت قابل للتطبيق إلى الحد الأدنى، لذلك كمهمة منزلية أخرى، يمكنك إعادة هيكلة كل ما رأيته :) قم بإنشاء حزمة استخدام، وفيها - فئة TelegramUtil :
package com.whiskels.telegram.util;

import com.whiskels.telegram.model.User;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;

public class TelegramUtil {
    public static SendMessage createMessageTemplate(User user) {
        return createMessageTemplate(String.valueOf(user.getChatId()));
    }

    // Создаем шаблон SendMessage с включенным Markdown
    public static SendMessage createMessageTemplate(String chatId) {
        return new SendMessage()
                .setChatId(chatId)
                .enableMarkdown(true);
    }

    // Создаем кнопку
    public static InlineKeyboardButton createInlineKeyboardButton(String text, String command) {
        return new InlineKeyboardButton()
                .setText(text)
                .setCallbackData(command);
    }
}
سنكتب أربعة معالجات: HelpHandler، QuizHandler، RegistrationHandler، StartHandler. معالج البداية:
package com.whiskels.telegram.bot.handler;

import com.whiskels.telegram.bot.State;
import com.whiskels.telegram.model.User;
import com.whiskels.telegram.repository.JpaUserRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

import static com.whiskels.telegram.util.TelegramUtil.createMessageTemplate;

@Component
public class StartHandler implements Handler {
    @Value("${bot.name}")
    private String botUsername;

    private final JpaUserRepository userRepository;

    public StartHandler(JpaUserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public List<partialbotapimethod<? extends="" serializable="">> handle(User user, String message) {
        // Приветствуем пользователя
        SendMessage welcomeMessage = createMessageTemplate(user)
                .setText(String.format(
                        "Hola! I'm *%s*%nI am here to help you learn Java", botUsername
                ));
        // Просим назваться
        SendMessage registrationMessage = createMessageTemplate(user)
                .setText("In order to start our journey tell me your name");
        // Меняем пользователю статус на - "ожидание ввода имени"
        user.setBotState(State.ENTER_NAME);
        userRepository.save(user);

        return List.of(welcomeMessage, registrationMessage);
    }

    @Override
    public State operatedBotState() {
        return State.START;
    }

    @Override
    public List<string> operatedCallBackQuery() {
        return Collections.emptyList();
    }
}

</string></partialbotapimethod<?>
معالج التسجيل:
package com.whiskels.telegram.bot.handler;

import com.whiskels.telegram.bot.State;
import com.whiskels.telegram.model.User;
import com.whiskels.telegram.repository.JpaUserRepository;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;

import java.io.Serializable;
import java.util.List;

import static com.whiskels.telegram.bot.handler.QuizHandler.QUIZ_START;
import static com.whiskels.telegram.util.TelegramUtil.createInlineKeyboardButton;
import static com.whiskels.telegram.util.TelegramUtil.createMessageTemplate;

@Component
public class RegistrationHandler implements Handler {
    //Храним поддерживаемые CallBackQuery в виде констант
    public static final String NAME_ACCEPT = "/enter_name_accept";
    public static final String NAME_CHANGE = "/enter_name";
    public static final String NAME_CHANGE_CANCEL = "/enter_name_cancel";

    private final JpaUserRepository userRepository;

    public RegistrationHandler(JpaUserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public List<partialbotapimethod<? extends="" serializable="">> handle(User user, String message) {
        // Проверяем тип полученного события
        if (message.equalsIgnoreCase(NAME_ACCEPT) || message.equalsIgnoreCase(NAME_CHANGE_CANCEL)) {
            return accept(user);
        } else if (message.equalsIgnoreCase(NAME_CHANGE)) {
            return changeName(user);
        }
        return checkName(user, message);

    }

    private List<partialbotapimethod<? extends="" serializable="">> accept(User user) {
        // Если пользователь принял Name - меняем статус и сохраняем
        user.setBotState(State.NONE);
        userRepository.save(user);

        // Создаем кнопку для начала игры
        InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup();

        List<inlinekeyboardbutton> inlineKeyboardButtonsRowOne = List.of(
                createInlineKeyboardButton("Start quiz", QUIZ_START));

        inlineKeyboardMarkup.setKeyboard(List.of(inlineKeyboardButtonsRowOne));

        return List.of(createMessageTemplate(user).setText(String.format(
                "Your name is saved as: %s", user.getName()))
                .setReplyMarkup(inlineKeyboardMarkup));
    }

    private List<partialbotapimethod<? extends="" serializable="">> checkName(User user, String message) {
        // При проверке имени мы превентивно сохраняем пользователю новое Name в базе
        // идея для рефакторинга - добавить временное хранение имени
        user.setName(message);
        userRepository.save(user);

        // Doing кнопку для применения изменений
        InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup();

        List<inlinekeyboardbutton> inlineKeyboardButtonsRowOne = List.of(
                createInlineKeyboardButton("Accept", NAME_ACCEPT));

        inlineKeyboardMarkup.setKeyboard(List.of(inlineKeyboardButtonsRowOne));

        return List.of(createMessageTemplate(user)
                .setText(String.format("You have entered: %s%nIf this is correct - press the button", user.getName()))
                .setReplyMarkup(inlineKeyboardMarkup));
    }

    private List<partialbotapimethod<? extends="" serializable="">> changeName(User user) {
        // При requestе изменения имени мы меняем State
        user.setBotState(State.ENTER_NAME);
        userRepository.save(user);

        // Создаем кнопку для отмены операции
        InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup();

        List<inlinekeyboardbutton> inlineKeyboardButtonsRowOne = List.of(
                createInlineKeyboardButton("Cancel", NAME_CHANGE_CANCEL));

        inlineKeyboardMarkup.setKeyboard(List.of(inlineKeyboardButtonsRowOne));

        return List.of(createMessageTemplate(user).setText(String.format(
                "Your current name is: %s%nEnter new name or press the button to continue", user.getName()))
                .setReplyMarkup(inlineKeyboardMarkup));
    }

    @Override
    public State operatedBotState() {
        return State.ENTER_NAME;
    }

    @Override
    public List<string> operatedCallBackQuery() {
        return List.of(NAME_ACCEPT, NAME_CHANGE, NAME_CHANGE_CANCEL);
    }
}

</string></inlinekeyboardbutton></partialbotapimethod<?></inlinekeyboardbutton></partialbotapimethod<?></inlinekeyboardbutton></partialbotapimethod<?></partialbotapimethod<?>
معالج المساعدة:
package com.whiskels.telegram.bot.handler;

import com.whiskels.telegram.bot.State;
import com.whiskels.telegram.model.User;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static com.whiskels.telegram.bot.handler.RegistrationHandler.NAME_CHANGE;
import static com.whiskels.telegram.util.TelegramUtil.createInlineKeyboardButton;
import static com.whiskels.telegram.util.TelegramUtil.createMessageTemplate;

@Component
public class HelpHandler implements Handler {

    @Override
    public List<partialbotapimethod<? extends="" serializable="">> handle(User user, String message) {
        // Создаем кнопку для смены имени
        InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup();

        List<inlinekeyboardbutton> inlineKeyboardButtonsRowOne = List.of(
                createInlineKeyboardButton("Change name", NAME_CHANGE));

        inlineKeyboardMarkup.setKeyboard(List.of(inlineKeyboardButtonsRowOne));

        return List.of(createMessageTemplate(user).setText(String.format("" +
                "You've asked for help %s? Here it comes!", user.getName()))
        .setReplyMarkup(inlineKeyboardMarkup));

    }

    @Override
    public State operatedBotState() {
        return State.NONE;
    }

    @Override
    public List<string> operatedCallBackQuery() {
        return Collections.emptyList();
    }
}

</string></inlinekeyboardbutton></partialbotapimethod<?>
QuizHandler (الأسوأ
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION