JavaRush /Java Blog /Random-JA /Spring Boot を使用したテレグラム ボットの作成 Pt.3: クイズ ボット
Whiskels
レベル 41
Москва

Spring Boot を使用したテレグラム ボットの作成 Pt.3: クイズ ボット

Random-JA グループに公開済み
パート 1 パート 2 残念ながら、記事の最初のバージョンは文字数制限に収まらなかったので、ここで終了します。 QuizHandler:
package com.whiskels.telegram.bot.handler;

import com.whiskels.telegram.bot.State;
import com.whiskels.telegram.model.Question;
import com.whiskels.telegram.model.User;
import com.whiskels.telegram.repository.JpaQuestionRepository;
import com.whiskels.telegram.repository.JpaUserRepository;
import lombok.extern.slf4j.Slf4j;
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.ArrayList;
import java.util.Collections;
import java.util.List;

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

@Slf4j
@Component
public class QuizHandler implements Handler {
    //Храним поддерживаемые CallBackQuery в виде констант
    public static final String QUIZ_CORRECT = "/quiz_correct";
    public static final String QUIZ_INCORRECT = "/quiz_incorrect";
    public static final String QUIZ_START = "/quiz_start";
    //Храним варианты ответа
    private static final List<string> OPTIONS = List.of("A", "B", "C", "D");

    private final JpaUserRepository userRepository;
    private final JpaQuestionRepository questionRepository;

    public QuizHandler(JpaUserRepository userRepository, JpaQuestionRepository questionRepository) {
        this.userRepository = userRepository;
        this.questionRepository = questionRepository;
    }

    @Override
    public List<partialbotapimethod<? extends="" serializable="">> handle(User user, String message) {
        if (message.startsWith(QUIZ_CORRECT)) {
            // действие на коллбек с правильным ответом
            return correctAnswer(user, message);
        } else if (message.startsWith(QUIZ_INCORRECT)) {
            // действие на коллбек с неправильным ответом
            return incorrectAnswer(user);
        } else {
            return startNewQuiz(user);
        }
    }

    private List<partialbotapimethod<? extends="" serializable="">> correctAnswer(User user, String message) {
        log.info("correct");
        final int currentScore = user.getScore() + 1;
        user.setScore(currentScore);
        userRepository.save(user);

        return nextQuestion(user);
    }

    private List<partialbotapimethod<? extends="" serializable="">> incorrectAnswer(User user) {
        final int currentScore = user.getScore();
        // Обновляем лучший итог
        if (user.getHighScore() < currentScore) {
            user.setHighScore(currentScore);
        }
        // Меняем статус пользователя
        user.setScore(0);
        user.setBotState(State.NONE);
        userRepository.save(user);

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

        List<inlinekeyboardbutton> inlineKeyboardButtonsRowOne = List.of(
                createInlineKeyboardButton("Try again?", QUIZ_START));

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

        return List.of(createMessageTemplate(user)
                .setText(String.format("Incorrect!%nYou scored *%d* points!", currentScore))
                .setReplyMarkup(inlineKeyboardMarkup));
    }

    private List<partialbotapimethod<? extends="" serializable="">> startNewQuiz(User user) {
        user.setBotState(State.PLAYING_QUIZ);
        userRepository.save(user);

        return nextQuestion(user);
    }

    private List<partialbotapimethod<? extends="" serializable="">> nextQuestion(User user) {
        Question question = questionRepository.getRandomQuestion();

        // Собираем список возможных вариантов ответа
        List<string> options = new ArrayList<>(List.of(question.getCorrectAnswer(), question.getOptionOne(), question.getOptionTwo(), question.getOptionThree()));
        // Перемешиваем
        Collections.shuffle(options);

        // Начинаем формировать сообщение с вопроса
        StringBuilder sb = new StringBuilder();
        sb.append('*')
                .append(question.getQuestion())
                .append("*\n\n");

        InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup();

        // Создаем два ряда кнопок
        List<inlinekeyboardbutton> inlineKeyboardButtonsRowOne = new ArrayList<>();
        List<inlinekeyboardbutton> inlineKeyboardButtonsRowTwo = new ArrayList<>();

        // Формируем сообщение и записываем CallBackData на кнопки
        for (int i = 0; i < options.size(); i++) {
            InlineKeyboardButton button = new InlineKeyboardButton();

            final String callbackData = options.get(i).equalsIgnoreCase(question.getCorrectAnswer()) ? QUIZ_CORRECT : QUIZ_INCORRECT;

            button.setText(OPTIONS.get(i))
                    .setCallbackData(String.format("%s %d", callbackData, question.getId()));

            if (i < 2) {
                inlineKeyboardButtonsRowOne.add(button);
            } else {
                inlineKeyboardButtonsRowTwo.add(button);
            }
            sb.append(OPTIONS.get(i) + ". " + options.get(i));
            sb.append("\n");
        }

        inlineKeyboardMarkup.setKeyboard(List.of(inlineKeyboardButtonsRowOne, inlineKeyboardButtonsRowTwo));
        return List.of(createMessageTemplate(user)
                .setText(sb.toString())
                .setReplyMarkup(inlineKeyboardMarkup));
    }

    @Override
    public State operatedBotState() {
        return null;
    }

