LoginSignup
8
2

More than 1 year has passed since last update.

Spring boot + JPA + Flyway on herokuでLINE Botを作る

Last updated at Posted at 2021-07-26

概要

Spring BootとJPAとFlywayとherokuを使って、DBから返信メッセージを取得するLINE Botを作る。
あくまでちょっと動かしてみた程度のソースなので、細かいところのツッコミはご容赦ください。
また以下については説明しないので、リンク先を参照してください。

関連

前提

lineが配布しているサンプルコードを使う。
https://github.com/line/line-bot-sdk-java.git

環境

  • Java 11.02
  • Spring Boot 2.4.9 (line-bot-sdk-javaで使われているversion)

要件

LINEでbot宛にメッセージを送信すると、その送信文字列に応じた返信メッセージをDBのTableから抽出し、返信するシステムを作る。返信の条件は以下の通り。

送信Message 返信Message
ハロー ハローワールド
hello Hello world!!
それ以外 返信しない

アーキテクチャ

  • heroku
  • PostgreSQL 13.3 (on heroku)

ディレクトリ構成

サンプルコードの中で、今回編集、新規追加するファイルだけを以下に記した。

.
├ ─ ─  Procfile
└ ─ ─  sample-spring-boot-kitchensink
     ├ ─ ─  build.gradle
     └ ─ ─  src
         └ ─ ─  main
             ├ ─ ─  java
             │    └ ─ ─  com
             │        └ ─ ─  example
             │            └ ─ ─  bot
             │                └ ─ ─  spring
             │                    ├ ─ ─  KitchenSinkController.java
             │                    ├ ─ ─  models
             │                    │    └ ─ ─  TextMessageModel.java
             │                    ├ ─ ─  repositories
             │                    │    └ ─ ─  TextMessageRepository.java
             │                    └ ─ ─  services
             │                         └ ─ ─  TextMessageService.java
             └ ─ ─  resources
                 ├ ─ ─  application.yml
                 └ ─ ─  db
                     └ ─ ─  migration
                         └ ─ ─  V1_0__create_table_text_messages.sql

サンプルコードの中のsample-spring-boot-kitchensinkを利用するので、以下3つのディレクトリは消してしまってもOK。

  • sample-manage-audience
  • sample-spring-boot-echo
  • sample-spring-boot-echo-kotlin

DB Table

text_messagesという返信用メッセージを入れるテーブルを作成する。Flywayでmigrateするので、手動での作成は不要。

line-bot-sample::DATABASE=> \d text_messages;
                                    Table "public.text_messages"
 Column  |          Type          | Collation | Nullable |                  Default
---------+------------------------+-----------+----------+-------------------------------------------
 id      | integer                |           | not null | nextval('text_messages_id_seq'::regclass)
 message | character varying(255) |           | not null |
Indexes:
    "text_messages_pkey" PRIMARY KEY, btree (id)

GitHUB

完成品はこちら
https://github.com/Esfahan/line-bot-java-sample

作成手順

サンプルコードの取得

lineが配布しているサンプルコードを使う。

$ git clone https://github.com/line/line-bot-sdk-java.git

JPAとflywayを利用できるようにする

sample-spring-boot-kitchensink/build.gradleに以下を追記する。

sample-spring-boot-kitchensink/build.gradle
dependencies {
     implementation project(':line-bot-spring-boot')
     implementation "org.springframework.boot:spring-boot-starter-web"
     implementation 'com.google.guava:guava'
+    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+    implementation 'org.flywaydb:flyway-core'
+    runtimeOnly 'org.postgresql:postgresql'
 }

application.ymlを作成

sample-spring-boot-kitchensink/src/main/resources/application.ymlを新規作成する。
環境変数の値については後述する。

line.bot:
  channel-token: ${CHANNEL_TOKEN}
  channel-secret: ${CHANNEL_SECRET}
  handler.path: /callback

