nanisore oishisou

Webエンジニアあるまさんのゆるふわ奮闘記。

LINE APIの権限まわりについて

どうもベル子です。
社内で知識をチケットにまとめるという
プロジェクトが始まりました。

ただ、やっぱり私は、エンジニアたるもの社内だけで知識を共有するってのは違うよなと思うタイプなんで、そっちでまとめたものも社会貢献&自分の財産としてブログに転載していくつもりです!
オープンソースマインド★

そもそも知識は人から与えられるものよりも
自分で取りに行ったもののほうが身につくということもあるので
あまり超人的な先輩がたくさんまとめているものを最初からあてにするのはよくないんではないかという気もします。

お手本がなくても、いじくって自分のコードを仕上げるという経験から学ぶことって多いじゃないですか。

だから私自身は、ある程度の基礎以外は聞かれたら教えるというスタイルなんですよね。

まあ、でも先人の知恵をさくっと習得したほうが、合理的で時間の節約もできますよね。

このバランス感がプログラマとして大事なのではないかと思ってます。

合理性や論理性、そして情熱や衝動のバランス。

それがプログラマになるには必要な才能のような気がしているんですよね。センスというか。
そして最後には、情熱や衝動に敵うものはなくて、才能やセンスも情熱を傾けた努力には敵わない。そんな気がします。

久しぶりにエモいw

というわけで本題はいります!


LINE APIには大きく分けて2つの権限レイアーがあります。

  • プロバイダ
  • チャネル

このプロバイダへ参加するメンバーを登録することができ、
参加メンバーはそのプロバイダに所属しているチャネルの一覧を見ることができます。

ただし、チャネルはチャネルへの参加者権限を付与しなければチャネル詳細を閲覧したり編集したりすることができないようになっています。

プロバイダへ参加していないメンバーでも、
開発者アカウントのメールアドレスを利用しチャネルに個別に追加することができます。 この場合、個別に追加されたメンバーはプロバイダに所属している他のチャネル一覧を閲覧することはできません。

クライアントに引き渡す際には、
このようにして個別のチャネルにクライアントの開発者アカウントを追加してください。

プロバイダ

企業や組織名を登録し、プロバイダの配下にLINEログインやMessaging APIのチャネルを登録していきます。
例)株式会社ほげ

チャネル

LINEログイン、またはMessaging APIのことをチャネルと呼びます。
Messaging APIチャネルは、作成すると1つにつき1つのLINE@のアカウントが自動的に作成され
LINEアカウントでログインするLINE@マネジャーに管理中のアカウントとして自動的に追加されます。

LINEログインチャネルは作成しても紐づくLINEアカウントなどは作成されません。

すでに作成済みのLINE@アカウントをMessaging APIに紐付けることもできます。
その場合、「1:1トーク」と「LINE@アプリ」の利用ができなくなるため、すでに利用しているクライアントには確認が必要。
https://at.line.me/jp/plan

Messaging APIチャネルの権限管理とLINE@の権限管理は別ですが、
Messaging APIチャネルで権限を与えるとLINE@にも追加されるという仕組みのようです。

注意事項

プロバイダに所属している チャネルA に管理者が1人しかおらず、
その管理者をプロバイダからbanするとチャネルがゴースト化して削除できずに残ってしまう。
おそらくバグ、もしくは削除方法があるのかもしれないが現状そうなってしまうので
テストでいろいろ作成する際にはテストで作ったチャネルを全て削除してからアカbanするようにしてください。

開発者アカウントについて

開発者アカウントの開発者名、メールアドレスは自由に変更ができます。
開発者アカウントのメールアドレスは個人のLINEアカウントから作成されるため
通常はプライベートなメールアドレスに設定する場合が多いと思われますが
権限一覧では一部が伏せ字になっているので
クライアントに個人のメールアドレスが丸見えになることはありません。
開発者名はクライアントから丸見えなので、それらしいものを付けたほうがよければ編集してください。



検索用キーワード:
LINE API 権限管理 Messaging API LINE@ line Line LINEログイン ライン

勉強会のスライド

おはようございます!
勉強会で作ったスライドは全世界に公開することにしている私です。
今までの勉強会のスライドをブログにまとめて載せておきます。

Selenium勉強会

speakerdeck.com

Vuexで覚える状態管理

speakerdeck.com

今月は忙しいので、ブログが単なるまとめになりました。

ちなみに個人的なニュースとしては、ベリーダンスフェスティバルのステージに立つことが決まりました!
その練習も結構入ってくる予定で、さらにダラダラ年末に書いていた小説の連載を始めたので、そちらも完結させようと思ってるんで、しばらく私生活も忙しくなりそうです。

実はちょっと仕事が忙しいうえに、いろいろとあって精神的にやや参っているので、逆に何も考えられなくなるくらい忙しくしてみようと思って、好きなことをして追い込みをかけてみています。
良い子はマネしないようにね★

効果があるのかないのかは分かりませんが、ひとまずまだ雑誌の後ろに載ってる何でも願い事が叶うブレスレットは買わないで済んでいます。

と、ちょっと愚痴っぽくなりましたが、回復力がすごいタイプなので、おそらく来週くらいにはディズニーランドの店員ぐらい元気いっぱいになってると思う。

のうまく さんまんだ ばざらだん せんだ まかろしゃだ そわたや うんたらた かんまん

とりあえず、毎日マントラ唱えとくわ!

んだば!

PHPでメール着弾時、メール本文をパースしDBへ格納する(1)〜メールサーバ構築編〜

