EatSmartシステム部ブログ

ウェブサイトの開発や運営に関する情報です。

Reactで実装した機能をリリースしました

先日、もぐナビのスマートフォンサイトで、Reactで実装した機能をリリースしました。いくつかの機能をReactで実装したのですが、この記事ではクチコミ投稿機能に関して使った技術や参考にしたページを振り返ってみたいと思います。

f:id:eatsmart:20190802163748p:plain
クチコミ

React

まずは、言わずと知れたReact自身です。

React – ユーザインターフェース構築のための JavaScript ライブラリ

ただ、Reactに本格的に触れるのは今回が初めてだったので、プロトタイプをcreate-react-appで始めました。

Facebook公式のcreate-react-appコマンドを使ってReact.jsアプリを爆速で作成する - Qiita

また、途中から、どうせES6で書くなら、せっかくならTypeScriptで書こうと思い直し、create-react-appでTypeScriptのプロジェクトを生成しました。

React + TypeScriptプロジェクトをcreate-react-appに任せる - Qiita

Redux

実装した機能は、クチコミ用のダイアログをオーバーレイで表示し、ダイアログ内でページ遷移をしながら内容を埋めていくというものです。 複数のページ(React的にはコンポーネント)にまたがってオブジェクト(データ)を共有するには、どうやらReduxというものを使うらしいということを知りました。

Getting Started with Redux · Redux

そもそもReactのstateも知らなかったので理解が大変でしたが、以下のページなどを参考にして作ってみました。

Redux入門【ダイジェスト版】10分で理解するReduxの基礎 - Qiita
たぶんこれが一番分かりやすいと思います React + Redux のフロー図解 - Qiita
Reduxの実装とReactとの連携を超シンプルなサンプルを使って解説 | maesblog

実際に使えるようになるまでは、そこそこ苦労しましたが…。

eatsmart.hatenablog.com

react-modal

クチコミダイアログのオーバーレイ表示は、react-modalを使いました。

GitHub - reactjs/react-modal: Accessible modal dialog component for React

使い方はさほど難しくはありませんでした。styleは外部からCSSで指定しました。

react-modalを使ってモーダルを表示する - わいの技術メモ

redux-thunk

今回はサーバーとの通信があったので、いくつか調べた結果、Action内で通信する選択肢としてredux-thunkを使用することにしました。

reduxで非同期処理をするいくつかの方法(redux-thunk、redux-saga) - Qiita

ReduxのAdvancedな使い方 ~ 公式ドキュメント Advanced~ - Qiita

redux-thunk自体はシンプルなので、サンプルを見てすぐに使えるようになりました。
ただ、この時点ではES6のアロー関数にもあまり慣れていなかったので、以下のページを読むことで、色々な理解が深まりました。

redux-thunkを学ぶ - 豆腐とコンソメ

react-router-dom

ダイアログ内でのページの切り替えやリンク移動については、react-router-domを使用しました。

react-router@v4を使ってみよう:シンプルなtutorial - Qiita
ささっと学ぶReact Router v4 – the2g

実際に使ったのはv5です。

React Router 5の変更点 – the2g

react-router自体は、上記ページを参考にして問題なく使うことができました。

ただ、Actionに伴いページ遷移をさせるときに使ったconnected-react-routerを理解するのに少し苦労しました。

こちらの記事に書いてあるように、

React Router v4とReduxを繋げるにはconnected-react-routerを使うのがおすすめだよ! - Qiita

  • historyオブジェクトを作る
    今回はブラウザのURLを変えたくなかったので、createMemoryHistoryを使いました。
  • connectRouterでrouterReducerを取得する
    今回は他のreducerとcombinedReducerしました。
// reducer作成
const reducer = combineReducers({
  product: productReducer,
  picture: pictureReducer,
  comment: commentReducer,
   …
  form: reduxFormReducer,
  router: connectRouter(history),
});
  • Providerの子要素にConnectedRouterを置き、historyオブジェクトを渡す

とし、

  • Routeを使うコンポーネントはwithRouterでラップしてexportする
  • Actionでページ遷移するときは、pushをdispatchする
import { push } from 'connected-react-router';
   …