    @Override
    public List<string> operatedCallBackQuery() {
        return List.of(QUIZ_START, QUIZ_CORRECT, QUIZ_INCORRECT);
    }
}
<h3>データベースへの書き込み</h3>ここでは、データベース初期化スクリプトから類推して質問を書き留めます。
DELETE
FROM java_quiz;

INSERT INTO java_quiz (question, answer_correct, option1, option2, option3)
VALUES ('What is a correct syntax to output "Hello World" in Java?', 'System.out.println("Hello World!");',
        'print("Hello World!");', 'sout("Hello World!");', 'Systemout.print("Hello world!");'),
       ('What is the correct way to create an object called foo of Bar class?', 'Bar foo = new Bar();',
        'Foo bar = new Foo();', 'Bar foo() = new Foo();', 'Foo bar() = new Bar();'),
       ('Which operator can be used to compare two values?', '==', '=', '&', '==='),
       ('Which method can be used to return a string in upper case letters?', 'toUpperCase()', 'camelCase()',
        'upperCase()', 'formatUpper()'),
       ('Which method can be used to find the length of a string?', 'length()', 'getSize()', 'len()', 'getLength()'),
       ('Which data type is used to create a variable that should store text?', 'String', 'Text', 'Varchar', 'const'),
       ('How to insert a comment?', '// like this', '# like this', '<-- like this -->', '/ like this');
残っているのは、application.yaml を修正することだけです。
bot:
  name: JavaQuiz
  token: 1234567:AAF0Wru1Z60p8vPtKihx3odbwSv9O0y_-MM
spring:
  datasource:
    url: jdbc:postgresql://ec2-54-75-199-252.eu-west-1.compute.amazonaws.com:5432/d5p9skg6nin3mh?user=bozuqwnhjhoubl&password=8a40050de8a0014c14df49aeaac5880f1ac633cc20c78a4cc3b32323231
    driver-class-name: org.postgresql.Driver
    initialization-mode: never
  jpa:
    database-platform: org.hibernate.dialect.PostgreSQLDialect
    show-sql: true
    hibernate:
      ddl-auto: update
データベース URL は Heroku の URI フィールドにリストされているものとは異なるため、データベースの構築方法に関する簡単なチートシートを次に示します。
jdbc:postgresql://{Host}:{Port}/{Database}?user={User}&password={Password}
すべてが正しく行われた場合、main を実行してもスタックトレースは表示されません。また、その逆も同様です。
.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.4.RELEASE)

2020-10-02 19:27:43.081  INFO 14536 --- [           main] com.whiskels.telegram.App                : Starting App on Kuzmin with PID 14536 (D:\utilities\forJavaRush\target\classes started by whiskels in D:\utilities\forJavaRush)
2020-10-02 19:27:43.087  INFO 14536 --- [           main] com.whiskels.telegram.App                : No active profile set, falling back to default profiles: default
2020-10-02 19:27:44.014  INFO 14536 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-10-02 19:27:44.131  INFO 14536 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 102ms. Found 2 JPA repository interfaces.
2020-10-02 19:27:44.774  INFO 14536 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-10-02 19:27:44.885  INFO 14536 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {5.4.10.Final}
2020-10-02 19:27:45.053  INFO 14536 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-10-02 19:27:45.278  INFO 14536 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-10-02 19:27:46.770  INFO 14536 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-10-02 19:27:46.790  INFO 14536 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
2020-10-02 19:27:49.731  INFO 14536 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-10-02 19:27:49.741  INFO 14536 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-10-02 19:27:50.817  INFO 14536 --- [           main] c.g.x.bots.TelegramBotAutoConfiguration  : Starting auto config for telegram bots
2020-10-02 19:27:50.830  INFO 14536 --- [           main] c.g.x.bots.TelegramBotAutoConfiguration  : Initializing API without webhook support
2020-10-02 19:27:50.831  INFO 14536 --- [           main] c.g.x.bots.TelegramBotAutoConfiguration  : Registering polling bot: JavaQuiz
2020-10-02 19:27:51.556  INFO 14536 --- [           main] com.whiskels.telegram.App                : Started App in 9.196 seconds (JVM running for 10.023)
ボットと通信してみましょう: 知り合いになろうSpring Boot を使用したテレグラム ボットの作成 Pt.3: クイズ ボット - 1プレイしてみよSpring Boot を使用したテレグラム ボットの作成 Pt.3: クイズ ボット - 2う 助けを求めようSpring Boot を使用したテレグラム ボットの作成 Pt.3: クイズ ボット - 3完了! クイズボットを書きました。今、私たちの前には大きな仕事が待っています。必要なものは次のとおりです。
  • 今日書かれたコードを理解する。
  • Spring Data のドキュメントを読んでください。
  • ロガーを追加します。
  • リファクタリングを行う。
  • デザインパターンを適用する。
  • State.NONE だけでなく助けを求めることができるようにします。
  • 新しい機能を追加します。
  • リーダーボード;
  • ユニークな質問のみを発行します。
  • 質問のカテゴリー。
この 2 つの記事がお役に立てば幸いです。もしそうなら、私のリポジトリにスターを付けてください。喜んでさせていただきます! ご質問がございましたら、コメントで検討させていただきます。そして最後に - プロジェクト図: Spring Boot を使用したテレグラム ボットの作成 Pt.3: クイズ ボット - 4 UPDATE 10.12: プロジェクト コードは変更せずにそのまま gitに投稿されました。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION