LINE Messaging APIをSpringBoot(on Heroku)で試す

# はじめに
LINE Messaging APIをSpringBootで実装するSDKが公式からでていたので試す。
LINE Messaging APIは、httpsでの通信が前提なので、Herokuにアプリケーションをデプロイ(無料)してテストする。

# 手順

基本的には、[公式SDK](https://github.com/line/line-bot-sdk-java/tree/master/sample-spring-boot-echo)のgithubのマニュアルのとおり実施しますが、botの内容を発言したユーザーのユーザーIDを返信する処理にしました。
自分のLINEのユーザーIDを知る術がわからなかったので、replyのメッセージから取得したかったというのもあります。
※詳しい人は教えてください。

1. SpringBootのプロジェクトを作成
2. LINE Messaging APIの公式SDKの`line-bot-spring-boot`をgradleの依存に追加
3. sampleを参考にSpringBootの起動クラスにコードを追加
4. Herokuにデプロイ
4. LINE Messaging APIの`Messaging API Developer Trial`アカウントを追加(もろもろの設定とシークレットとトークンを取得)
5. Herokuの設定にシークレットとトークンを設定
6. 動作確認

# 詳細

## SpringBootのプロジェクトを作成
以下の記事を参考にSpringBootの雛形プロジェクトを作成

[Spring BootをHerokuにデプロイするのが劇的に簡単になっている件](http://qiita.com/gosshys/items/6d409a245a108ad25202)

※Herokuにアプリケーションデプロイするところまで実施する

## LINE Messaging APIの公式SDKをgradleの依存に追加

build.gradleの`dependencies`に以下を追加

```
dependencies {
  compile('com.linecorp.bot:line-bot-spring-boot:1.3.0') // ココを追加
  compile('org.springframework.boot:spring-boot-starter-actuator')
  compile('org.springframework.boot:spring-boot-starter-web')
  testCompile('org.springframework.boot:spring-boot-starter-test')
}
```

## sampleを参考にSpringBootの起動クラスにコード追加

[公式SDKのsample実装](https://github.com/line/line-bot-sdk-java/blob/master/sample-spring-boot-echo/src/main/java/com/example/bot/spring/echo/EchoApplication.java)を参考に以下のようにSpringBootの起動クラスを修正

サンドイッチコメントで修正箇所を記載しています。クラスにも`@LineMessageHandler`アノテーションを付けるのをわすれずに。
importはsampleを参考にしてください。

```
package com.example;

import com.linecorp.bot.client.LineMessagingService;
import com.linecorp.bot.model.ReplyMessage;
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.model.response.BotApiResponse;
import com.linecorp.bot.spring.boot.annotation.EventMapping;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.Collections;

@SpringBootApplication
@LineMessageHandler //----- ココを追加
public class DemoApplication {

  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }

//----- ここから -----
  @Autowired
  private LineMessagingService lineMessagingService;

  @EventMapping
  public void handleTextMessageEvent(MessageEvent<TextMessageContent> event) throws Exception {
    System.out.println("event: " + event);
    final BotApiResponse apiResponse = lineMessagingService
        .replyMessage(new ReplyMessage(event.getReplyToken(),
                                       Collections.singletonList(new TextMessage(event.getSource().getUserId()))))
        .execute().body();
    System.out.println("Sent messages: " + apiResponse);
  }

  @EventMapping
  public void defaultMessageEvent(Event event) {
    System.out.println("event: " + event);
  }
//----- ここまで追加 -----

}

```

## Herokuにデプロイ

```
git commit -am "add line bot"
git push heroku master
```

デプロイは成功するはず。Herokuのログを確認すると、

```
heroku logs --tail
```

以下のエラーが発生する

### 抜粋

```
2016-11-23T12:34:48.860128+00:00 app[web.1]: Field error in object 'line.bot' on field 'channelToken': rejected value [null]; codes [NotNull.line.bot.channelToken,NotNull.channelToken,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [line.bot.channelToken,channelToken]; arguments []; default message [channelToken]]; default message [may not be null]
2016-11-23T12:34:48.860129+00:00 app[web.1]: Field error in object 'line.bot' on field 'channelSecret': rejected value [null]; codes [NotNull.line.bot.channelSecret,NotNull.channelSecret,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [line.bot.channelSecret,channelSecret]; arguments []; default message [channelSecret]]; default message [may not be null]
```

### 全体

```
2016-11-23T12:34:25.585995+00:00 heroku[web.1]: Starting process with command `java -Dserver.port=26054 $JAVA_OPTS -jar build/libs/*.jar`
2016-11-23T12:34:28.350814+00:00 app[web.1]: Create a Procfile to customize the command used to run this process: https://devcenter.heroku.com/articles/procfile
2016-11-23T12:34:28.363096+00:00 app[web.1]: Setting JAVA_TOOL_OPTIONS defaults based on dyno size. Custom settings will override them.
2016-11-23T12:34:28.370890+00:00 app[web.1]: Picked up JAVA_TOOL_OPTIONS: -Xmx350m -Xss512k -Dfile.encoding=UTF-8
2016-11-23T12:34:33.611174+00:00 app[web.1]: 
2016-11-23T12:34:33.611223+00:00 app[web.1]:   .   ____          _            __ _ _
2016-11-23T12:34:33.611274+00:00 app[web.1]:  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
2016-11-23T12:34:33.611327+00:00 app[web.1]: ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
2016-11-23T12:34:33.611374+00:00 app[web.1]:  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
2016-11-23T12:34:33.611423+00:00 app[web.1]:   '  |____| .__|_| |_|_| |_\__, | / / / /
2016-11-23T12:34:33.611471+00:00 app[web.1]:  =========|_|==============|___/=/_/_/_/
2016-11-23T12:34:33.616715+00:00 app[web.1]:  :: Spring Boot ::        (v1.4.2.RELEASE)
2016-11-23T12:34:33.616758+00:00 app[web.1]: 
2016-11-23T12:34:35.077856+00:00 app[web.1]: 2016-11-23 12:34:35.043  INFO 4 --- [           main] com.example.DemoApplication              : Starting DemoApplication on 3fec34e1-1683-4668-b572-8d779c0b0d2a with PID 4 (/app/build/libs/demo-0.0.1-SNAPSHOT.jar started by u52915 in /app)
2016-11-23T12:34:35.099269+00:00 app[web.1]: 2016-11-23 12:34:35.078  INFO 4 --- [           main] com.example.DemoApplication              : No active profile set, falling back to default profiles: default
2016-11-23T12:34:35.786319+00:00 app[web.1]: 2016-11-23 12:34:35.770  INFO 4 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@6659c656: startup date [Wed Nov 23 12:34:35 UTC 2016]; root of context hierarchy
2016-11-23T12:34:45.729917+00:00 app[web.1]: 2016-11-23 12:34:45.729  INFO 4 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 26054 (http)
2016-11-23T12:34:45.758893+00:00 app[web.1]: 2016-11-23 12:34:45.758  INFO 4 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2016-11-23T12:34:45.761198+00:00 app[web.1]: 2016-11-23 12:34:45.760  INFO 4 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.6
2016-11-23T12:34:46.348214+00:00 app[web.1]: 2016-11-23 12:34:46.346  INFO 4 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2016-11-23T12:34:46.349784+00:00 app[web.1]: 2016-11-23 12:34:46.349  INFO 4 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 10608 ms
2016-11-23T12:34:48.252663+00:00 app[web.1]: 2016-11-23 12:34:48.252  INFO 4 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2016-11-23T12:34:48.290693+00:00 app[web.1]: 2016-11-23 12:34:48.290  INFO 4 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'metricsFilter' to: [/*]
2016-11-23T12:34:48.291348+00:00 app[web.1]: 2016-11-23 12:34:48.291  INFO 4 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2016-11-23T12:34:48.291501+00:00 app[web.1]: 2016-11-23 12:34:48.291  INFO 4 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2016-11-23T12:34:48.291646+00:00 app[web.1]: 2016-11-23 12:34:48.291  INFO 4 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2016-11-23T12:34:48.291783+00:00 app[web.1]: 2016-11-23 12:34:48.291  INFO 4 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2016-11-23T12:34:48.291919+00:00 app[web.1]: 2016-11-23 12:34:48.291  INFO 4 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'webRequestLoggingFilter' to: [/*]
2016-11-23T12:34:48.292078+00:00 app[web.1]: 2016-11-23 12:34:48.291  INFO 4 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'applicationContextIdFilter' to: [/*]
2016-11-23T12:34:48.852189+00:00 app[web.1]: 2016-11-23 12:34:48.851 ERROR 4 --- [           main] o.s.b.b.PropertiesConfigurationFactory   : Properties configuration failed validation
2016-11-23T12:34:48.852531+00:00 app[web.1]: 2016-11-23 12:34:48.852 ERROR 4 --- [           main] o.s.b.b.PropertiesConfigurationFactory   : Field error in object 'line.bot' on field 'channelToken': rejected value [null]; codes [NotNull.line.bot.channelToken,NotNull.channelToken,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [line.bot.channelToken,channelToken]; arguments []; default message [channelToken]]; default message [may not be null]
2016-11-23T12:34:48.856823+00:00 app[web.1]: 2016-11-23 12:34:48.852 ERROR 4 --- [           main] o.s.b.b.PropertiesConfigurationFactory   : Field error in object 'line.bot' on field 'channelSecret': rejected value [null]; codes [NotNull.line.bot.channelSecret,NotNull.channelSecret,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [line.bot.channelSecret,channelSecret]; arguments []; default message [channelSecret]]; default message [may not be null]
2016-11-23T12:34:48.860126+00:00 app[web.1]: 2016-11-23 12:34:48.859  WARN 4 --- [           main] ationConfigEmbeddedWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'demoApplication': Unsatisfied dependency expressed through field 'lineMessagingService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.linecorp.bot.spring.boot.LineBotAutoConfiguration': Unsatisfied dependency expressed through field 'lineBotProperties'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'line.bot-com.linecorp.bot.spring.boot.LineBotProperties': Could not bind properties to LineBotProperties (prefix=line.bot, ignoreInvalidFields=false, ignoreUnknownFields=true, ignoreNestedProperties=false); nested exception is org.springframework.validation.BindException: org.springframework.boot.bind.RelaxedDataBinder$RelaxedBeanPropertyBindingResult: 2 errors
2016-11-23T12:34:48.860128+00:00 app[web.1]: Field error in object 'line.bot' on field 'channelToken': rejected value [null]; codes [NotNull.line.bot.channelToken,NotNull.channelToken,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [line.bot.channelToken,channelToken]; arguments []; default message [channelToken]]; default message [may not be null]
2016-11-23T12:34:48.860129+00:00 app[web.1]: Field error in object 'line.bot' on field 'channelSecret': rejected value [null]; codes [NotNull.line.bot.channelSecret,NotNull.channelSecret,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [line.bot.channelSecret,channelSecret]; arguments []; default message [channelSecret]]; default message [may not be null]
2016-11-23T12:34:48.864487+00:00 app[web.1]: 2016-11-23 12:34:48.864  INFO 4 --- [           main] o.apache.catalina.core.StandardService   : Stopping service Tomcat
2016-11-23T12:34:49.049475+00:00 app[web.1]: 2016-11-23 12:34:49.047  INFO 4 --- [           main] utoConfigurationReportLoggingInitializer : 
2016-11-23T12:34:49.049477+00:00 app[web.1]: 
2016-11-23T12:34:49.049478+00:00 app[web.1]: Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
2016-11-23T12:34:49.142077+00:00 app[web.1]: 2016-11-23 12:34:49.141 ERROR 4 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 
2016-11-23T12:34:49.142090+00:00 app[web.1]: 
2016-11-23T12:34:49.142091+00:00 app[web.1]: ***************************
2016-11-23T12:34:49.142092+00:00 app[web.1]: APPLICATION FAILED TO START
2016-11-23T12:34:49.142092+00:00 app[web.1]: ***************************
2016-11-23T12:34:49.142093+00:00 app[web.1]: 
2016-11-23T12:34:49.142094+00:00 app[web.1]: Description:
2016-11-23T12:34:49.142094+00:00 app[web.1]: 
2016-11-23T12:34:49.142095+00:00 app[web.1]: Binding to target LineBotProperties(channelToken=null, channelSecret=null, apiEndPoint=https://api.line.me/, connectTimeout=10000, readTimeout=10000, writeTimeout=10000, handler=LineBotProperties.Handler(enabled=true, path=/callback)) failed:
2016-11-23T12:34:49.142096+00:00 app[web.1]: 
2016-11-23T12:34:49.142097+00:00 app[web.1]:     Property: line.bot.channelToken
2016-11-23T12:34:49.142097+00:00 app[web.1]:     Value: null
2016-11-23T12:34:49.142098+00:00 app[web.1]:     Reason: may not be null
2016-11-23T12:34:49.142098+00:00 app[web.1]: 
2016-11-23T12:34:49.142099+00:00 app[web.1]:     Property: line.bot.channelSecret
2016-11-23T12:34:49.142100+00:00 app[web.1]:     Value: null
2016-11-23T12:34:49.142100+00:00 app[web.1]:     Reason: may not be null
2016-11-23T12:34:49.142101+00:00 app[web.1]: 
2016-11-23T12:34:49.142102+00:00 app[web.1]: 
2016-11-23T12:34:49.142102+00:00 app[web.1]: Action:
2016-11-23T12:34:49.142103+00:00 app[web.1]: 
2016-11-23T12:34:49.142103+00:00 app[web.1]: Update your application's configuration
2016-11-23T12:34:49.142104+00:00 app[web.1]: 
2016-11-23T12:34:49.346537+00:00 heroku[web.1]: Process exited with status 1
2016-11-23T12:34:49.361013+00:00 heroku[web.1]: State changed from starting to crashed
```

エラーになってもここまでくればOK

## LINE Messaging APIのアカウントを追加

まず、[LINE BUSINESS CENTER](https://business.line.me/ja/companies/1235502/)でアカウント登録が必要かもしれないので、未登録の人は登録してください。メールアドレスとパスワードを入れて認証(LINEに4桁のコードが送られてくる)するだけです。

また、公式の[ドキュメント](https://developers.line.me/messaging-api/getting-started#create_lineat)にわかりやすい動画(36秒)があるのでそれを見たほうが早いかもしれません。


[Messaging API](https://business.line.me/ja/companies/1235502/services/bot)のページの「Develper Trialを始める」のボタンをクリック
![image](https://qiita-image-store.s3.amazonaws.com/0/38174/de0e1103-dd15-4374-0fca-93a24d25fa5c.png)
アカウントの作成画面がでるので、アカウント名に適当な名前と、業種を適当に選択し、「確認する」ボタンをクリック
![image](https://qiita-image-store.s3.amazonaws.com/0/38174/3b528342-5b44-bd34-b899-109db2b31a8a.png)
確認画面に遷移するので「申し込む」ボタンをクリック
![image](https://qiita-image-store.s3.amazonaws.com/0/38174/d6f47f42-7839-01fd-247b-91b7c5a73b0d.png)
以下の画面に遷移するので、「LINE@MANAGERへ」のボタンをクリック
![image](https://qiita-image-store.s3.amazonaws.com/0/38174/35d26a9c-91e2-3617-543a-eb4a30e9d9c1.png)
Bot設定の画面に遷移するので「APIを利用する」ボタンをクリック
![image](https://qiita-image-store.s3.amazonaws.com/0/38174/f361be84-e6be-f20a-b4f2-5e10d7c04eac.png)

再度注意事項が確認されるので「確認」をクリック
![image](https://qiita-image-store.s3.amazonaws.com/0/38174/5341bab4-3c10-f25b-7df6-eeb35af03e6c.png)

「成功しました」のメッセージがでるので「OK」ボタンをクリック
![image](https://qiita-image-store.s3.amazonaws.com/0/38174/79e8cd87-a4de-70dc-ec40-d9c0c9469db1.png)

Bot設定画面で、以下のように設定

- Webhook送信 「利用する」
- 自動応答メッセージ 「利用しない」(任意)
- 友だち追加時あいさつ 「利用しない」(任意)

![image](https://qiita-image-store.s3.amazonaws.com/0/38174/2b53054e-94b0-fc8d-f617-4ea740ac6d35.png)
「成功しました」のメッセージがでるので「OK」ボタンをクリック
![image](https://qiita-image-store.s3.amazonaws.com/0/38174/b4cb74f2-0b3b-4220-1f21-4e847b54f57f.png)
[LINE BUSINESS CENTERのアカウントリスト](https://business.line.me/ja/companies/1235502/accounts?ownerType=company&roleType=operator)に戻って先ほど作成したアカウントの「Messaging API」の見出しの右にある「LINE Developers」ボタンをクリック
![image](https://qiita-image-store.s3.amazonaws.com/0/38174/187c0b29-ecfd-3282-5419-41047c81b749.png)
アカウントの設定画面で以下を実施

- Webhook URLにHerokuにデプロイしたアプリケーションのURLを設定
  - 今回のsampleでは、HerokuのアプリケーションURLが`https://salty-badlands-47970.herokuapp.com/`だったので、`https://salty-badlands-47970.herokuapp.com/callback`と設定する
- Channel Secretを「SHOW」ボタンを押してメモる
- Channel Access Tokenを「ISSUE」ボタンを押して発行し、メモる

![image](https://qiita-image-store.s3.amazonaws.com/0/38174/925caf68-cc7c-bd05-05a0-fcfdc226ab83.png)
![image](https://qiita-image-store.s3.amazonaws.com/0/38174/ee491818-a3c8-a2bc-a934-c9fbbd4d89de.png)

## Herokuの設定にシークレットとトークンを設定

LINE管理画面で取得したシークレットとトークンをherokuのCLIを利用して環境変数に設定します。
[公式SDK](https://github.com/line/line-bot-sdk-java/tree/master/sample-spring-boot-echo#step-3)の説明のとおりHerokuの管理画面でも出来ます。

以下を実行

```
❯ heroku config:set LINE_BOT_CHANNEL_TOKEN={メモったChannel Access Token} LINE_BOT_CHANNEL_SECRET={メモったChannel Secret}
Setting LINE_BOT_CHANNEL_TOKEN, LINE_BOT_CHANNEL_SECRET and restarting ⬢ salty-badlands-47970... done, v9
LINE_BOT_CHANNEL_SECRET: {メモったChannel Secret}
LINE_BOT_CHANNEL_TOKEN: {メモったChannel Access Token}
```

正しく設定されたか不安な場合は`heroku config`コマンドで確認

上記の`heroku config:set`コマンドで、Herokuのアプリケーションは自動的に再起動しているはずなので、`heroku logs`コマンドで起動状態を確認

```
・・・中略・・・
2016-11-23T12:45:11.027942+00:00 app[web.1]: 2016-11-23 12:45:11.027  INFO 4 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-11-23T12:45:11.039393+00:00 app[web.1]: 2016-11-23 12:45:11.039  INFO 4 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 0
2016-11-23T12:45:11.165588+00:00 app[web.1]: 2016-11-23 12:45:11.163  INFO 4 --- [           main] c.l.b.s.b.s.LineMessageHandlerSupport    : Registered LINE Messaging API event handler: count = 0
2016-11-23T12:45:11.211816+00:00 app[web.1]: 2016-11-23 12:45:11.211  INFO 4 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 31911 (http)
2016-11-23T12:45:11.218260+00:00 app[web.1]: 2016-11-23 12:45:11.218  INFO 4 --- [           main] com.example.DemoApplication              : Started DemoApplication in 8.773 seconds (JVM running for 9.902)
2016-11-23T12:45:11.605169+00:00 heroku[web.1]: State changed from starting to up
```

起動していました。

起動ログの中にこれがあると完璧

```
2016-11-23T12:45:09.878281+00:00 app[web.1]: 2016-11-23 12:45:09.878  INFO 4 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/callback],methods=[POST]}" onto public void com.linecorp.bot.spring.boot.support.LineMessageHandlerSupport.callback(java.util.List<com.linecorp.bot.model.event.Event>)
```

## 動作確認

自分のLINEアカウントで追加したBOTアカウントを友だちにして(ID検索などを利用)、なにか発言してみてください。

![image](https://qiita-image-store.s3.amazonaws.com/0/38174/91d33685-cd39-eb41-dc7e-7cd2cd6a75bd.png)

自分のuserIdがメッセージで返却されます。

sampleで作成したBOTですが、しばらく残しておくので単純にユーザーIDが知りたい人は何か話しかけてください。

<img src="http://qr-official.line.me/L/_LZCXsSfPh.png">
<a href="https://line.me/R/ti/p/%40zlk4352h"><img height="36" border="0" alt="友だち追加" src="https://scdn.line-apps.com/n/line_add_friends/btn/ja.png"></a>