// リピする?の入力フォーム
export const setRepeat = (value: string) => (dispatch) => {
  // reducerでstoreを更新
  dispatch({
      type: SET_REPEAT,
      repeat: value
    });
  // いつ食べた?ページに遷移
  dispatch(push('/product/detail/when'));
};

という実装で実現できました。

下の記事でRouterの中の仕組みが少し分かって、理解に役立ちました。

React Routerのhistoryはどこから来るのか | TECHSCORE BLOG

ひとまず

大まかなメインの動作は、こんなところです。 UI上の工夫は他にも色々としているのですが、その辺りはまた別の記事にしたいと思います。

実際の動作については、ぜひ、もぐナビに会員登録してクチコミをしてみて下さい!

それにしてもQiitaって便利ですね。

コンビニ賞ページのOGPの設定について

今回は、もぐナビで7月23より公開しているコンビニ賞 ページの実装の際に、OGPの設定について行ったので内容をまとめていきます。

OGPについて

OGPは、SNS(facebooktwitterなど)へ特定のWEBページをシェアする際にそのページの内容を わかりやすく伝えるために用いられるHTMLの要素になります。 予め設定したタイトルやイメージを使って、端的にそのWEBページの要旨を表現できます。 適切な設定がされないと、シェアするWEBページとは意図しないタイトル、イメージ画像が シェアする際の情報として表示されてしまいます。 正しく設定することで、クリック率が向上し、より多くの人に閲覧してもらえるというメリットが あります。

実際に使用したogタグの設定コード

<!-- OGPの利用をするための宣言をする。 -->
<html lang="ja" prefix="og: http://ogp.me/ns#">

<head>
<meta property="og:title" content="もぐナビ コンビニ賞" />
<!-- TOPページ以外の場合は、articleを指定。 -->
<meta property="og:type" content="article" />
<!-- サムネイルの画像に該当する画像パスを指定。 -->
<meta property="og:image" content="https://mognavi.jp/campaign/conveni_award2019/image/og.png" />
<!-- 遷移先のurlを指定。 -->
<meta property="og:url" content="https://mognavi.jp/do/campaign/convenience_store_food_awards201907" />
<!-- サイト名を指定 -->
<meta property="og:site_name" content="国内最大級の食品クチコミサイト『もぐナビ』" />
<!-- ページの説明文(概要)に該当する内容を記述 -->
<meta property="og:description" content="食品クチコミサイト「もぐナビ」に寄せられた1年間のクチコミを元に、コンビニ商品のランキングを作ってみました♪
レビュアーさんに支持された各ジャンルの第1位と、各コンビニさんのコンビニスイーツ・第1位を堂々発表です!" />
<!-- facebookにサイトの管理者情報を伝えるためのidを指定-->
<meta property="fb:app_id" content="179050255554650" />
<!-- twitterカードの種類を指定する。ここでは大きいイメージ画像付きの表示になるように設定-->
<meta name="twitter:card" content="summary_large_image" ※後述します>
<!-- WEBサイトのtwitterIdを指定 -->
<meta name="twitter:site" content="@mognavi">
</head>

上記のうち、title,type,image,url,は設定に必須で、facebooktwitterに共通の項目も あります。

実際のコンビニ賞urlをシェア時の表示例

facebookの場合

f:id:eatsmart:20190725185122p:plain

twitterの場合

f:id:eatsmart:20190726095032p:plain

設定したタイトル、イメージ画像、概要を確認することができます。

twitterカードについて

今回は、上記タグで"summary_large_image"を指定しています。4種類あるうちのカード形式の一つで どういった形式のカードでシェアするのかを表します。 ①Summary Card,②Summary Card with Large Image,③App Card,④Player Cardの4つ種類があり、 シェアするurlの目的によって使い分けます。 ①、②はWEBサイト用、③がアプリ配布用、④が動画サイト用になります。 試しにもぐナビのTOPページだと、以下のように表示になります。 f:id:eatsmart:20190726095106p:plain ページのソースを確認すると、と①のタイプであることが わかります。コンビニ賞より、画像が小さく文字情報が多くなっています。

OGPの設定確認方法について

シェアされる時に適切に設定した表示がされるかは、Developer向けのツールを使って確認することが できます。
facebook debuger(facebookのOGP確認用)
Card validator(twitterのOGP確認用)
上記で用いたtwitterの表示確認にもこちらを使用しており、アカウントさえあればどちらのツールも利用が可能です。