大まかな流れ

  • 特定のメールアドレス宛のメールを受信したらPHPをキックするようにpostfixで設定

大まかなやること

  1. 目的のドメインにMXレコードを追加する
  2. 本番サーバにpostfixをインストール
  3. postfixの設定をする
  4. /etc/aliasesに着弾用ユーザ名を追加し、phpをキックさせるようにする

本番サーバにpostfixをインストール&設定手順

postfixがインストールしてあるか確認

yum list installed | grep postfix
cd /etc/postfix
which postfix

インストールしてなかったらyumでインストール

sudo yum -y install postfix

インストールしてある人はmtaの確認

sudo alternatives --config mta

2 プログラムがあり 'mta' を提供します。

  選択       コマンド
-----------------------------------------------
*  1           /usr/sbin/sendmail.sendmail
 + 2           /usr/sbin/sendmail.postfix

Enter を押して現在の選択 [+] を保持するか、選択番号を入力します:

こんなふうにsendmail.postfixが選択されていたらEnterを押して終了する。 sendmail.sendmailだった人は、周りに確認してからsendmail.postfixに変更。

MXレコードの確認

nslookup -type=mx example.com                                                                                                                                                     
Server:     192.168.0.1
Address:    192.168.0.1#53

Non-authoritative answer:
example.com     mail exchanger = 10 mx1.example.com.

こんな感じでNon-authoritative answerのところに追加したMXレコードがでてくればOK。

DNSの更新が遅い場合があるので、そんな時はGoogle Public DNSを指定して問合せてみるとすでに反映されている可能性がある。

nslookup -type=mx example.com 8.8.8.8                                                                                                                                                   12:48:32
Server:     8.8.8.8
Address:    8.8.8.8#53

Non-authoritative answer:
example.com mail exchanger = 10 example.com.

Authoritative answers can be found from:

ひとまずメール送信できるかテスト

sendmail watashi_no_address@example.co.jp
From:test@example.com
To:chakudan@example.com

Subject:テスト送信

テスト送信してみました。てへぺろ
.

この段階では送れはするが送信元アドレスとかいろいろおかしい。

迷惑メールフォルダに入ってる可能性もあるので、そっちもチェックしたほうがいい。

メールログで送信できてるか確認

sudo cat /var/log/maillog

status=sentになってれば送れてる

Postfix設定

main.cfというのがPostfixの設定ファイルなので、それを編集して設定していく。

SMTP認証関係の設定も同時に行う。

今回は受信が必要なので、受信用の設定も同時に行う。

sudo cp main.cf main.cf.org
sudo vi main.cf

▼main.cf

myhostname = mx1.example.com
mydomain = example.com
myorigin = $mydomain
inet_interfaces = all
inet_protocols = ipv4
mynetworks = 192.168.0.0/24, 127.0.0.0/8
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain

home_mailbox = Maildir/

smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions =
    permit_mynetworks
    permit_sasl_authenticated
    reject_unauth_destination
smtpd_sasl_security_options = noanonymous

※inet_interfacesとmydestinationは外部からメールを受け付ける受信用の設定

master.cfの設定もしていく

sudo cp master.cf master.cf.org
sudo vi master.cf

master.cfの以下の行をコメントインする

▼master.cf

submission inet n       -       n       -       -       smtpd
  -o smtpd_sasl_auth_enable=yes
   -o smtpd_client_restrictions=$mua_client_restrictions

postfixを再起動する

sudo service postfix restart

Postfix自動起動設定

sudo systemctl enable postfix
sudo systemctl start postfix

SMTP認証に使われるsaslをインストールする

sudo yum install cyrus-sasl

自動起動設定

sudo systemctl start saslauthd
sudo systemctl enable saslauthd

saslの設定

sudo cp /etc/sasl2/smtpd.conf /etc/sasl2/smtpd.conf.org
sudo vi /etc/sasl2/smtpd.conf

▼smtpd.conf

pwcheck_method: auxprop
mech_list: plain login

ユーザーディレクトリの雛型を設定しメール用のディレクトリをデフォで作る

sudo mkdir -p /etc/skel/Maildir/{new,cur,tmp}
sudo chmod -R 700 /etc/skel/Maildir/

着弾用ユーザを追加

sudo useradd chakudan
sudo passwd chakudan

追加したユーザにSMTP認証用パスワードを設定

sudo saslpasswd2 -u mx1.example.com chakudan

saslのユーザーリストに登録されているか確認

sudo sasldblistusers2

パーミッションの変更

sudo chgrp postfix /etc/sasldb2
sudo chmod 640 /etc/sasldb2

postfixとsaslを再起動

sudo systemctl restart postfix
sudo systemctl restart saslauthd

外部からメールを受信させる設定

25番ポート開いてるか確認(外から)

↓こんな感じで返って来たら開いてる

telnet mx1.example.com 25
Trying xx.xx.xx.xx...
Connected to mx1.example.com.
Escape character is '^]'.
220 mx1.example.com ESMTP Postfix

↓こんな感じで返って来たら開いてない

Trying xx.xx.xx.xx...
telnet: connect to address xx.xx.xx.xx: Connection refused
telnet: Unable to connect to remote host: Connection refused

※ポートの確認をするのはドメインに対してではなく、MXレコードで追加したメールサーバのホストネームのほうで確認しないと意味ない。MXレコードのIPを記述ミスするとハマる。

25番ポートを開ける

まずはクラウド側でポート解放設定があったらそれを先にやる

それだけで解放できない場合はサーバ側からも解放する

