LoginSignup
10
10

More than 5 years have passed since last update.

SpringBoot(Kotlin)+nowでLINE Botを作ってみた

Last updated at Posted at 2017-12-31

はじめに

LINE Advent Calendar 2017Qiitaで人気のLINE関連投稿紹介(保存版)を見て面白そうだったのでLINE Botを作ってみました。

私は普段はJavaを使っていて、SpringBootは趣味の範囲、Kotlinとnowは初心者です。そんな私がLINE BotをSpringBoot(Kotlin)とnowで開発してみてわかったことを中心に書いています。自分用の開発メモですが、誰かの役に立てば幸いです。

LINE Botの作り方を知りたい方は、Qiitaで人気のLINE関連投稿紹介(保存版)の「2. 作り方解説」で紹介されているブログ等をご参考ください。JavaやSpringBootでの開発はQiitaにいくつか記事があるので検索してみてください。

作ったBot

九九学習用のLINE Bot(DBや外部リソースへのアクセスは無し)
https://github.com/uemu/kukubot

九九の掛け算だけでなく、九九の次に習う割り算に向けて、九九の範囲で割り算も学習できます。

screenshot

開発環境

開発の流れ

  1. LINE Messaging APIの登録
  2. Botアプリの開発
  3. Botアプリのデプロイ
  4. LINE Messaging APIのWebhookの設定
  5. 動作確認

1. LINE Messaging APIの登録

下記サイトで開発するBot用のChannelを作成します。丁寧に解説されているブログがありますので、本記事では説明を割愛します。

2. Botアプリの開発

Spring InitializrでGradleプロジェクトを作成

https://start.spring.io/でGradle Project、Kotlin、Spring Boot 1.5.9を選択し、Gradleプロジェクトを作成します。今回作ったBotは特にDependenciesを追加していません。

build.gradleの追記

build.gradle
// Spring InitializrでGradle、Kotlin、Spring Boot 1.5.9を選択して生成したbuild.gradleがベース
// 追加した部分はコメントに「追加: XXX」と記載
buildscript {
    ext {
        kotlinVersion = '1.2.0'
        springBootVersion = '1.5.9.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
        classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
    }
}

apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'idea' // 追加: IntelliJ用
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'uemu'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter')
    compile("org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}")
    compile("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
    compile('com.linecorp.bot:line-bot-spring-boot:1.12.0') // 追加: LINE Botのライブラリ
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

GradleプロジェクトをIntelliJにインポート

下記のようにgradleのideaタスクを実行してIntelliJ関連のファイルを作成し、その後にIntelliJでプロジェクトを開きます。

$ ./gradlew idea

Botアプリの実装

https://github.com/line/line-bot-sdk-java/blob/master/line-bot-spring-boot/README.md
を参考に実装します。

@LineMessageHandlerを付与したクラスを作成し、@EventMappingを付与したメソッドを実装します。

  • handleTextMessageEventはテキストメッセージに対する処理を実装しています。複数のメッセージを返すので戻り値の型をList<TextMessage>にしています。
  • handleDefaultMessageEventはテキスト以外のメッセージに対する処理を実装しています。何も処理せずメッセージも返さない実装になっています。

その他に@PreDestroyを付与したメソッドを実装しています。アプリケーションがスリープした際に利用者に通知します(Herokuでは動作しましたが、nowでは動作しませんでした、、、)。
LINE Messaging API(フリープラン)で動くように、@PreDestroyを付与したメソッドは削除しました。

KukubotApplication.kt
package uemu.kukubot

import com.linecorp.bot.model.event.Event
import com.linecorp.bot.model.event.MessageEvent
import com.linecorp.bot.model.event.message.TextMessageContent
import com.linecorp.bot.model.message.TextMessage
import com.linecorp.bot.spring.boot.annotation.EventMapping
import com.linecorp.bot.spring.boot.annotation.LineMessageHandler
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import java.util.concurrent.ConcurrentHashMap

@SpringBootApplication
@LineMessageHandler
class KukubotApplication {

  private val userContextMap = ConcurrentHashMap<String, UserContext>()

  @EventMapping
  fun handleTextMessageEvent(messageEvent: MessageEvent<TextMessageContent>): List<TextMessage> {
    val context = userContextMap.getOrPut(messageEvent.source.userId) { UserContext() }
    val response = ArrayList<TextMessage>()
    val text = messageEvent.message.text

    if (text == "か") {
      context.mode = text
      context.fixedNumber = 0
      response.add(TextMessage("かけ算になったよ"))
    } else if (text == "わ") {
      context.mode = text
      context.fixedNumber = 0
      response.add(TextMessage("わり算になったよ"))
    } else if (text.matches(Regex("だ[1-9]"))) {
      context.fixedNumber = text.substring(1).toInt()
      response.add(TextMessage("${context.fixedNumber}のだんになったよ"))
    } else if (context.answer != 0 && text.matches(Regex("[0-9]+"))) {
      if (text.toInt() == context.answer) {
        context.combo++
        response.add(TextMessage("正かい!" + if (context.combo == 1) "" else " ${context.combo}れんぞく"))
      } else {
        context.combo = 0
        response.add(TextMessage("ざんねん ... 答えは${context.answer}"))
      }
    } else {
      response.add(TextMessage("[つかい方]\nか -> かけ算になるよ\nわ -> わり算になるよ\nだ数字 -> だんをえらべるよ\n数字 -> 答えられるよ"))
    }

    if (context.mode == "か") {
      context.firstNumber = if (context.fixedNumber == 0) (Math.random() * 9).toInt() + 1 else context.fixedNumber
      context.secondNumber = (Math.random() * 9).toInt() + 1
      context.answer = context.firstNumber * context.secondNumber
      response.add(TextMessage("${context.firstNumber} × ${context.secondNumber} = "))
    } else {
      context.answer = (Math.random() * 9).toInt() + 1
      context.secondNumber = if (context.fixedNumber == 0) (Math.random() * 9).toInt() + 1 else context.fixedNumber
      context.firstNumber = context.secondNumber * context.answer
      response.add(TextMessage("${context.firstNumber} ÷ ${context.secondNumber} = "))
    }

    return response
  }

  @EventMapping
  fun handleDefaultMessageEvent(event: Event) {
  }

}

class UserContext(var mode: String = "か",
                  var fixedNumber: Int = 0,
                  var firstNumber: Int = 0,
                  var secondNumber: Int = 0,
                  var answer: Int = 0,
                  var combo: Int = 0)

fun main(args: Array<String>) {
  SpringApplication.run(KukubotApplication::class.java, *args)
}

設定ファイルの作成

LINE Botが使用するchannelTokenとchannelSecretを環境変数から渡す前提で定義しています。

application.properties
line.bot.channelToken=${CHANNEL_TOKEN}
line.bot.channelSecret=${CHANNEL_SECRET}

3. Botアプリのデプロイ

作成したアプリをnowにデプロイします。nowはDockerコンテナかNode.jsアプリか静的Webサイトをデプロイできます。SpringBootアプリなのでDockerコンテナとしてデプロイします。

nowを使ってみた感想ですが、Webサイトでユーザ登録などをせずにメールでの認証とコマンドラインのみでクラウドにアプリをデプロイできるので、とても便利です!

では、デプロイの準備として、下記の構成でnow作業用のディレクトリにいくつかファイルを作成します。

kukubot(Gradleプロジェクトのルートディレクトリ)
|--now
|  |--Dockerfile
|  |--deploy.sh
|  |--kukubot.jar // gradleのタスクで生成
|  |--now.json
|  |--undeploy.sh

Dockerfileの作成

FROM java:8-jre-alpine
COPY kukubot.jar /
EXPOSE 8080
CMD ["java","-jar","/kukubot.jar"]

nowは内部でDockerfileで指定したポートに転送してくれるみたいです。

Deploying Docker Apps

now.jsonの作成

アプリ名やアプリの別名や環境変数などを設定します。

Configuring Now

now.json
{
  "name": "kukubot",
  "alias": "kukubot",
  "env": {
    "CHANNEL_SECRET": "@channel-secret",
    "CHANNEL_TOKEN": "@channel-token"
  }
}

nameを指定することでデプロイしたアプリのURLが下記のようになります。

https://[name]-[ランダムな文字列].now.sh/

nowにデプロイするたびにURLのランダムな文字列が変わりますが、aliasを指定することでランダムな文字列を含まないURLでアプリにアクセスできるようになります。

https://[alias].now.sh/

OSSプランの場合はnowにデプロイしたアプリのソースが公開されます。公開したくない情報はnow secretで秘密にできます。

now secret add [key] [value]

秘密にした値は@[key]で使用できます。

nowで、LineBotのチャンネルアクセストークンとチャンネルシークレットを隠蔽する
Environment Variables and Secrets

deploy.shとundeploy.shの作成

開発中に頻繁に実施するコマンドをスクリプト化しておきます。undeploy.shはちょっと強引かな。。。

deploy.sh
#!/bin/bash

now && now alias
undeploy.sh
#!/bin/bash

now rm $(now ls|grep "kukubot-"|cut -d" " -f2) //kukubotの部分はnow.jsonのnameに合わせる

build.gradleの追記

テストに必要な環境変数やnow作業ディレクトリにjarを作成するタスクを追記します。

build.gradle

・・・

// 追加: nowにデプロイする前に実行するビルドタスク
task buildForNow(type: Copy, dependsOn: build) {
    from jar.archivePath
    into project.file('now')
    rename {
        'kukubot.jar'
    }
}

// 追加: cleanタスクの後にnow用のjarを削除
clean.doLast { //+
    project.file('now/kukubot.jar').delete()
}

デプロイ

gradleのbuildForNowタスクを実行した後に、nowディレクトリに移動してdeploy.shを実行してデプロイします。

$ ./gradlew buildForNow
$ cd now
$ bash deploy.sh

4. LINE Messaging APIのWebhookの設定

「1. LINE Messaging APIの登録」で作成したChannelの基本設定でWebhook URLにデプロイしたアプリのURLを設定します。

https://[alias].now.sh/callback

接続確認が正常終了すれば設定完了です。

5. 動作確認

作成したBotをLINEの友達に登録し、動作確認を行います。
娘に使ってもらったところ、すぐに九九の範囲の割り算を覚えてしまい、作ったものの早くも役目を終えてしまいました。。。

kukubot-check.png

10
10
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
10
10