以上になりますが、少しの設定で見せ方が変わり、見せ方が変わるとエンゲージメントにも影響するので 実装の中で後回しになりがちですが忘れずに意識していくことだと感じました。

DNSサーバーの移転ついて

今回は、現状運用してるDNSサーバーを別のサーバーに移転することになり、そのノウハウを記事にしてみました。

DNSサーバーの仕組み

DNSサーバーを移転するに辺り、DNSサーバーの仕組みについて簡単に触れておきたいとおもいます。
DNSサーバーには2つの種類が存在します。

権威DNSサーバー

権威DNSサーバーとは、自身が管理するゾーン情報(ドメイン名とIPアドレスの紐づけ等)を保持し、問い合わせに対して自身が管理している情報のみを答えます。

ゾーン情報

test.co.jp   A   111.22.332.44 //test.co.jpのIPアドレスは「111.22.332.44」
test.co.jp  MX  mail.test.com //test.co.jpのメールサーバーは「mail.test.com」
test.co.jp  NS  old_ns1.test.com //test.co.jpのプライマリDNSサーバーは「old_ns1.test.com」
test.co.jp  NS  old_ns2.test.com //test.co.jpのセカンダリDNSサーバーは「old_ns2.test.com」
    ・
    ・
    ・

DNSキャッシュサーバー

クライアントからあるドメイン名の名前解決のリクエストを受け、該当ドメイン名を管理する権威DNSサーバへの問い合わせを行い結果を返却する。
また、問い合わせ結果は一定期間(TTL値で)保存され、期間内に同じ問い合わせが来た時には保存した内容を返却する。
一定期間が経過すると新に権威DNSサーバへの問い合わせを行う。

TTL

TTL(Time To Live)とは、一旦DNS経由でドメイン名の名前を解決(ドメイン名とIPアドレスの紐づけ)した場合、その情報をキャッシュしておく時間(数値は秒)である。

DNSサーバーの移転で注意すべき点

移転の際に注意すべき事項として下記2点があります。

  1. DNSキャッシュサーバー群からの名前解決要求を、いかにして移転先の権威DNSサーバーに向けさせるか
  2. DNSキャッシュサーバー群に、いかにして新しいDNSデータ(ゾーン情報)を提供するか

DNSサーバーの移転方法について

上記注意すべき点を考慮したDNSサーバーの移転方法は下記のようになります。

1. 移転先の権威DNSサーバーの構築

移転先の権威DNSサーバーに新しいゾーン情報及び新しいNSレコードを設定する。 その他変更が無いゾーン情報はそのまま設定する

2. 移転元ゾーン情報の切り替え

移転元の権威DNSサーバーのゾーン情報を、新しいゾーン情報(移転先データ)に切り替える
※移転元のTTLが、短時間で切り替わる様に短くしておく
※NSレコードは移転先の情報を設定する

3. 親(レジストラ)に登録した委任情報の切り替え

親に登録している委任情報(NSレコード情報等)の変更を申請し、移転先の権威DNSサーバーに切り替える

4. TTLが切れるまで移転元と移転先の権威DNSサーバーを並行運用する

すべてのDNSキャッシュサーバー群が移転先の権威DNSサーバーのみを参照するようになるまで、DNSサーバーを並行運用します。

5.動作確認

TTLが切れたら、DNSサーバーが切り替わった事を確認する方法を記載します
Linux環境
dig NS ドメイン

[test@ ~]# dig ns test.co.jp

;; QUESTION SECTION:
;test.co.jp.                        IN      NS

;; ANSWER SECTION:
test.co.jp.         300     IN      NS      new_ns1.test.com. //NSレコードが移転先に切り替わった事を確認
test.co.jp.         300     IN      NS      new_ns2.test.com. //NSレコードが移転先に切り替わった事を確認

Windows環境
nslookup
set type=ns
ドメイン
コマンドプロンプトから確認

C:\nslookup
> set type=ns
> test.co.jp
サーバー:  xxxx.xxx.xx
Address:  111.22.33.444