まずは開いてるポートの確認

sudo firewall-cmd --list-ports --zone=public
xxx/tcp

25が開いてない人は開ける

sudo firewall-cmd --add-port=25/tcp --zone=public

このままだと再起動するとポートが閉じてしまうので再起動しても開いてくれる設定をする

sudo firewall-cmd --reload
sudo firewall-cmd --add-port=25/tcp --zone=public --permanent
sudo firewall-cmd --reload

25番ポート開いてるか再確認

telnet mx1.example.com 25

サーバローカルで認証のテスト

Base62でユーザ名とパスワードをエンコードしておく

perl -MMIME::Base64 -e 'print encode_base64("chakudan\0chakudan\0password");'

telnetで認証のテストをする

telnet localhost 25
EHLO localhost
AUTH PLAIN Y2hha3VkYW4AY2hha3VkYW4AcGFzc3dvcmQ=

235 2.7.0 Authentication successfulが表示されたら認証OK。

上記のパスワードは、chakudanユーザのOSログインパスワードではなくSMTP認証用パスワードで設定したパスワード。

転送設定

ひとまず自分のアドレスへ転送設定をしておく

sudo cp /etc/aliases /etc/aliases.org
sudo vi /etc/aliases

▼aliasesの一番下にこれを追加

chakudan: chakudan, watashi_no_address@example.co.jp

chakudanは着弾用ユーザ名
watashi_no_address@example.co.jpは自分が普段使ってるgmailのアドレスなんかにしておけばいいと思う。

aliasの設定を反映

sudo newaliases

テスト送信

普段使ってるgmailなどのメーラーからchakudan@example.comにテストメールを送信してみる。

サーバ側の/home/chakudan/Maildirにメールが届いていて、watashi_no_address@example.co.jpにも同じメールが転送されていれば、サーバ側の設定は完了。

ついでにPOP3サーバの設定もする

dovecotをインストールする

sudo yum install dovecot

dovecotの設定

sudo cp /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.org
sudo vi /etc/dovecot/dovecot.conf

dovecot.confの以下をコメントイン

protocols = imap pop3 lmtp
sudo cp /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf.org
sudo vi /etc/dovecot/conf.d/10-auth.conf

sudo cp /etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf.org
sudo vi /etc/dovecot/conf.d/10-mail.conf

sudo cp /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf.org
sudo vi /etc/dovecot/conf.d/10-ssl.conf

▼10-auth.conf

disable_plaintext_auth = no

▼10-mail.conf

mail_location = maildir:~/Maildir

▼10-ssl.conf

ssl = no

dovecotの起動設定

sudo systemctl start dovecot
sudo systemctl enable dovecot

pop用ポートを解放

クラウド側で110番ポートを解放

サーバ側ポート解放

sudo firewall-cmd --add-port=110/tcp --zone=public --permanent

ポートが開いてるか確認

sudo firewall-cmd --list-ports --zone=public
telnet example.com 110

メーラーに設定して送受信のテストを行う。

送信も受信もできれば、メールサーバの構築は完了。

めでたし、めでたし。

次回は、PHPをキックしてメールをパースしてDBに格納するよっ♥

お楽しみにね★

Laravelでカスタムフォーム使ってViewを綺麗に保とうず

どうも。
お久しぶり、あなたのベル子です。

ベル子の雑談ファンのみんな安心してくれたまえ。
やたら長くなったから巻末に付いておるぞよ。

本題

みなさんLaravel Collective使ってますか?

laravelcollective.com

「何それ、美味しそう」マンの諸君への解説

説明しよう。
Laravel CollectiveとはLaravelのアップデートでコアのフレームワークからは削除されてしまったLaravelの機能を、ライブラリとして組み込むことができるライブラリである。

大きく分けて5種類の機能があり、それぞれcomposerでinstallする必要がある。

Annotations

関連するメタデータを付与してやることで、イベントをリッスンしたり、ルーティングしたりできるという機能。
Laravel5で追加されるはずの機能だったようですが、最終的なリリース版からは切り離されてしまったようです。

Forms & HTML

bladeテンプレートで使用できるフォームとhtmlのヘルパーを集めたパッケージ。
Laravel5でForm、Htmlファサードコンポーネントはコアから削除されてしまいました。

元々、私はフロントエンジニアなんで、このFormコンポーネントのコードが逆に長くて読みづらくてうざいので、後述のカスタムマクロするときくらいしか使っていなかったんですが、Input::old()をいちいち書くのがうざいということもあり、最近はナチュラルに受け止められるようになりました。

www.dn-web64.com

SSH

Laravelコード内でSSHで何かやるときに使う機能だったけど、今はそういうのはEnvoy使えよってことで削除されたらしいです。

IronMQ Laravel Queue Driver

ちょっと前にキューのチュートリアルを書きましたが、Laravel 5.2からフレームワーク側で用意してくれているキュードライバーの設定からIronMQが削除されたので、IronMQ使いたい人はこれを突っ込めよって話です。

Command Bus

5.1で消えたようです。
コントローラーのアクション内での様々な処理をタスクとして分けてコマンドを実行するというような実装パターンの時に、タスクをカプセル化する便利なメソッドだそうで、
どういうときに使うかというと、こないだ書いたようなキューに押し込む必要があるようなメール送信とか、そういうのをハンドリングしたいときに使うものだったようなのですが、
あんまり使ったことある人いないかもしれませんが、Laravelには5.1からJobという概念が出てきました。

