Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

AWS Lambdaを使ったStudyplus for SchoolのLINE連携

こんちにちは、ForSchool事業部の島田です。

今回はStudyplus for School(以下FS)のLINE連携について紹介させていただきます。

LINE連携とは?

LINEの「FS公式アカウント」と生徒の保護者が友だちになることで保護者と塾(講師)が連絡をとれたり、生徒(子供)の塾への入退室情報や勉強の状況を共有できる機能です。

LINE連携でできること

  • 保護者が
    • 塾とメッセージのやりとりが出来る
    • 生徒の塾の入室・退室のお知らせを受信できる
  • 塾が保護者へ
    • 指導報告や面談報告を送信できる
    • 生徒の学習記録を送信できる

LINE連携のフロー概要

LINEとのメッセージの送受信には、Messaging APIを使っています。

developers.line.biz

LINE連携でのメッセージをやりとりするフローの概要は以下のようになります。

  1. LINEと連携するためのステップ(最初の1度のみ)
  2. 保護者へのメッセージの送受信
  3. 保護者からのメッセージの送信

f:id:yo-shimada:20200220151245p:plain

システム構成について

FSとLINEとのやりとりをする実装には AWS Lambda + API Gateway を利用しています。 外部に公開するURLはできるだけFSシステムと疎結合にしておき、LINEとのやりとりはLambdaに責任を持たせて、FSのシステムではなるべく関知しないようにしたいという意図があります。

f:id:yo-shimada:20200210195854p:plain

Lambda関数について

Lambdaに2つの関数を実装しました。説明の便宜上/webhook/messages と記載します。

  • /webhook:LINEからのwebhookを受けるエンドポイント。LINE上でFS公式アカウントに対してイベントが起きたときにこのエンドポイントでPOSTリクエストを受けます。
  • /messages:FSからメッセージを送信する際に受けるエンドポイント。

関数の実装について

Lambdaは、FSがRuby on Railsを利用している観点から「Ruby 2.5」を利用しています。
LINE APIとのやりとりにはLINE Messaging API SDK for Rubyを利用しています。

github.com

以下は実装のイメージを掴んでいただくための概略したコードです。
それぞれpost_handlerがLambdaのハンドラー関数になります。

webhook.rb

require 'json'
require 'line/bot'
require 'net/https'
require 'uri'

# LINEにメッセージがあった場合にwebhookから呼び出される関数
def post_handler(event:, context:)
  signature = event["headers"].fetch("X-Line-Signature")
  body = event["body"]

  raise "LINEの署名が不正です" unless client.validate_signature(body, signature)

  events = client.parse_events_from(body)
  events.each do |event|
    case event
    when Line::Bot::Event::Message
      case event.type
      when Line::Bot::Event::MessageType::Text
        line_user_id = event["source"]["userId"]
        # 初めて連携をする場合にFSから発行した連携コードをメッセージしてもらう
        if line_code?(event.message["text"])
          line_code = event.message["text"]
          # FS側に連携コードとLINE UserIDを渡して、FSの生徒と保護者のLINEを紐づける
          connect(line_code, line_user_id, reply_token: event["replyToken"])
        else
          # LINEメッセージの受信。保護者から来たメッセージをFSに渡し塾が確認できるようにする
          message = event.message["text"]
          send_fs_message(message, line_user_id)
        end
    when Line::Bot::Event::Unfollow
      # FS公式アカウントをブロックした場合
      disconnect(event["source"]["userId"])
    when Line::Bot::Event::Postback
      # FlexMessageを利用して場合に利用
      ...
    end
  end

  { statusCode: 200, body: JSON.generate('ok') }
rescue => e
  puts e, e.backtrace
  { statusCode: 400, body: JSON.generate("Bad Request") }
end

def client
  @client ||= Line::Bot::Client.new do |config|
    config.channel_secret = LINE_CHANNEL_SECRET
    config.channel_token = LINE_CHANNEL_TOKEN
  end
end

def connect(line_code, line_user_id, reply_token:)
  res = post(URI.join(FS_URL, CONNECT_PATH), {
    code: line_code,
    line_user_id: line_user_id
  })
  case res.code
  when "200"
    send_line_message("#{student_name}さん」と連携しました!", reply_token: reply_token)
  when "404"
  ...
  end
end

# FSにメッセージを送信
def send_fs_message(message, line_user_id)
  post(URI.join(FS_URL, MESSAGE_PATH), {
    line_user_id: line_user_id,
    message: message,
  })
end

# LINEにメッセージを送信
def send_line_message(message, reply_token:)
  client.reply_message(reply_token, {
    type: "text",
    text: message
  })
end

messages.rb

require 'json'
require 'line/bot'

# FSからのメッセージ受けてをLINEへ送信する関数
def post_handler(event:, context:)
  body = JSON.parse(event["body"])

  # メッセージとLINE UserIDを取得
  message = body["message"]
  line_user_id = body["line_user_id"]

  # LINEにメッセージを送信
  result = client.push_message(line_user_id, message)
  if result.all? { |res| res.code.start_with?('20') }
    { statusCode: 200, body: JSON.generate('success') }
  else
    response_body = result.map { |res| { code: res.code, body: res.body } } 
    { statusCode: 400, body: JSON.generate(response_body) }
  end
end

def client
  # webhook.rb と同じ 
end

最後に

スタディプラスでは、前例がなくても要件を実現するためには新しいサービスやツールを積極的に取り入れています。
ForSchool事業部でLambdaを採用することは今回が初めてでしたが、大きな問題なく開発・運用ができています。
今後も外部との連携やシステムを疎結合にしていく際に、この事例を参考に必要なサービスを利用していこうと考えています。

LINEでのやりとりのキャプチャー f:id:yo-shimada:20200219144120j:plain