権限のない回答:
test.co.jp  nameserver = new_ns1.test.com
test.co.jp  nameserver = new_ns2.test.com
※「権限のない回答とは」DNSキャッシュを使って回答している事を示している
6. 移転元の権威DNSサーバーの停止

DNSサーバーが切り替わった事を確認できたら移転元権威DNSサーバーを停止する

まとめ

DNSサーバーは普段はあまり接する機会が無いので、忘れてしまっている部分がありましたが、今回の移転で振り返る事が出来て良い機会になりました。
DNSサーバーの基本的な仕組みは是非押さえておくと良いと思います。

Background Removal API を試してみる

画像の背景を切り抜くサービスを、Javaから利用してみました。

www.remove.bg

準備

まずは上記サイトで登録とAPIキーの取得を行います。 各言語ごとにサンプルが用意されているので簡単に利用出来ます。

今回はJavaから利用するので、httpclientが必要になりました。 予めダウンロードしてクラスパスに通して置きます。

実装

このAPIでは画像が透過PNGとして出力されるようです。 イートスマートではJPEG形式の画像を利用しているので、最終的にPNGからJPEGへ変換する必要があります。 背景色が白の画像に透過PNGを重ね合わせ、JPEG形式で出力するコードを書いてみました。

public class RemoveBackground {

    final String API_KEY = "[API_KEY]";

    void removeBackground(File from, File to) throws IOException {
        Response response = Request.Post("https://api.remove.bg/v1.0/removebg")
                .addHeader("X-Api-Key", API_KEY)
                .body(
                        MultipartEntityBuilder.create()
                        .addBinaryBody("image_file", from)
                        .addTextBody("size", "auto")
                        .build()
                        ).execute();
        response.saveContent(to);
    }

    void saveAsJpeg(File png, File jpeg) throws FileNotFoundException, IOException {
        BufferedImage source = ImageIO.read(png);
        int width = source.getWidth();
        int height = source.getHeight();

        BufferedImage dest = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
        Graphics2D g2d = dest.createGraphics();
        try {
            g2d.setBackground(Color.WHITE);
            g2d.clearRect(0, 0, width, height);[f:id:eatsmart:20190703203931j:plain]
            g2d.drawImage(source, 0, 0, width, height, 0, 0, width, height, null);
        } finally {
            g2d.dispose();
        }

        ImageIO.write(dest, "JPEG", jpeg);
    }

}

検証

もぐナビニュースで実際に使われた写真をサンプルに、背景画像を切り抜いてみます。

まずは以下の写真でテストを行いました。 お皿に乗ったお菓子がどう扱われるかが気になります。

f:id:eatsmart:20190703203905j:plain

以下のようになりました。

f:id:eatsmart:20190703203931j:plain

綺麗にテーブルクロスだけが切り抜かれお皿が残りました。

続いては、同じくお皿に乗ったパンケーキです。 先程の写真ではお皿が残りましたが、この写真ではホイップクリームがお皿からはみ出しています。 このあたりの影響はあるのでしょうか?

f:id:eatsmart:20190703204237j:plain

以下のようになりました。

f:id:eatsmart:20190703204252j:plain

こちらも先程の写真と同じく、お皿が残りました。 お皿からはみ出したホイップクリームが残っているので、背景と区別がされているようです。 写真の中央にお皿がある場合、背景の判別が行いやすいのかもしれません。

最後に、これまでとは異なる写真を試してみます。 テーブルの上のお皿ではなく、カップに入った状態です。 また、テーブルとスクリーンの2つの異なる背景があります。

f:id:eatsmart:20190703204607j:plain

以下のようになりました。

f:id:eatsmart:20190703204627j:plain

これまでの写真と同様背景は切り抜かれましたが、手前のクッキーが残ってしまいました。 また、カップの左下にも何かしら残っています。 これまでと異なり商品と背景の差があいまいで難易度は高いと思いますが、わずかな手直しで済みそうです。

まとめ

簡単な検証ですが、綺麗に背景が切り抜かれることがわかりました。 プログラムから利用することが想定されており、APIキーさえ取得すれば簡単に利用することが出来ました。 このAPIを利用して画像を事前に処理してけば、人は確認と手直しに専念することができそうです。

Googleのコアアルゴリズムアップデートの影響と対応について