このCommand Busで作るコマンドのクラスを作成するときmake:commandというコマンドを叩いてapp/Commandsの配下にできるということだったんですけど、これLaravel 4.2使ってる人だと分かると思うんですけど、コンソールコマンドと混同してしまいますよね。

たぶん分かりづらいねってことになって名前とディレクトリを切り離したんだと思います。

だいたい使うことがあるのはForms & HTML

前置きが長くなりましたが、だいたい使うことがあるのはフォームヘルパーです。

先述したとおり、バリデーションでリダイレクトバックした際に前にインプットしてあったデータを再度セットしてやるInput::old()、今のバージョンだとold()をフォームに書くのがうざいという理由で使っている人が多いんじゃないでしょうか。
あとselectが1行で書けるというのもありますね。

正直、引数がバラバラで覚えづらいので、Emmetで一瞬で書けるフォームパーツをいちいちドキュメントを見ながら書くというのが非常につらいので、それ以外はあまりメリットは感じられないです。

加えて、フォーム系はフロント側でdata属性を追加しないといけないことが多くて、こういうことをフォームヘルパーでやろうとするとカスタムで作らないといけませんでした。
最新版では引数で渡せるようになったようですが。
古い案件はつらいですね。

ただ、このカスタムでform&htmlパーツが作れるというのが、このライブラリの最強にして最大の魅力

なんです!!

↓インストール方法はこちらの分かりやすい記事を参照してください。

qiita.com

例えば、時刻を入力させるのに

  • 時間と分はそれぞれ別のプルダウン形式で入力
  • 時間は1~24までの1時間刻みで。
  • 分は00から55までの5分刻みで。

のようなフォームパーツがアプリケーションのいたるところに登場するシステムを想像してみてください。

これを自力でViewに書いていくとどうなるか。

<div class="row">
    <div class="col-md-3 form-inline">
        <select class="form-control" id="test_time_h" name="test_table[test_time_h]">
            <option value="01">01</option>
            <option value="02">02</option>
            <option value="03">03</option>
            <option value="04">04</option>
            <option value="05">05</option>
            <option value="06">06</option>
            <option value="07">07</option>
            <option value="08">08</option>
            <option value="09">09</option>
            <option value="10">10</option>
            <option value="11">11</option>
            <option value="12">12</option>
            <option value="13">13</option>
            <option value="14">14</option>
            <option value="15" selected="selected">15</option>
            <option value="16">16</option>
            <option value="17">17</option>
            <option value="18">18</option>
            <option value="19">19</option>
            <option value="20">20</option>
            <option value="21">21</option>
            <option value="22">22</option>
            <option value="23">23</option>
            <option value="24">24</option>
        </select>
        <span></span>
    </div>
    <div class="col-md-3 form-inline">
        <select class="form-control"  id="test_time_i" name="test_table[test_time_i]">
            <option value="00">00</option>
            <option value="05">05</option>
            <option value="10">10</option>
            <option value="15">15</option>
            <option value="20">20</option>
            <option value="25">25</option>
            <option value="30">30</option>
            <option value="35">35</option>
            <option value="40" selected="selected">40</option>
            <option value="45">45</option>
            <option value="50">50</option>
            <option value="55">55</option>
        </select>
        <span></span>
    </div>
</div>

はい。こんなコードを自力で書くのなんて、ぞっとしますね。
中間部分はループさせられるとしても、こんなん大量にあるのにいちいち全部同じように書くなんて想像するだけで、ぞっとします。

それをカスタムフォームヘルパーを使えばあら不思議!!

{!! Form::selectTime('test_table', 'test_time', $row->test_time, ['class' => 'form-control']) !!}

たったの1行で書けてしまうんですね。

▼カスタムフォームヘルパーの実装方法の参考ページ qiita.com

カスタムフォームヘルパーの実装方法は↑こちらの方の方法とほぼ同様に実装してますが、私は以下のようにapp直下に入れてます。

<?php

namespace App\Providers;

use Form;
use Illuminate\Support\ServiceProvider;

class FormMacroServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {

        require app_path().'/macros.php';

    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Laravel 4.2ではデフォでフォームヘルパー使えるのでライブラリのインストールは必要なくて、カスタムしたかったらapp/start/global.phpで以下のようにmacroを書いたphpファイルをrequireしてやればいいです。

require app_path().'/macros.php';

別にフォームだけじゃなくてhtmlもオレオレパーツ作り放題

システムを通して毎回出てくる系のhtmlを作っておくと、これまた1行で書けてすごい便利です。
業務システムとか、この特殊ラベルどのページにも出てきますやんみたいなのありがちですよね。(アイコン付きの出荷済み、発送済みとかそいういうやつ)

あれも引数にkey渡すだけで全てよしなに表示してくれるパーツが作れます。

アイコン変えたかったら1箇所変えるだけ。

超絶DRY
超絶DRY

大事なことだから2回言いました。

雑談コーナー

最近、イラストを公式Twitterにアップしている関係で、ベル子さんの趣味は絵を描くことなのかしらと思ってる人が多いと思うんですが、私の最大の趣味はプロフィールの趣味のところにもあるとおり、小説の執筆です。

「野いちご」「魔法のiらんど」といった携帯小説時代から書いていまして、小さな賞とかをもらったこともあるんですが、まあ、ずっと書いてる割にあまり自慢できる成績は残せていません。

好きな作家が乙一東野圭吾江戸川乱歩のようなあまり陽気じゃない路線なので、周りの人に明るく「これ読んでみてー♡」と言えないのが上手にならない最大の要因なんですが、初期の頃の作品と比べるとかなり文章がマシになってきました。
が、ストーリーは結構、初期のもののほうが面白いので、リライトしようかなーと、最近、思ってます。

文章を書くことを勧めてくれたのは小学校の5年のときの担任の先生で、すごく嫌っていた先生なんですが、「あなたは文学的な才能を持っているので、文章を書いたほうがいいと思います」と、とにかく私を理解して肯定しようと努力してくれた人です。

今の私はついつい彼と同じことやってしまうんですけど、ありがたいけど、うざったいんですよね。気を付けないと。

でも、お陰様で翻訳やちょっとした記事を書くといった文章を書く仕事をすることもできましたし、絵以外の趣味にも出会えました。

サンキュー、高橋!
(↑先生を面と向かって呼び捨てで呼んでたけど、今思うとどんだけエキセントリックなんだよって感じ)

最後まで読み切った、そこの君、明日幸運が訪れます!
ありがとう★

9Sと2Bを描いてみた

f:id:arm4:20171127104146p:plain

どーん。
ここのところ、通勤中やら、寝る前やらに描いていたニーアオートマタの9Sと2Bのイラストが仕上がったのでブログで紹介したいと思いますよ。

小さくなるとナインズの目があんまり見えなくて怖いですね。
イラストって大きさによって結構印象が変わるから、拡大したり縮小したりしながら描いたほうがいいかも。

あと、最近思ったのは左右逆にしながら描いていくとバランス取りやすいですね。何ていうか効き顔っていうんですが、描きやすいほうの顔の向きとかあるんで。

↓描き初めの頃のナインズ。角度変えただけでも結構印象違う。

f:id:arm4:20171127104949p:plain

ついでに服の描き込みをする前のものも貼っておきます。 ちょっと2Bの顔を最後に変えたので違う顔してますね。

f:id:arm4:20171127105134p:plain

こんな感じで技術だけじゃなくてイラストもこれからこちらのブログで紹介していこうかと思いますー。

普段はファンアートよりオリジナルが多いよー。
さらに言うと女の子ばっかり描くよー。
よろしくね★

gitのブランチ名を入れ替えたい

たとえば、ちょっと大きな修正を行っているfeatureブランチが存在するとする。
デザインリニューアルとかそんな感じの。

ブランチ一覧

  • new_design
  • staging
  • develop
  • master

new_designでは現行バージョンから廃止される機能などもあるので、基本的にdevelopからコードを取得してマージしていない。

現行でも新デザインでも必要なbug_fixはそれぞれ適宜に取り込んでいる。

そんなこんなで、晴れて、新デザインをリリースすることになった。

基本的に違うバージョンなのでnew_designをdevelopにマージすることは できない。

ということはnew_designから新たにmasterを作ってデプロイというような流れになると思う。

今後はnew_designで開発を続けて、現状のdevelopブランチは更新されない。

new_designで開発を続けて、master02にマージしてデプロイ?

ブランチ一覧?

  • new_design
  • master02
  • staging02
  • staging
  • develop
  • master

この経緯を知らない新人PGが入ってきて環境を作ってコードを修正してと言われたとする。

(?????
よく分からないけど、とりあえずdevelopからブランチ作って作業すればいいのかな。。。よく分からないけど。。)

たとえnew_designからブランチを作ってと指示されていたとしても、上のようなブランチ一覧を見たPGは混乱してしまうと思う。

こんなとき、どうするか。

そうだ、リネームしよう。

現状にそっていないネーミングの関数
現状にそっていないネーミングのクラス

PGなら分かると思うが、これらはバグの温床である。

現状にそっていないネーミングのブランチ

これも事故の元となるので、面倒がらずにちゃんとリネームしてあげよう。

重大な変更作業なので、作業の前にはバックアップを取ることを忘れずに。

事前準備

まずは作業前にmaster以外の自分のローカルブランチを削除しておく。

develop → develop_old

# ローカルにリネームした新しいブランチを作成
git branch develop_old origin/develop
# チェックアウト
git checkout develop_old
# 新しいブランチとしてリモートにpushし、上流ブランチに設定
git push -u origin develop_old
# 古いほうのリモートブランチを削除
git push origin :develop

new_design → develop

# ローカルにリネームした新しいブランチを作成
git branch develop origin/new_design
# チェックアウト
git checkout develop
# 新しいブランチとしてリモートにpushし、上流ブランチに設定
git push -u origin develop
# 古いほうのリモートブランチを削除
git push origin :new_design

masterとstagingについても同様の作業をする

ブランチ一覧

  • develop_old
  • master_old
  • staging_old
  • staging
  • develop
  • master

oldではなくバージョン番号にしてもいいと思う。

チームメンバーのローカルでもリネームしてもらう

# ローカルのリモートブランチ情報を最新の状態にフェッチする
git fetch -p
# 状態を確認
git branch -avv
# ローカルのリモートブランチ情報がリモートのブランチ一覧と合致していることを確認
# masterブランチにチェックアウト
git checkout master
# ローカルの作業ブランチを削除
git branch -d new_design
git branch -d develop
# ローカルの作業ブランチを新規作成
git branch develop origin/develop
git branch develop_old origin/develop_old
# 状態を確認
git branch -avv
# 上流ブランチにローカルブランチ名と同じ名前のリモートブランチが設定されていることを確認

ブランチ名の入れ替えは、結構思い切った作業だと思うけど、
それはリファクタリングと同じで
何か起こると怖いから汚いコードを放置しているのと同じ。

放置された汚いコードの上から、さらにコードを付け加えたり削除したりを繰り返すと、いつか誰も読めないグチャグチャなものになり、
もっとも恐れていることが起きる。

いろんな経緯があって、たくさんブランチができてる、怖いから全部残している、その気持ち、非常によく分かります。

でも、家の大掃除だと思って、思い切ってgitのブランチ名を適切な状態に整理しましょう!

Laravel5.4でイベント&キューを使ってメールをキュー送信する手順(3)

Laravel5.4でイベント&キューを使ってメールをキュー送信する手順もとうとう最終回を迎えます!!

上級Laravelerの人はこの回から読んでも大丈夫です。
初級Laravelerの人は過去記事から読んでみてください。

arm4.hatenablog.com

arm4.hatenablog.com

というわけで恒例の今北さん用3行を