spring:
  jpa:
    database: POSTGRESQL
    open-in-view: True
  datasource:
    url: ${DATABASE_JDBC_URL}
    username: ${DATABASE_USER}
    password: ${DATABASE_PASSWORD}
    driver-class-name: org.postgresql.Driver
    connectionProperties: useUnicode=true;characterEncoding=utf-8;
  flyway:
    locations: classpath:/db/migration
    baseline-on-migrate: true

その他のプロパティは以下を参照。
https://spring.pleiades.io/spring-boot/docs/current/reference/html/application-properties.html

Migrationファイルを作成

マイグレーションファイルの命名規則は以下を参照。
https://garafu.blogspot.com/2020/06/how-to-use-flyway.html

今回は以下の名前で作成する。
V1_0__create_table_text_messages.sql

application.ymlのflyway.locationsで定義したように、resources/db/migration/にsqlファイルを設置するとmigrationが実行されるので、ディレクトリを作ってsqlファイルを作成する。

$ mkdir -p sample-spring-boot-kitchensink/src/main/resources/db/migration/
$ vi sample-spring-boot-kitchensink/src/main/resources/db/migration/V1_0__create_table_text_messages.sql
V1_0__create_table_text_messages.sql
CREATE TABLE text_messages(
  id serial PRIMARY KEY,
  message varchar(255) NOT NULL
);

ディレクトリ作成

各ソースコードを設置するmodels/,repositories/,services/ディレクトリを作成する。

$ mkdir sample-spring-boot-kitchensink/src/main/java/com/example/bot/spring/{models,repositories,services}

Modelの作成

以下のファイルを新規作成する。
sample-spring-boot-kitchensink/src/main/java/com/example/bot/spring/models/TextMessageModel.java

TextMessageModel.java
package com.example.bot.spring.models;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="text_messages")
public class TextMessageModel {
    @Id
    private Integer id;

    public String message;
}

Repositoryの作成

以下のファイルを新規作成する。
sample-spring-boot-kitchensink/src/main/java/com/example/bot/spring/repositories/TextMessageRepository.java

TextMessageRepository.java
package com.example.bot.spring.repositories;

import com.example.bot.spring.models.TextMessageModel;
import org.springframework.data.jpa.repository.JpaRepository;

public interface TextMessageRepository extends JpaRepository<TextMessageModel, Integer> {
}

Serviceの作成

以下のファイルを新規作成する。
sample-spring-boot-kitchensink/src/main/java/com/example/bot/spring/services/TextMessageService.java

TextMessageService.java
package com.example.bot.spring.services;

import com.example.bot.spring.repositories.TextMessageRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class TextMessageService {
    private String replyTextMessage;
    private boolean replyFlg;

    @Autowired
    TextMessageRepository textMessageRepository;

    public void fetchReplyTextMessage (String message) {
        log.info("fetchReplyTextMessage is called: {}", message);

        Integer id = replyId(message);
        if (id > 0) {
            this.replyTextMessage = textMessageRepository.findById(id).get().message;
            this.replyFlg = true;
        } else {
            this.replyFlg = false;
        }
    }

    public String getReplyMessage() {
        return this.replyTextMessage;
    }

    public boolean getReplyFlg() {
        return this.replyFlg;
    }

    private Integer replyId(String message) {
        switch (message) {
            case "ハロー": {
                return 1;
            }
            case "hello": {
                return 2;
            }
            default: {
                return 0;
            }
        }
    }
}

controller

以下のファイルを編集する。
sample-spring-boot-kitchensink/src/main/java/com/example/bot/spring/KitchenSinkController.java

ここでは、処理内容をシンプルに分かりやすくするため、handleTextMessageEventメソッド1つで全ての処理するように書き換える。

KitchenSinkController.java
+import com.example.bot.spring.services.TextMessageService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