弊社サービスの「もぐナビ」は、Googleの検索結果からサイト訪問する割合が多く、常にGoogleの検索結果の表示順位に気を配っています。 Googleは3月初めと6月初めに検索に関してのアルゴリズムを更新し、検索結果の表示順位に影響を受けました。

【March 2019 Core Update】2019年3月12日、コアアルゴリズムのアップデートをGoogleが開始 | 海外SEO情報ブログ

【June 2019 Core Update】2019年6月、広範囲にわたるコアアルゴリズムのアップデートをGoogleが開始 | 海外SEO情報ブログ

このアルゴリズムアップデートについての影響や変動の調査に際して、気付いたことがあったので記事にしたいと思います。

影響の調査について

Googleの検索結果の表示回数や順位、クリック数などは、Google SearchConsoleの[検索パフォーマンス]-[検索結果]を使って分析します。

f:id:eatsmart:20190628141857p:plain
サーチコンソール

SearchConsoleは少し前にリニューアルされ、使い方が少し変わりました。日付比較などは以前の方が使いやすいところもあったのですが、今後改善されていくでしょう。

また、SearchConsoleはGoogle Analyticsと連携して、Analyticsを使って分析することもできます。

f:id:eatsmart:20190628142509p:plain
Analytics

ただ、詳細にドリルダウンして分析するなら、SearchConsoleの方が使いやすいと思います。

気付いたこと

平均掲載順位は大きく変わらないのに、表示回数が激減

アルゴリズムアップデートの前後で、どんな検索キーワードについて表示回数やクリック数が減ったのかを調べていたのですが、平均掲載順位は大きくは変わらないのに、表示回数やクリック数が大きく減っているキーワードを見つけました。

f:id:eatsmart:20190628143206p:plain
表示回数激減

キーワードに対しての掲載順位や表示回数を詳しくて見ていくと、アルゴリズムアップデートから数日後に掲載順位が大きく低下し、検索結果に表示されないために平均掲載順位は下がらず表示回数が激減しているようでした。
(グラフを見ると、たまに以前の順位で表示されているようです。)

はじめは、対象のキーワードの検索ニーズが急に無くなった(はやりのキーワードだった?)のかと思ったりしたのですが、こういった形になることはSearchConsoleのヘルプにも書いてありました。

表示回数、掲載順位、クリック数とは - Search Console ヘルプ

掲載順位が記録されるためには、そのリンクが実際に表示されなければなりません。ある検索結果が表示されなかった(たとえば検索結果は 3 ページあったのにユーザーが 1 ページ目しか見なかった)場合、その検索結果の掲載順位はそのクエリには記録されません。

表示回数・掲載順位に対して、クリック数が少ない

次に、掲載順位は低くなく表示回数も少なくないにも関わらず、クリック数が少ないURLを見つけました。

掲載順位ごとのクリック率の統計は、以下のようなものが公開されています。

【2017年夏】グーグル検索結果のクリック率データ: 1位は21%、2位は……【SEO記事11本まとめ】 | 海外&国内SEO情報ウォッチ | Web担当者Forum

これに対し、掲載順位が4〜5位なのに、クリック率が0.0%台でした。

f:id:eatsmart:20190628144846p:plain
CTR低い

これもSearchConsoleのヘルプに書いてあったのですが、実はクリック率が低いURLは、検索結果SERP内のサイトリンクでした。
例えば、「菓子パン」でGoogle検索をすると、検索結果のもぐナビのSERPは以下のように表示されます。

f:id:eatsmart:20190628145622p:plain
サイトリンク

ここでサイトリンクとして表示されたURLは、検索キーワードに対し表示回数と掲載順位が加算されますが、クリックはなかなかされないので、クリック数が少なくなっているのでした。

今後の対応について

アルゴリズムアップデートの影響についていろいろ分析したところ、もぐナビニュースで以前は人気記事だったけど情報が古くなっているページの検索順位が落ちて流入が減っていたようでした。今後は、そういった古くなりがちな情報に対して、最新の内容を保てるようにしていきたいと考えています。

もぐナビのクチコミ一覧からもぐナビニュースへ

イートスマートの新人エンジニアが、「もぐナビ」での開発を振り返り、学んだ内容をまとめていきます。 今回は、キャッシュとシーケンシャルスキャンについて考える機会となりました。