  • Laravel新規プロジェクトを作ってダミーデータを挿入した
  • 投稿一覧ページと詳細ページを作っていいねボタンを設置した
  • いいね押したら投稿者にメールでお知らせが行くようにした

今回やること

  • ボタンの種類を増やして種類ごとにメール送信の挙動を変える
  • イベント&リスナーでメール送信を実装
  • リスナーをキュー化して非同期でメールを送信

イベントリスナーって何?

いわゆるオブザーバーパターンと呼ばれるコードの実装方法で、JavaScriptでDOM操作するときにはおなじみの方法です。

ボタンがクリックされたら"click"というイベントが発火して、そのイベントが発火したら特定の処理が走るようにするという実装方法です。

イベント&リスナーとか使ったことない機能で怖いと思うサーバサイドエンジニア諸君もいるかもしれませんが、JavaScriptで普段やってることを、PHPでもやる。ただそれだけのことです。

フロントエンドから来たエンジニアにとっては、むしろ分かりやすくてコードも綺麗になるので、とてもオススメです。

↓これより下は手順だから、ですますやめます。

実装方法を考える

まずはどんなイベントが必要か考える。

今回は「お知らせの種類」ごとにイベントを作成することにした。

  • ユーザー全員に知らせるお知らせイベント
  • 本人だけに知らせるお知らせイベント

今回は処理にフォーカスしたイベントを考えてみたが、イベントには複数のリスナークラス(処理)を紐付けられるので、もっと大きく捉えて「何をした」というイベントを発火させて、その後の処理をリスナーとして登録するという実装方法もあるだろう。

例えば、「いいね」ボタンを押したら、「全員にお知らせメールを送り、アプリの通知機能でお知らせする」のように複数の処理が紐付くような場合は、「○○ボタンが押された」のようなイベントを作成するといいかもしれない。

ルートを追加する

まずは「やだねする」「どうでもいい」「君の名は?」の3種類を押したとき用のルートを追加しよう。

routes/web.php

<?php
.
.
.

Route::post('/send-dislike/{post}', 'HomeController@sendDislike')->name('send.dislike');
Route::post('/send-whatever/{post}', 'HomeController@sendWhatever')->name('send.whatever');
Route::post('/send-yourname/{post}', 'HomeController@sendYourname')->name('send.yourname');

コントローラーを修正

まずはイベント&リスナーを使わず、「いいねする」と同じように本人にだけメールが送信されるところまで作っていこう。

<?php
.
.
.

    public function sendDislike(Request $request, Post $post)
    {
        $triggered_user = Auth::user();

        $data = [
            'type' => 'やだね',
        ];

        // やだねのときは本人だけに知らせる
        Mail::to($post->user)
                ->send(new SendMail($post, $triggered_user, $data));

        return response()->json(['status' => 0]);
    }
    public function sendWhatever(Request $request, Post $post)
    {
        $triggered_user = Auth::user();

        $data = [
            'type' => 'どうでもいい',
        ];

        // どうでもいいときは誰にも知らせない
        
        return response()->json(['status' => 0]);
    }
    public function sendYourname(Request $request, Post $post)
    {
        $triggered_user = Auth::user();

        $data = [
            'type' => '君の名は?',
        ];

        // 君の名は?のときは本人だけに知らせる
        Mail::to($post->user)
                ->send(new SendMail($post, $triggered_user, $data));

        return response()->json(['status' => 0]);
    }

同じことをしてる部分をまとめる

ボタンを押した時に何のボタンかdataに入れてpostしているので、それを使ってデータセット部分を1つのメソッドで済ませるようにリファクタリングしてみる。

<?php
.
.
.
class HomeController extends Controller
{
    private $types = [
        'like'     => 'いいね',
        'dislike'  => 'やだね',
        'whatever' => 'どうでもいい',
        'yourname' => '君の名は?',
    ];

    private $triggered_user = null;
    private $data = null;
.
.
.
    private function setAlertData($request) {
        $this->triggered_user = Auth::user();

        $this->data = [
            'type' => $this->types[$request->type],
        ];
    }

    public function sendLike(Request $request, Post $post)
    {
        $this->setAlertData($request);

        // いいねのときはユーザー全員に知らせる
        Mail::to($post->user)
                ->send(new SendMail($post, $this->triggered_user, $this->data));

        return response()->json(['status' => 0]);
    }