public class KitchenSinkController {
     @Autowired
     private LineBlobClient lineBlobClient;

+    @Autowired
+    private TextMessageService textMessageService;
+
     @EventMapping
     public void handleTextMessageEvent(MessageEvent<TextMessageContent> event) throws Exception {
         TextMessageContent message = event.getMessage();
-        handleTextContent(event.getReplyToken(), event, message);
+
+        log.info("TextMessageContent is called");
+
+        textMessageService.fetchReplyTextMessage(message.getText());
+
+        if (textMessageService.getReplyFlg()) {
+            log.info("replyFlg is true");
+
+            String replyText = textMessageService.getReplyMessage();
+            List<Message> replyMessage = singletonList(new TextMessage(replyText));
+            String replyToken = event.getReplyToken();
+
+            log.info("Returns echo message {}: {}", replyToken, replyText);
+
+            boolean notificationDisabled = false;
+            try {
+                BotApiResponse apiResponse = lineMessagingClient
+                        .replyMessage(new ReplyMessage(replyToken, replyMessage, notificationDisabled))
+                        .get();
+                log.info("Sent messages: {}", apiResponse);
+            } catch (InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+        }
     }

Procfileを書き換え

sample-spring-boot-kitchensinkを利用するため、Procfileを以下の様に書き換える。

-web: java $JAVA_OPTS -jar sample-spring-boot-echo/build/libs/sample-spring-boot-echo-*.jar --server.port=$PORT
+web: java $JAVA_OPTS -jar sample-spring-boot-kitchensink/build/libs/sample-spring-boot-kitchensink-*.jar --server.port=$PORT

.gitignore

*.jarをignoreすると./gradle/wrapper/gradle-wrapper.jarがDeployされず、以下のエラーが出るので編集する。
Error: Could not find or load main class org.gradle.wrapper.GradleWrapperMain

.gitignore
# Package Files #
-*.jar
+#*.jar
 *.war
 *.ear

herokuの環境変数設定

herokuに以下の環境変数を設定する。

Key Value
CHANNEL_SECRET LINEのChannel access token
CHANNEL_TOKEN LINEのChannel secret
DATABSE_JDBC_URI jdbc:postgresql://herokuのpostgresのhostname:5432/dbname
DATABSE_USER herokuのpostgresのuser name
DATABSE_PASSWORD herokuのpostgresのpassword

Deploy

herokuにdeployする。

$ git remote add heroku https://git.heroku.com/${HEROKU_APP_NAME}.git
$ git add --all
$ git commit -m 'Added a sample codeset for Qiita'
$ git push heroku master

Migrationの結果を確認

herokuにdeployすると、postgresに以下のテーブルが作成される。
Intellijなどローカルの場合は、IDEでプログラムを実行すれば接続先のDBにテーブルが作成される。

$ heroku pg:psql postgresql-abcdefg-12345 --app line-bot-sample
line-bot-sample::DATABASE=> \d
                     List of relations
 Schema |         Name          |   Type   |     Owner
--------+-----------------------+----------+----------------
 public | flyway_schema_history | table    | sample-user
 public | text_messages         | table    | sample-user
 public | text_messages_id_seq  | sequence | sample-user
(3 rows)

Insert

DBに以下をinsertする。これが、返信用メッセージとなる。

line-bot-sample::DATABASE=> insert into text_messages (message) values ('ハローワールド');
INSERT 0 1
line-bot-sample::DATABASE=> insert into text_messages (message) values ('Hello world!!');
INSERT 0 1
line-bot-sample::DATABASE=> select * from text_messages;
 id |    message
----+----------------
  1 | ハローワールド
  2 | Hello world!!
(2 rows)

LINEでWebhook URLを設定

LINE DeveloperのMessage APIのWebhook Settingsに、herokuのURLを登録する。

Webhook URL: https://{HEROKU_APP_NAME}.herokuapp.com/callback
Use webhook: onにする

これでbotにメッセージを送信すると、以下のような結果となる。
IMG_5259.PNG

テストコードを書く場合

以下にtest用ディレクトリを作成し、その下にテストコードを作成する。

$ mkdir -p sample-spring-boot-kitchensink/src/test/java/com/example/bot/spring/

参考

https://tosi-tech.net/2019/04/flyway-with-spring-boot/
https://tomokazu-kozuma.com/how-to-migrate-mysql-using-flyway/

関連

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2