今回の実装の内容

弊社サイト「もぐナビ」でトラフィックの大きいユーザーさんのクチコミ一覧から、もぐナビのニュース枠を設置し、そこからもぐナビニュースへ誘導しようという経緯で実装しました。
※もぐナビニュースについて(コンビニ商品を中心に新作や話題性のあるものを紹介しています。) f:id:eatsmart:20190621125606p:plain

実装例(関連するコードのみ抜粋)

CacheTopBean.java---キャッシュの中身に関するクラス

public class CacheTopBean implements CacheInterface {
   private NewsItemDto newsItem = new NewsItemDto();

   public NewsItemDto getNewsItem() {
        return NewsItem;
    }
  public void setNewsItem(NewsItemDto newsItem) {
        this.newsItem = newsItem;
    }
 ※NewsItemDtoには、表示に必要なニュースタイトル、遷移先url、イメージ画像、掲載日などをフィールドにもっています。
}

TopManager.java---キャッシュ作成に関わるクラス

public class TopManager implements ReloadableCacheManager {

    private static TopManager instance = new TopManager();

    private TopManager() {
    }
        public static TopManager getInstance() {
        return instance;
    }
public CacheTopBean createCacheTopBean(Calendar date) throws ServerException  {
    CacheTopBean ctb = new CacheTopBean();
 
  NewsItemDto newsItem = new NewsItemDto();
  //DBから該当のデータを抽出し、newItemに代入します。
    newsItem = jp.mognavi.news.NewsItemDao.getInstance().getNewsItemDto();
    //生成したキャッシュに目的のニュース情報のインスタンスをセットします。
    ctb.setNewsItem(newsItem);

    return ctb;
        }
}

NewsItemDao.java---ニュースのDB操作に関するクラス

public  NewsItemDto getNewsItemDto() {
        return  getSingleResult(
                "SELECT v.guid, v.title as title ,v.contents, v.category, v.pub_date, t.title as ptitle, v.image, v.update_date "
                        + "FROM v_news_item v LEFT JOIN t_news_item_pickup t ON v.guid = t.guid "
                        + "WHERE v.del_kbn = ? " + "AND v.pub_date > now() - interval '2 days' "
                        + EXCLUDE_PR_NEWS(PR商品を除外するsql)
                        + INCLUDE_NEWS_CATEGORY(特定のカテゴリに絞るsql)
                        + "ORDER BY random() LIMIT ?",
                NewsItemDto.class,
                KbnConstant.DEL_KBN_VALID, 1);
    }
※詳細を省きますが、ざっくりと公開から48時間以内で、特定のカテゴリに絞りランダムに1件取得しています。

ListAction.Java---クチコミのリスト表示に関するクラス

public class BlogItemDao extends DaoBase {
public class ListAction extends GenericBaseAction<...Bean> {
   
    CacheTopBean ctb = TopManager.getInstance().getCacheTopBean();
        //キャッシュにセットしたnewsItemをアクションの方でセットします。
        request.setAttribute("newsItem", ctb.getNewsItem());
        return success();
  }
}
上記の"newsItem"をビュー側で利用します。

実装のポイントについて

最初は、データベースから取得してきた値をニュース表示のアクションにセットするという方法で実装していましたが、それだとページの読み込み速度に影響する可能性があったため、キャッシュに保存することでその問題を回避するようにしました。
次に、sqlの中で使用したrandom関数ですが、シーケンシャルスキャンが発生するため対象データが多い場合にはクエリの実行速度に影響します。今回のケースでは、公開から48時間という制約が大きく、母数がかなり限定的されるため特段問題にしませんでした。母数が大きい場合には、一旦キャッシュに100件保存し、キャッシュからランダムに1件抽出する方法などが考えられます。

シーケンシャルスキャン参考

以上になりますが、実装内容は基本的なものになりますが随所で学びポイントや工夫の機会がありました。それらを次のタスクに繋げていければと思います。

LINEのプッシュ通知について

前回の記事で「LINE公式アカウント」の統合について紹介しましたが、今回はプッシュ通知についてまとてみたいと思います。

eatsmart.hatenablog.com

プッシュ通知とは