    public function sendDislike(Request $request, Post $post)
    {
        $this->setAlertData($request);

        // やだねのときは本人だけに知らせる
        Mail::to($post->user)
                ->send(new SendMail($post, $this->triggered_user, $this->data));

        return response()->json(['status' => 0]);
    }
    public function sendWhatever(Request $request, Post $post)
    {
        $this->setAlertData($request);

        // どうでもいいときは誰にも知らせない
        
        return response()->json(['status' => 0]);
    }
    public function sendYourname(Request $request, Post $post)
    {
        $this->setAlertData($request);

        // 君の名は?のときは本人だけに知らせる
        Mail::to($post->user)
                ->send(new SendMail($post, $this->triggered_user, $this->data));

        return response()->json(['status' => 0]);
    }

EventServiceProviderにイベントを記載

直書きしていたメール送信部分をイベントにしていく。

プロジェクト作成時から用意されているEventServiceProvider.phpの$listen部分に連想配列の形式で、イベント(key) => リスナー(value)のように記載してイベントとリスナーを登録する。

まだapp/Eventsやapp/Listenersというディレクトリが無くても構わない。ここにクラス名を記載してgenerateコマンドを実行すると、ディレクトリとファイルが作成される。

イベント、リスナーの命名規則はないが、イベント名は「何が起こった」、リスナー名は「何をする」のように付けると分かりやすい。

つなげて読むと、「何が起きたら、何をする」と読めるからだ。

app/Providers/EventServiceProvider.php

<?php
.
.
.
    protected $listen = [
        'App\Events\PersonalAlertCreated' => [
            'App\Listeners\SendPersonalAlert',
        ],
        'App\Events\PublicAlertCreated' => [
            'App\Listeners\SendPublicAlert',
        ],
    ];

イベント&リスナーファイルを作成

generateコマンドを実行する

php artisan event:generate

イベントを発火させる

コントローラーを修正し、メール送信している箇所にイベントを仕込んでいく。

  • 全員に知らせる ( PublicAlertCreated )
  • 本人だけに知らせる ( PersonalAlertCreated )

app/Http/Controllers/HomeController.php

<?php
.
.
.
use App\Events\PublicAlertCreated;
use App\Events\PersonalAlertCreated;
.
.
.
    public function sendLike(Request $request, Post $post)
    {
        $this->setAlertData($request);

        // いいねのときはユーザー全員に知らせる
        event(new PublicAlertCreated($post, $this->triggered_user, $this->data));

        return response()->json(['status' => 0]);
    }

    public function sendDislike(Request $request, Post $post)
    {
        $this->setAlertData($request);

        // やだねのときは本人だけに知らせる
        event(new PersonalAlertCreated($post, $this->triggered_user, $this->data));

        return response()->json(['status' => 0]);
    }

    public function sendWhatever(Request $request, Post $post)
    {
        $this->setAlertData($request);

        // どうでもいいときは誰にも知らせない
        
        return response()->json(['status' => 0]);
    }

    public function sendYourname(Request $request, Post $post)
    {
        $this->setAlertData($request);

        // 君の名は?のときは本人だけに知らせる
        event(new PersonalAlertCreated($post, $this->triggered_user, $this->data));

        return response()->json(['status' => 0]);
    }

イベントファイルを設定

イベントファイルはデータを受け取ってセットするだけのクラスなのでロジックを書くことはない。

コンストラクタの中でイベントで使用するデータをセットする。

PersonalAlertCreatedの記述もまったく同じなので、ここでは省略するが、ファイルは2つともちゃんと設定すること。

app/Events/PublicAlertCreated.php

<?php
.
.
.
use App\Post;
use App\User;
.
.
.
class PublicAlertCreated
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

     public $post;
     public $triggered_user;
     public $data;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(Post $post, User $triggered_user, $data)
    {
        $this->post = $post;
        $this->triggered_user = $triggered_user;
        $this->data = $data;
    }

リスナーファイルを設定

handleの部分に処理を書いていく。

app/Listeners/SendPublicAlert.php

<?php
.
.
.
use App\User;
use App\Mail\SendMail;
use Illuminate\Support\Facades\Mail;
use App\Events\PublicAlertCreated;
.
.
.
    public function handle(PublicAlertCreated $event)
    {
        $post = $event->post;
        $triggered_user = $event->triggered_user;
        $data = $event->data;

        // クリックした人以外の全員に送る
        $users = User::whereNotIn('id', [$triggered_user->id])->get();

        foreach ($users as $user) {
            Mail::to($user)
                    ->send(new SendMail($post, $triggered_user, $data));
        }
    }

app/Listeners/SendPersonalAlert.php

<?php
.
.
.
use App\User;
use App\Mail\SendMail;
use Illuminate\Support\Facades\Mail;
use App\Events\PersonalAlertCreated;
.
.
.
    public function handle(PersonalAlertCreated $event)
    {
        $post = $event->post;
        $triggered_user = $event->triggered_user;
        $data = $event->data;

        Mail::to($post->user)
                ->send(new SendMail($post, $triggered_user, $data));
    }

ここまで設定したら、「いいねする」ボタンを押してみよう。

自分以外の全員あてのメール(およそ40通)が送られてきていたら、イベントリスナーでメール送信の実装はできている。

大量の処理を追加してみよう

ここで、publicAlertのほうに大量にデータを突っ込む処理を入れないといけないことになったとする。

app/Listeners/SendPublicAlert.php

<?php
.
.
.
use App\User;
use App\Mail\SendMail;
use Illuminate\Support\Facades\Mail;
use App\Events\PublicAlertCreated;
.
.
.
    public function handle(PublicAlertCreated $event)
    {
        $post = $event->post;
        $triggered_user = $event->triggered_user;
        $data = $event->data;

        // クリックした人以外の全員に送る
        $users = User::whereNotIn('id', [$triggered_user->id])->get();

        // 大量の処理を追加!!
        for ($i=0;$i<5000;$i++) {
            $users = User::whereNotIn('id', [$triggered_user->id])->get();
        }

        // みんなに知られてしまうアラート
        foreach ($users as $user) {
            Mail::to($user)
                    ->send(new SendMail($post, $triggered_user, $data));
        }
    }

これで「いいねする」ボタンを押してみた人は気づいたと思うが、40通のメールの送信処理&5000回のクエリ取得が終わってからレスポンスが返ってくるので、「メール完了 メールを送りました!」の表示が表示されるのはボタンを押してからしばらく経ってからになる。

本番のアプリケーションでは二重投稿を防止する処理などが入っていて、レスポンスが返ってくるまでユーザーがボタンを押せないこともあるだろう。

送信完了のお知らせ表示が表示される時間が、送信ユーザー数に依存するというのはユーザーにとって分かりづらいため、送信できていないと勘違いして連投してしまう人もいるかもしれない。

多くのユーザーにメールを送信したり、多くの通知用データを挿入したりする処理を挟んでしまうと、その処理が完了してレスポンスが返ってくるまでに時間がかかる。

こんなときは、処理の受付だけ済ませて、あとの処理はバックグラウンドでやってもらいたいと思う。

誰も32個の小包の発送手続きが完了する様を20分立ったまま眺めていたくはない。
32個の小包を誰かに渡して、「住所は貼っといたから、あとはよろしく」と言いたいのだ。

そこでキューの登場

この「あとは、そっちでよろしく」方式のことを、キューと呼ぶ。待ち行列という意味だ。

ユーザーは自分の代わりに、小人たちを列に並ばせて小包を32個発送させられる。この小人のことをワークやジョブと呼ぶ。

キューを使うには、小人を並ばせるためのテーブルが必要だ。

Laravelではジョブを管理するDriverは以下の中から選べる。