プッシュ通知とは、 アプリを起動していない状態でもメッセージがユーザーのスマートフォンに通知される為、他のメッセージ送信手段に比べ読まれる確率が高いです。

プッシュ通知のメリット

メールでメッセージを送信すると、メールボックスに溜まって多くのメッセージの中に埋もれてしまったり、意図せぬフィルター制御で未読状態のままとなってしまう可能性がありますが、プッシュ通知の場合スマートフォンに通知が届けば、リアルタイムで ユーザーに気付いてもらえる可能性が高まり開封率の向上が期待できます。

LINE経由のプッシュ通知について

LINE経由のプッシュ通知を行うには、LINEが提供しているMessaging APIを利用して行います。また、プッシュ通知の送信タイプは以下のとおりです。

1人のユーザー

1人のユーザー、グループ、またはトークルームに、任意のタイミングでプッシュ通知を送信するAPIです。

複数のユーザー

複数のユーザーに、任意のタイミングでプッシュ通知を送信するAPIです。
※グループまたはトークルームにメッセージを送ることはできません。

公式アカウントと友だちになっているすべてのユーザー

LINE公式アカウントと友だちになっているすべてのユーザーに、任意のタイミングでプッシュ通知を送信します。

LINE経由のプッシュ通知を試してみる

今回は、curlコマンドでMessaging APIを呼び出して、1人のユーザーへのプッシュ通知を試してみました。 Line Developerサイトには、各種解説やサンプルが多数存在しており参考になりました。送信までの流れを記載します。

Lineプッシュ通知送信までの流れ

1)Line Developerで「LINEログイン」チャンネル開設

https://developers.line.biz/ja/docs/line-login/getting-started/

2)Line Developerで「Messaging API」チャンネル開設

この時、Line Offcial Account Manager側とLINEアカウント連携しておくと、Line Offcial Account Manager側にアカウントが自動で作成されます。

https://developers.line.biz/ja/docs/messaging-api/getting-started/

3)LINE公式アカウントをLINEログインのチャネルにリンクする

これを行っておくと、ログイン連携でアプリの利用同意を行った後に、公式アカウントを友達追加するオプションが表示されます。
表示方法は2種類存在していて、アプリの利用同意画面に組み込む形式と、アプリの同意画面表示後に友達追加を別画面で表示する方法が存在します。
また、APIの呼び出し方法は以下のLINEログインの認可APIbot_promptクエリパラメータを付加します。

https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id={CHANNEL_ID}&redirect_uri={CALLBACK_URL}&state={STATE}&bot_prompt={BOT_PROMPT}&scope={SCOPE_LIST}

また、bot_promptクエリパラメータに以下の値のいずれかを設定します。
normal:
アプリの利用条件の同意画面に、LINE公式アカウントを友だち追加するオプションを追加します。
aggressive:
アプリの利用条件の同意画面の後に、LINE公式アカウントを友だち追加するかどうか確認する画面を開きます。

https://developers.line.biz/ja/docs/line-login/web/link-a-bot/

4)Line Developerの「Messaging API」チャンネルでアクセストークンを発行する

プッシュ通知を呼び出す際に、チャンネルアクセストークンが必要な為、 「Messaging API」チャンネルの「チャンネル基本設定」タブで、アクセストークンを発行します。

5)curlでプッシュ通知を検証する
curl -v -X POST https://api.line.me/v2/bot/message/push \
-H 'Content-Type:application/json' \
-H 'Authorization: Bearer {チャンネルアクセストークン}' \
-d '{
    "to": "送信先ID(今回はuserId)",
    "messages":[
        {
            "type":"text",
            "text":"Hello, world1"
        },
        {
            "type":"text",
            "text":"Hello, world2"
        }
    ]
}'

https://developers.line.biz/ja/reference/messaging-api/#send-push-message

(補足)
最初、ログイン連携だけして、公式アカウントを友達追加していない状態でプッシュ通知を行っていましたが、その状態ではメッセージが届きまん。
そもそも公式アカウントを友達追加しておかないと、トークに公式アカウントが追加されない為、メッセージも届かくわけないですよね。

最後に

今回はプッシュ通知を試してみましたが、Messaging APIはプッシュ通知以外にもボットを活用した双方向の通信が行える様なのでその辺りの機能も試していきたと思います。