  • sync
  • database
  • Amazon SQS
  • Beanstalkd
  • Redis

今回はアプリで使ってるDBにキュー用テーブルを作成して実装するdatabaseを選択することにする。

キューの設定をする

.env

QUEUE_DRIVER=database

config/queue.php

ひとまずqueue.phpはデフォルトのままでOK。

ジョブを発行するためのテーブルを作成

以下のコマンドを実行するとジョブテーブルのmigrationが出来る。

failed-tableのほうはその名のとおり、エラーを吐いたジョブを記録しておくテーブルだ。こちらは、作らなくてもキュー化はできる。

php artisan queue:table
php artisan queue:failed-table

マイグレーションファイルが出来たのでmigrateを実行しよう。

php artisan migrate

jobsとfailed_jobsというテーブルがDBに作成された。

メール送信部分をキュー化

テーブルができたら、SendPersonalAlert.phpとSendPublicAlert.phpのMailのsendをqueueとするだけ。

SendPublicAlert

            Mail::to($user)
                    ->queue(new SendMail($post, $triggered_user, $data));

SendPersonalAlert

        Mail::to($post->user)
                ->queue(new SendMail($post, $triggered_user, $data));

これでメール送信処理のジョブがテーブルの中に並ぶようになる。

あとはジョブが受付に並んだらそれを検知して実際に処理をしてくれる人が必要だ。

ワーカーを立ち上げる

ジョブを処理するプロセスを起動させる。

php artisan queue:work --tries=3

このワーカーは手動でストップするか、ターミナルを閉じるまで起動している。
バックグラウンドで永続的に起動させておきたい場合は、Supervisorなどのプロセス永続化ツールを導入しよう。

これでメール送信のほうはキュー化できた。

いったん話をメール送信だけにしたいので、大量処理の部分はコメントアウトしてからいいねボタンを押してみて欲しい。

app/Listeners/SendPublicAlert.php

<?php
.
.
.
        // 大量の処理を追加!!
        // for ($i=0;$i<5000;$i++) {
        //     $users = User::whereNotIn('id', [$triggered_user->id])->get();
        // }

Chromeのデブツールなどで観察すれば、すぐにレスポンスが返ってきていることが分かると思う。

ターミナルに処理ログが表示されていればキューで処理が実行されている。

でもまだ大量インサートの問題が残っている。
もちろん、それもキューにしたい!

リスナーをキュー化する

Laravelコマンドでgenerateすると、リスナーファイルは最初からこのShouldQueueというインターフェースを読み込んでいる。

このIlluminate\Contracts\Queue\ShouldQueueをリスナークラスに実装してあげると、キューにして処理させるべきクラスだということをLaravelに知らせることができる。

app/Listeners/SendPublicAlert.php

<?php
.
.
.
class SendPublicAlert implements ShouldQueue
{

では、さっそく大量処理部分のコメントアウトを外して、いいねボタンを押してみよう。

f:id:arm4:20170911180222p:plain

コードの変更が反映されてないと思ったら、いったんワーカーを立ち上げ直してみよう。

デーモンで立ち上げている場合は、起動時のコードがメモリにロードされていてそれで実行されてしまうためだ。

まとめ

いやーまとめるのは時間かかったけど、こうやって読み返すと、Laravelで重たい処理をキュー化するのが、いかに簡単かということが分かっていただけたことと思います!!

バッチだけじゃなくて、キューで処理というのも、いろんなことに活用できるような気がしますね。

キューにしなくても、イベント&リスナーの実装はコードが整理されて気持ちよくコーディングできるのでいいなと思いましたYO★

参考になった人は是非★をください!!

▼サンプルコードをgithubにアップしたよーー。

github.com